diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json new file mode 100644 index 000000000..18bae550c --- /dev/null +++ b/.config/dotnet-tools.json @@ -0,0 +1,13 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "gitversion.tool": { + "version": "6.1.0", + "commands": [ + "dotnet-gitversion" + ], + "rollForward": false + } + } +} \ No newline at end of file diff --git a/.gitignore b/.gitignore index b5fc713bc..6cbd591e0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,15 +1,119 @@ -obj -bin -*.user +# Build Folders (you can keep bin if you'd like, to store dlls and pdbs) +[Bb]in/ +[Oo]bj/ +build/ + +# mstest test results +TestResults + +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files *.suo -*.orig -output -publish-* +*.user +*.sln.docstates + +# Build results +[Dd]ebug/ +[Rr]elease/ +x64/ +*_i.c +*_p.c +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.log +*.vspscc +*.vssscc +.builds + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf + +# Visual Studio profiler +*.psess +*.vsp +*.vspx + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper* + +# NCrunch +*.ncrunch* +.*crunch*.local.xml + +# Installshield output folder +[Ee]xpress + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish + +# Publish Web Output +*.Publish.xml + +# NuGet Packages Directory packages -_ReSharper.* -_dotCover.* -*.Resharper + +# Windows Azure Build Output +csx +*.build.csdef + +# Windows Store app package directory +AppPackages/ + +# Others +[Bb]in +[Oo]bj +sql +TestResults +[Tt]est[Rr]esult* *.Cache -*.cache -~$* -[Pp]ublish \ No newline at end of file +ClientBin +[Ss]tyle[Cc]op.* +~$* +*.dbmdl +Generated_Code #added for RIA/Silverlight projects + +# Backup & report files from converting an old project file to a newer +# Visual Studio version. Backup files are not needed, because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +publish-net40/ +nunit.TestResult.* +/output + +# Visual Studio options and artifacts +.vs/ +*.lock.json + +# Custom +artifacts/ \ No newline at end of file diff --git a/Build.RunTask.bat b/Build.RunTask.bat deleted file mode 100644 index 5bc0b2f0e..000000000 --- a/Build.RunTask.bat +++ /dev/null @@ -1,2 +0,0 @@ -@echo off -powershell -NoProfile -ExecutionPolicy unrestricted -Command "& .\build\Build.RunTask.ps1 %*" \ No newline at end of file diff --git a/Changelog.md b/Changelog.md new file mode 100644 index 000000000..f1c4c8d0d --- /dev/null +++ b/Changelog.md @@ -0,0 +1,200 @@ +# NEventStore Versions + +## 10.1.1 + +### BugFix + +- Fixed Assemblies Version Numbers (AssemblyInfo files). + +## 10.1.0 + +- Improved `IEventStream` interface: `CommitChanges()` and `CommitChangesAsync()` now return `ICommit` instead of `void`. +- Updated MessagePack serializer to 3.1.3 + +### Breaking Changes + +- `IEventStream.CommitChanges()` and `IEventStream.CommitChangesAsync()` now return `ICommit` instead of `void`. + +## 10.0.1 + +### BugFix + +- Async Pipeline Hooks: initialization and PreCommit/PostCommit invocation bugs [#516](https://github.com/NEventStore/NEventStore/issues/516) + +## 10.0.0 + +- Async Methods to read from and write to streams (IStoreEvents, IEventStream, IPersistStreams, IPersistStreamsAsync, ICommitEventsAsync, IAccessSnapshotsAsync). [#513](https://github.com/NEventStore/NEventStore/issues/513) + - methods that read from a stream in an async way follow the Observer pattern and requires you to pass in an `IAsyncObservable` that will receive data as soon as they are available. +- Async Pipeline Hooks (IPipelineHookAsync). [#515](https://github.com/NEventStore/NEventStore/issues/515) +- AsyncPollingClient: a new polling client implementation that uses Async interfaces. [#505](https://github.com/NEventStore/NEventStore/issues/505) +- Removed the BinarySerializer (BinaryFormatter) from the core package and moved it to its own package [#510](https://github.com/NEventStore/NEventStore/issues/510) +- Improved comments and added more nullability checks. +- Minor performance improvements. +- Updated Testing Packages (NUnit, FluentAssertions, Microsoft.NET.Test and so on...). + +### Breaking Changes + +- `PersistStreamsExtensions.GetFrom(IPersistStreams, DateTime)` and `PersistStreamsExtensions.GetFromTo(IPersistStreams, DateTime, DateTime)` extension methods have been removed: they had inconsistent behavior with the other GetFrom(checkpointToken) methods, + they were getting data from the default bucket only. +- `PipelineHooksAwarePersistanceDecorator` renamed to `PipelineHooksAwarePersistStreamsDecorator`. +- `IPipelineHook.Select` method renamed to `IPipelineHook.SelectCommit`. +- `BinarySerializer` moved to its own package: `NEventStore.Serialization.Binary`. + - for net8.0+ call `AppContext.SetSwitch("System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization", true);` to enable unsafe BinaryFormatter usage. +- Improved many method signature with nullability annotations. +- `Wireup.With()` renamed `Wireup.Register()`. +- `OptimisticEventStream` constructors replaced by initialization functions: + - `new OptimisticEventStream(string bucketId, string streamId, ICommitEvents persistence, int minRevision, int maxRevision)` -> `new OptimisticEventStream(string bucketId, string streamId, ICommitEvents persistence).Initialize(int minRevision, int maxRevision)`. + - `new OptimisticEventStream(ISnapshot snapshot, ICommitEvents persistence, int maxRevision)` -> `new OptimisticEventStream(string bucketId, string streamId, ICommitEvents persistence).Initialize(ISnapshot snapshot, int maxRevision)`. + +## 9.2.0 + +- Updated nuget packages to include symbol packages and more information. +- Updated Newtonsoft.Bson 13.0.3 +- Added MessagePack serializer, thanks to [@pvagnozzi](https://github.com/pvagnozzi) +- Improved comments and removed some compilation warnings. + +## 9.1.1 + +- Fixed `build.ps1` script to correctly update Assembly Version number before building. +- Updated Readme with how Versioning works. + +## 9.1.0 + +- Support the following Target Frameworks only: netstandard2.0, net462. +- Updated Newtonsoft.Json 13.0.3 + +## 9.0.1 + +- Added documentation files to NuGet packages (improved intellisense support) [#496](https://github.com/NEventStore/NEventStore/issues/496) + +## 9.0.0 + +- Added support for .net 6 [#493](https://github.com/NEventStore/NEventStore/issues/493). +- Change / Optimization: Commit and CommitAttempt do not create internal read-only collections anymore, it can be useless given that we can change properties of events. +- NEventStore.Serialization.Json: accepts a JsonSerializerSettings to configure the serializer. + +## 8.0.0 + +- Added support for .net 5 [#489](https://github.com/NEventStore/NEventStore/issues/489). +- Added support for .net framework 4.6.1. +- Fixed InMemoryPersistenceEngine.AddSnapshot() behavior: adding multiple snapshots for the same tuple bucketId, streamId, streamRevision is not allowed; the updated snapshot will be ignored [#484](https://github.com/NEventStore/NEventStore/pull/484). +- Logging infrastructure switched to [Microsoft.Extensions.Logging](https://docs.microsoft.com/en-us/dotnet/core/extensions/logging) [#454](https://github.com/NEventStore/NEventStore/issues/454), [#488](https://github.com/NEventStore/NEventStore/pull/488). +- Reviewed Exception (and logging) messages: many of those that refer to a StreamId should also provide BucketId information [#480](https://github.com/NEventStore/NEventStore/issues/480) + +### Breaking Changes + +- Dropped support for .NET Framework 4.5, only .NET 4.6.1+ will be supported in 8.x. .NET Framework support will be dropped in a future revision. +- Logging switched to Microsoft.Extensions.Logging, old logging code and configuration functions have been removed. + +## 7.0.0 + +- The IPersistStreams interface got some major changes: + - Added new `GetFromTo(Int64, Int64)` and `GetFromTo(string, Int64, Int64)` methods to the IPersistStreams interface. + - Extension methods `PersistStreamsExtensions.GetFrom(DateTime)` and `PersistStreamsExtensions.GetFromTo(DateTime, DateTime)` were marked obsolete and will be removed. + - A new PersistStreamsExtensions.GetCommit(Int64) method was added to retrieve a single commit [#445](https://github.com/NEventStore/NEventStore/issues/445). +- PollingClient was moved to its own NEventStore.PollingClient NuGet package [#467](https://github.com/NEventStore/NEventStore/issues/467). +- Added more information to the DuplicateCommitException error message (StreamId and BucketId), also the information provided by the Persistence providers will be reviewed [#372](https://github.com/NEventStore/NEventStore/issues/372). + +### Breaking Changes + +- The default value of 0 has been removed from the `IPersistStreams.GetFrom(Int64)` method. +- Removed the almost useless `GetFromStart()` extension method: use `IPersistStream.GetFrom(0)`. +- Bson serializer was moved from NEventStore.Serialization.Json to its own package: `NEventStore.Serialization.Bson`. Closes: [#479](https://github.com/NEventStore/NEventStore/issues/479). +- PollingClient was moved to its own package: add a reference to NEventStore.PollingClient NuGet package. Also the namespace was changed from NEventStore.Client to NEventStorePollingClient. + +## 6.1.0 + +Enlist in ambient transaction has been removed from the mail library and added to the persistence drivers implementations, each driver has its own way to support, enable or disable the feature. As of now this change will mainly impact Microsoft SQL Server users, because all other persistence plugins didn't use transactions at all. + +All the transactions (or their suppression) should be explicitly managed by the user. + +Minor optimizations were made if no pipeline hooks are used. + +### Breaking Changes + +- `PipelineHookBase`: changed the way the Dispose pattern was implemented to be compliant with the framework guidelines. Move all the Dispose logic to the overridden Dispose(bool disposing) method of your pipeline hook class. +- `OptimisticPipelineHook` optimization is not configured and enabled by default (if not enlisting in ambient transactions) anymore; it now must be explicitly enabled calling UseOptimisticPipelineHook() when configuring NEventStore. Do not use it if you plan to use transactions. To restore the previous behavior call .UseOptimisticPipelineHook() when configuring NEventStore. +- `EnlistInAmbientTransaction` has been removed from the core NEventStore library. It will be added to specific persistence drivers implementations. + +## 6.0.0 + +__Version 6.x is not backwards compatible with version 5.x.__ Updating to NEventStore 6.x without doing some preparation work will result in problems. + +### New Features + +- dotnet standard 2.0 , dotnet core 2.0 are now supported for the following projects: NEventStore, NEventStore.Domain, NEventStore.Persistence.Sql, NEventStore.Persistence.MongoDb + +### Breaking Changes + +- **Removed Dispatcher and dispatching mechanic, use the PollingClient**: it was marked obsolete in the version 5.x, you should dispatch events with other mechanisms, like using a PollingClient. +More information on this topic in the issue: [Race condition in sync and async dispatchers can result in subscribers getting commits / events out of order](https://github.com/NEventStore/NEventStore/issues/360). +- **Removed LongCheckpoint class**: checkpoint now is a plain Int64, there is no need to keep a LongCheckpoint class anymore. +- **PollingClient was removed because it used to depend on Rx**: you can [read more information here](src/NEventStore/Client/README.MD). The new polling client class is called PollingClient2, this however should be considered as a sample implementation you can use to derive your own. +- **JsonSerializer and BsonSerializer were moved in a separate assembly**: if you need them, you should reference the NEventStore.Serialization.Json assembly or implement your own serializers that depend on the Json.Net version you need. +- **EventMessage** class is now sealed. +- **`OptimisticEventStream` throws exceptions if a null message or a message with null body is added to the stream**. Previously if you called Add with null event message or add with an event message with null body, the add operation was ignored without any warning or error. + +## 6.0.0-rc-1 + +New features: + +- improved logging performances ([#468](https://github.com/NEventStore/NEventStore/issues/468)). + +Bug fixed: + +- adding events in the middle of a commit should throw ConcurrencyException ([#420](https://github.com/NEventStore/NEventStore/issues/420)). + +## 6.0.0-rc-0 + +__Version 6.x is not backwards compatible with version 5.x.__ Updating to NEventStore 6.x without doing some preparation work will result in problems. + +### New Features + +- dotnet standard 2.0 , dotnet core 2.0 are now supported for the following projects: NEventStore, NEventStore.Domain, NEventStore.Persistence.Sql, NEventStore.Persistence.MongoDb + +### Breaking changes + +- **Removed Dispatcher and dispatching mechanic, use the PollingClient**: it was marked obsolete in the version 5.x, you should dispatch events with other mechanisms, like using a PollingClient. +More information on this topic in the issue: [Race condition in sync and async dispatchers can result in subscribers getting commits / events out of order](https://github.com/NEventStore/NEventStore/issues/360). +- **Removed LongCheckpoint class**: checkpoint now is a plain Int64, there is no need to keep a LongCheckpoint class anymore. +- **PollingClient was removed because it used to depend on Rx**: you can [read more information here](src/NEventStore/Client/README.MD). The new polling client class is called PollingClient2, this however should be considered as a sample implementation you can use to derive your own. +- **JsonSerializer and BsonSerializer were moved in a separate assembly**: if you need them, you should reference the NEventStore.Serialization.Json assembly or implement your own serializers that depend on the Json.Net version you need. +- **EventMessage** class is now sealed. +- **OptimisticEventStream throws exceptions if a null message or a message with null body is added to the stream**. Previously if you called Add with null event message or add with an event message with null body, the add operation was ignored without any warning or error. + +### Other Notes + +All persistence providers: + +- [MongoDb](https://github.com/NEventStore/NEventStore.Persistence.MongoDB) +- [Sql](https://github.com/NEventStore/NEventStore.Persistence.SQL) +- [RavenDb](https://github.com/NEventStore/NEventStore.Persistence.RavenDB) - currently not maintained anymore. + +are now hosted in their own project. + +Common Domain is now moved in its [own repository](https://github.com/NEventStore/NEventStore.Domain). + +## 5.x.x + +Note: Version 5 is not backwards compatible with v4. Updating to v5 without doing some preparation work will result in problems. + +### Breaking Changes + +1. Underlying schema has changed for all v5 storage engines. In order to migrate a store from v4 to v5 use NEventStore.Migrations +1.The concept of a 'Bucket' has been added as a container for streams allowing multi-tenancy, partitions, multiple-bounded contexts, sagas, etc to be stored in the one store. The API changes have been such that, using extension methods, operations will work on the default bucket, unless a bucket Id has been explicitly supplied. This should mean minimal code changes for the user. +1.Stream Ids are now string based and are limited to 1000 characters. +In the SQL engines the stream Id's are limited to 40 characters and are hashed versions of the actual StreamId. +The hashing function can be overridden during wire-up. + +### New Features + +#### Polling Client + +As an alternative to the dispatcher mechanism and improved replay / catch-up story we have implemented a CheckpointNumber in the stores that guarantees ordering across the streams. This number is guaranteed to increment but not guaranteed to be sequential. This allows you to get all Commits from a specific checkpoint and observe new ones. This implementation is polling based (and thus works for all engines) so it doesn't have the same low-latency attributes of the dispatcher mechanism. You can see how to use it here: [https://gist.github.com/damianh/6370328](https://gist.github.com/damianh/6370328) .In this, instead of the store tracking what has been dispatched, the onus is on the client to track what it has seen. And upon restart, start subscribing from what it last saw. + +In the future I'd like to see / implement reactive clients that leverage stores that are observable. + +### Other Notes + +1. Only SQL and MongoDB persistence engines are supported in this release. RavenDB engine will be shipped later. +1. RavenDB and MongoDB persistence engines are now in their own repositories and will have be shipped independently. diff --git a/GitVersion.yml b/GitVersion.yml new file mode 100644 index 000000000..27f681df6 --- /dev/null +++ b/GitVersion.yml @@ -0,0 +1,5 @@ +mode: ContinuousDeployment +branches: {} +ignore: + sha: [] +merge-message-formats: {} diff --git a/Readme.md b/Readme.md new file mode 100644 index 000000000..aae0b792f --- /dev/null +++ b/Readme.md @@ -0,0 +1,62 @@ +NEventStore +=== + +NEventStore is a persistence library used to abstract different storage implementations when using event sourcing as storage mechanism. + +This library is developed with a specific focus on [DDD](http://en.wikipedia.org/wiki/Domain-driven_design)/[CQRS](https://en.wikipedia.org/wiki/Command%E2%80%93query_separation#Command_query_responsibility_segregation) applications. + +NEventStore currently supports: + +- .net standard 2.0 +- .net framework 4.6.2 + +Starting from Version 6.0.0 NEventStore will use [Semantic Versioning](https://semver.org/) to track the version numbers. + +Build Status (AppVeyor) +=== + +Branches: + +- master [![Build status](https://ci.appveyor.com/api/projects/status/frg36pb2oh1j2ddi/branch/master?svg=true)](https://ci.appveyor.com/project/AGiorgetti/neventstore/branch/master) +- develop [![Build status](https://ci.appveyor.com/api/projects/status/frg36pb2oh1j2ddi/branch/develop?svg=true)](https://ci.appveyor.com/project/AGiorgetti/neventstore/branch/develop) + +Main Library Packages +=== + +- NEventStore - the core library package. +- NEventStore.Serialization.Json - Json serialization to be used with an IDocumentSerializer. +- NEventStore.Serialization.Bson - BSon serialization to be used with an IDocumentSerializer. +- NEventStore.Serialization.MsgPack - Message Pack serialization to be used with an IDocumentSerializer. +- NEventStore.PollingClient - provides an implementation for a PollingClient. + +Documentation +=== + +Please see the [documentation](https://github.com/NEventStore/NEventStore/wiki) to get started and for more information. + +ChangeLog can be [found here](https://github.com/NEventStore/NEventStore/blob/master/Changelog.md) + +### Developed with: + +[![Resharper](http://neventstore.org/images/logo_resharper_small.gif)](http://www.jetbrains.com/resharper/) +[![TeamCity](http://neventstore.org/images/logo_teamcity_small.gif)](http://www.jetbrains.com/teamcity/) +[![dotCover](http://neventstore.org/images/logo_dotcover_small.gif)](http://www.jetbrains.com/dotcover/) +[![dotTrace](http://neventstore.org/images/logo_dottrace_small.gif)](http://www.jetbrains.com/dottrace/) + +# How to build (Windows OS) + +To build the project locally on a Windows Machine: + +- Open a Powershell console in Administrative mode and run the build script `build.ps1` in the root of the repository. + +## Versioning + +Versioning is done automatically by the build script updating the +AssemblyInfo.cs file (false in .csproj files) +before the build starts. The version number is retrieved +from the git repository tags using "gitversion" tool. + +Things are handled this way because NEventStore is used a submodule in other projects and it +need to have it's own version number when building other projects. + +You should not update the version number manually, not commit the updated AssemblyInfo files. \ No newline at end of file diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 000000000..b1d022cbf --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,59 @@ +version: 1.0.{build} +image: Visual Studio 2022 +configuration: Release +assembly_info: + patch: true + file: '**\AssemblyInfo.*' + assembly_version: '{version}' + assembly_file_version: '{version}' + assembly_informational_version: '{version}' +dotnet_csproj: + patch: true + file: '**\*.csproj' + version: '{version}' + version_prefix: '{version}' + package_version: '{version}' + assembly_version: '{version}' + file_version: '{version}' + informational_version: '{version}' +install: +- ps: choco install gitversion.portable -y +before_build: +- ps: >- + # Display .NET Core version + + dotnet --version + + # Display minimal restore text + + dotnet restore ./src/NEventStore.Core.sln --verbosity m + + gitversion /l console /output buildserver /updateAssemblyInfo +build: + project: src/NEventStore.Core.sln + verbosity: minimal +after_build: +- cmd: >- + dotnet pack ./src/NEventStore/NEventStore.Core.csproj -c %CONFIGURATION% --no-build -o artifacts /p:PackageVersion=%GitVersion_SemVer% + + dotnet pack ./src/NEventStore.PollingClient/NEventStore.PollingClient.csproj -c %CONFIGURATION% --no-build -o artifacts /p:PackageVersion=%GitVersion_SemVer% + + dotnet pack ./src/NEventStore.Serialization.Json/NEventStore.Serialization.Json.Core.csproj -c %CONFIGURATION% --no-build -o artifacts /p:PackageVersion=%GitVersion_SemVer% + + dotnet pack ./src/NEventStore.Serialization.Bson/NEventStore.Serialization.Bson.Core.csproj -c %CONFIGURATION% --no-build -o artifacts /p:PackageVersion=%GitVersion_SemVer% + + dotnet pack ./src/NEventStore.Serialization.MsgPack/NEventStore.Serialization.MsgPack.Core.csproj -c %CONFIGURATION% --no-build -o artifacts /p:PackageVersion=%GitVersion_SemVer% + + dotnet pack ./src/NEventStore.Serialization.Binary/NEventStore.Serialization.Binary.Core.csproj -c %CONFIGURATION% --no-build -o artifacts /p:PackageVersion=%GitVersion_SemVer% +test: + assemblies: + except: + - NEventStore.Persistence.AcceptanceTests.dll + - NEventStore.dll + - NEventStore.Serialization.Json.dll + - NEventStore.Serialization.Bson.dll + - NEventStore.Serialization.MsgPack.dll + - NEventStore.Serialization.Binary.dll +artifacts: +- path: '**\artifacts\**\*.*' +deploy: off \ No newline at end of file diff --git a/bin/7zip-bin/7za.exe b/bin/7zip-bin/7za.exe deleted file mode 100644 index 7f6bf86bc..000000000 Binary files a/bin/7zip-bin/7za.exe and /dev/null differ diff --git a/bin/7zip-bin/license.txt b/bin/7zip-bin/license.txt deleted file mode 100644 index 530ff3684..000000000 --- a/bin/7zip-bin/license.txt +++ /dev/null @@ -1,29 +0,0 @@ - 7-Zip Command line version - ~~~~~~~~~~~~~~~~~~~~~~~~~~ - License for use and distribution - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - 7-Zip Copyright (C) 1999-2010 Igor Pavlov. - - 7za.exe is distributed under the GNU LGPL license - - Notes: - You can use 7-Zip on any computer, including a computer in a commercial - organization. You don't need to register or pay for 7-Zip. - - - GNU LGPL information - -------------------- - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You can receive a copy of the GNU Lesser General Public License from - http://www.gnu.org/ diff --git a/bin/ilmerge-bin/ILMerge.exe b/bin/ilmerge-bin/ILMerge.exe deleted file mode 100644 index e36b0c14f..000000000 Binary files a/bin/ilmerge-bin/ILMerge.exe and /dev/null differ diff --git a/build.ps1 b/build.ps1 new file mode 100644 index 000000000..702f56acb --- /dev/null +++ b/build.ps1 @@ -0,0 +1,39 @@ +$configurationdefault = "Release" +$artifacts = "../../artifacts" + +$configuration = Read-Host 'Configuration to build [default: Release] ?' +if ($configuration -eq '') { + $configuration = $configurationdefault +} +$runtests = Read-Host 'Run Tests (y / n) [default:n] ?' + +# Install gitversion tool +dotnet tool restore + +# Display minimal restore information +dotnet restore ./src/NEventStore.Core.sln --verbosity m + +# GitVersion +$str = dotnet tool run dotnet-gitversion /updateAssemblyInfo | out-string +$json = convertFrom-json $str +$nugetversion = $json.SemVer + +# Build +Write-Host "Building: "$nugetversion +dotnet build ./src/NEventStore.Core.sln -c $configuration --no-restore -p:ContinuousIntegrationBuild=True + +# Testing +if ($runtests -eq "y") { + Write-Host "Executing Tests" + dotnet test ./src/NEventStore.Core.sln -c $configuration --no-build + Write-Host "Tests Execution Complated" +} + +# NuGet packages +Write-Host "NuGet Packages creation" +dotnet pack ./src/NEventStore/NEventStore.Core.csproj -c $configuration --no-build -o $artifacts /p:PackageVersion=$nugetversion +dotnet pack ./src/NEventStore.PollingClient/NEventStore.PollingClient.csproj -c $configuration --no-build -o $artifacts /p:PackageVersion=$nugetversion +dotnet pack ./src/NEventStore.Serialization.Json/NEventStore.Serialization.Json.Core.csproj -c $configuration --no-build -o $artifacts /p:PackageVersion=$nugetversion +dotnet pack ./src/NEventStore.Serialization.Bson/NEventStore.Serialization.Bson.Core.csproj -c $configuration --no-build -o $artifacts /p:PackageVersion=$nugetversion +dotnet pack ./src/NEventStore.Serialization.MsgPack/NEventStore.Serialization.MsgPack.Core.csproj -c $configuration --no-build -o $artifacts /p:PackageVersion=$nugetversion +dotnet pack ./src/NEventStore.Serialization.Binary/NEventStore.Serialization.Binary.Core.csproj -c $configuration --no-build -o $artifacts /p:PackageVersion=$nugetversion \ No newline at end of file diff --git a/build/Build.RunTask.ps1 b/build/Build.RunTask.ps1 deleted file mode 100644 index cc886354a..000000000 --- a/build/Build.RunTask.ps1 +++ /dev/null @@ -1,11 +0,0 @@ -Param( - [string]$task, - [string]$version = "0.0.0.0") - -if($task -eq $null) { - $task = read-host "Enter Task" -} - -$scriptPath = $(Split-Path -parent $MyInvocation.MyCommand.path) - -. .\build\psake.ps1 -scriptPath $scriptPath -t $task -properties @{ version=$version } \ No newline at end of file diff --git a/build/Modules/ILMerge.psm1 b/build/Modules/ILMerge.psm1 deleted file mode 100644 index 10509c9f0..000000000 --- a/build/Modules/ILMerge.psm1 +++ /dev/null @@ -1,37 +0,0 @@ -$script:ilMergeModule = @{} -$script:ilMergeModule.ilMergePath = $null - -function Merge-Assemblies { - Param( - $files, - $outputFile, - $exclude, - $keyfile, - $targetPlatform="v4,C:/WINDOWS/Microsoft.NET/Framework/v4.0.30319" - ) - - $exclude | out-file ".\exclude.txt" - - $args = @( - "/keyfile:$keyfile", - "/internalize:exclude.txt", - "/xmldocs", - "/wildcards", - "/targetplatform:$targetPlatform", - "/out:$outputFile") + $files - - if($ilMergeModule.ilMergePath -eq $null) - { - write-error "IlMerge Path is not defined. Please set variable `$ilMergeModule.ilMergePath" - } - - & $ilMergeModule.ilMergePath $args - - if($LastExitCode -ne 0) { - write-error "Merge Failed" - } - - remove-item ".\exclude.txt" -} - -Export-ModuleMember -Variable "ilMergeModule" -Function "Merge-Assemblies" \ No newline at end of file diff --git a/build/Modules/IO.psm1 b/build/Modules/IO.psm1 deleted file mode 100644 index ee554beb0..000000000 --- a/build/Modules/IO.psm1 +++ /dev/null @@ -1,64 +0,0 @@ -function Clean-Item { - Param( - [parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true)] - [string] $path - ) - Process - { - if(($path -ne $null) -and (test-path $path)) - { - write-verbose ("Removing {0}" -f $path) - remove-item -force -recurse $path | Out-Null - } - } -} - -function Remove-Directory { - Param( - [parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true)] - [string] $path - ) - rd $path -recurse -force -ErrorAction SilentlyContinue | out-null -} - -function New-Directory -{ - Param( - [parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true)] - [string] $path - ) - - mkdir $path -ErrorAction SilentlyContinue | out-null -} - -function Copy-Files { - Param( - [parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true)] - [string] $source, - [string] $destination, - [alias("exclude")] - [string[]] $excludeFiles=@(), - [string[]] $excludeDirectories=@() - ) - - New-Directory $destination - - #Get-ChildItem $source -Recurse -Exclude $exclude | Copy-Item -Destination {Join-Path $destination $_.FullName.Substring($source.length)} - - - $arguments = @($source, $destination, "*.*", "/e") - - if(($excludeFiles -ne $null) -and ($excludeFiles.Length -gt 0)) { - $arguments += "/xf" - $arguments += $excludeFiles - } - - if(($excludeDirectories -ne $null) -and ($excludeFiles.Length -gt 0)) { - $arguments += "/xd" - $arguments += $excludeDirectories - } - - robocopy.exe $arguments | out-null - - Expect-ExitCode -expectedExitCode 0,1 -formatMessage { param($taskName) "Copy was not successful" } -} \ No newline at end of file diff --git a/build/default.ps1 b/build/default.ps1 deleted file mode 100644 index 51f99ad8b..000000000 --- a/build/default.ps1 +++ /dev/null @@ -1,149 +0,0 @@ -properties { - $base_directory = Resolve-Path .. - $publish_directory = "$base_directory\publish-net40" - $build_directory = "$base_directory\build" - $src_directory = "$base_directory\src" - $output_directory = "$base_directory\output" - $packages_directory = "$src_directory\packages" - - $sln_file = "$src_directory\EventStore.sln" - $keyfile = "$src_directory/EventStore.snk" - $target_config = "Release" - $framework_version = "v4.0" - $version = "0.0.0.0" - - $mspec_path = "$src_directory\packages\Machine.Specifications.0.5.8\tools\mspec-x86-clr4.exe" - $ilMergeModule.ilMergePath = "$base_directory\bin\ilmerge-bin\ILMerge.exe" - $nuget_dir = "$src_directory\.nuget" - - if($runPersistenceTests -eq $null) { - $runPersistenceTests = $false - } -} - -task default -depends Build - -task Build -depends Clean, UpdateVersion, Compile, Test - -task UpdateVersion { - $versionAssemblyInfoFile = "$src_directory/proj/VersionAssemblyInfo.cs" - "using System.Reflection;" > $versionAssemblyInfoFile - "" >> $versionAssemblyInfoFile - "[assembly: AssemblyVersion(""$version"")]" >> $versionAssemblyInfoFile - "[assembly: AssemblyFileVersion(""$version"")]" >> $versionAssemblyInfoFile -} - -task Compile { - exec { msbuild /nologo /verbosity:quiet $sln_file /p:Configuration=$target_config /t:Clean } - - exec { msbuild /nologo /verbosity:quiet $sln_file /p:Configuration=$target_config /p:TargetFrameworkVersion=v4.0 } -} - -task Test -depends RunUnitTests, RunPersistenceTests, RunSerializationTests - -task RunUnitTests { - write-host "Unit Tests" - - exec { &$mspec_path "$src_directory/tests/EventStore.Core.UnitTests/bin/$target_config/EventStore.Core.UnitTests.dll" } -} - -task RunPersistenceTests -precondition { $runPersistenceTests } { - write-host "Acceptance Tests: Persistence Tests" - - exec { &$mspec_path "$src_directory/tests/EventStore.Persistence.AcceptanceTests/bin/$target_config/EventStore.Persistence.AcceptanceTests.dll" } -} - -task RunSerializationTests { - exec { &$mspec_path "$src_directory\tests\EventStore.Serialization.AcceptanceTests\bin\$target_config\EventStore.Serialization.AcceptanceTests.dll" } -} - -task Package -depends Build, PackageEventStore, PackageMongoPersistence, PackageRavenPersistence, PackageJsonSerialization, PackageServiceStackSerialization, PackageNLogLogging, PackageLog4NetLogging { - move $output_directory $publish_directory -} - -task PackageEventStore -depends Clean, Compile { - mkdir "$output_directory\bin" | out-null - Merge-Assemblies -outputFile "$output_directory\bin\EventStore.dll" -exclude "EventStore.*" -keyfile $keyFile -files @( - "$src_directory\proj\EventStore\bin\$target_config\EventStore.dll", - "$src_directory\proj\EventStore.Core\bin\$target_config\EventStore.Core.dll", - "$src_directory\proj\EventStore.Serialization\bin\$target_config\EventStore.Serialization.dll", - "$src_directory\proj\EventStore.Persistence.SqlPersistence\bin\$target_config\EventStore.Persistence.SqlPersistence.dll", - "$src_directory\proj\EventStore.Wireup\bin\$target_config\EventStore.Wireup.dll" - ) - - write-host Rereferencing Merged Assembly - exec { msbuild /nologo /verbosity:quiet $sln_file /p:Configuration=$target_config /t:Clean } - - exec { msbuild /nologo /verbosity:quiet $sln_file /p:Configuration=$target_config /p:ILMerged=true /p:TargetFrameworkVersion=v4.0 } -} - -task PackageMongoPersistence -depends Clean, Compile,PackageEventStore { - mkdir $output_directory\plugins\persistence\mongo | out-null - - Merge-Assemblies -outputFile "$output_directory/plugins/persistence/mongo/EventStore.Persistence.MongoPersistence.dll" -exclude "MongoDB.*" -keyfile $keyFile -files @( - "$src_directory/proj/EventStore.Persistence.MongoPersistence/bin/$target_config/EventStore.Persistence.MongoPersistence.dll", - "$src_directory/proj/EventStore.Persistence.MongoPersistence.Wireup/bin/$target_config/EventStore.Persistence.MongoPersistence.Wireup.dll" - ) - - copy "$src_directory\proj\EventStore.Persistence.MongoPersistence\bin\$target_config\MongoDB*.dll" "$output_directory\plugins\persistence\mongo" -} - -task PackageRavenPersistence -depends Clean, Compile, PackageEventStore { - mkdir $output_directory\plugins\persistence\raven | out-null - - Merge-Assemblies -outputFile "$output_directory/plugins/persistence/raven/EventStore.Persistence.RavenPersistence.dll" -exclude "Raven.*" -keyfile $keyFile -files @( - "$src_directory/proj/EventStore.Persistence.RavenPersistence/bin/$target_config/EventStore.Persistence.RavenPersistence.dll", - "$src_directory/proj/EventStore.Persistence.RavenPersistence.Wireup/bin/$target_config/EventStore.Persistence.RavenPersistence.Wireup.dll" - ) - - copy "$src_directory\proj\EventStore.Persistence.RavenPersistence\bin\$target_config\Raven*.dll" "$output_directory\plugins\persistence\raven" -} - -task PackageJsonSerialization -depends Clean, Compile, PackageEventStore { - mkdir $output_directory\plugins\serialization\json-net | out-null - - Merge-Assemblies -outputFile "$output_directory/plugins/serialization/json-net/EventStore.Serialization.Json.dll" -exclude "EventStore.*" -keyfile $keyFile -files @( - "$src_directory/proj/EventStore.Serialization.Json/bin/$target_config/EventStore.Serialization.Json.dll", - "$src_directory/proj/EventStore.Serialization.Json/bin/$target_config/Newtonsoft.Json*.dll", - "$src_directory/proj/EventStore.Serialization.Json.Wireup/bin/$target_config/EventStore.Serialization.Json.Wireup.dll" - ) -} - -task PackageServiceStackSerialization -depends Clean, Compile, PackageEventStore { - mkdir $output_directory\plugins\serialization\servicestack | out-null - - Merge-Assemblies -outputFile "$output_directory/plugins/serialization/servicestack/EventStore.Serialization.ServiceStack.dll" -exclude "EventStore.*" -keyfile $keyFile -files @( - "$src_directory/proj/EventStore.Serialization.ServiceStack/bin/$target_config/EventStore.Serialization.ServiceStack.dll", - "$src_directory/proj/EventStore.Serialization.ServiceStack/bin/$target_config/ServiceStack.Text.dll", - "$src_directory/proj/EventStore.Serialization.ServiceStack.Wireup/bin/$target_config/EventStore.Serialization.ServiceStack.Wireup.dll" - ) -} - -task PackageNLogLogging -depends Clean, Compile, PackageEventStore { - mkdir $output_directory\plugins\logging\nlog | out-null - copy "$src_directory\proj\EventStore.Logging.NLog\bin\$target_config\EventStore.Logging.NLog.*" "$output_directory\plugins\logging\nlog" - - copy "$src_directory\proj\EventStore.Logging.NLog\bin\$target_config\NLog.dll" "$output_directory\plugins\logging\nlog" -} - -task PackageLog4NetLogging -depends Clean, Compile, PackageEventStore { - mkdir $output_directory\plugins\logging\log4net | out-null - - copy "$src_directory\proj\EventStore.Logging.Log4Net\bin\$target_config\EventStore.Logging.Log4Net.*" "$output_directory\plugins\logging\log4net" - - copy "$src_directory\proj\EventStore.Logging.Log4Net\bin\$target_config\log4net.dll" "$output_directory\plugins\logging\log4net" -} - -task PackageDocs { - mkdir "$output_directory\doc" - copy "$base_directory\doc\*.*" "$output_directory\doc" -} - -task Clean { - Clean-Item $publish_directory -ea SilentlyContinue - Clean-Item $output_directory -ea SilentlyContinue -} - -task NuGetPack -depends Package { - gci -r -i *.nuspec "$nuget_dir" |% { .$nuget_dir\nuget.exe pack $_ -basepath $base_directory -o $publish_directory -version $version } -} \ No newline at end of file diff --git a/build/psake-config.ps1 b/build/psake-config.ps1 deleted file mode 100644 index dd5044d2b..000000000 --- a/build/psake-config.ps1 +++ /dev/null @@ -1,2 +0,0 @@ -$config.modules=(".\build\modules\*.psm1") -$config.moduleScope="global" \ No newline at end of file diff --git a/build/psake.cmd b/build/psake.cmd deleted file mode 100644 index 58b9371f7..000000000 --- a/build/psake.cmd +++ /dev/null @@ -1,11 +0,0 @@ -@echo off - -if '%1'=='/?' goto help -if '%1'=='-help' goto help -if '%1'=='-h' goto help - -powershell -NoProfile -ExecutionPolicy Bypass -Command "& '%~dp0\psake.ps1' %*; if ($psake.build_success -eq $false) { exit 1 } else { exit 0 }" -goto :eof - -:help -powershell -NoProfile -ExecutionPolicy Bypass -Command "& '%~dp0\psake.ps1' -help" diff --git a/build/psake.ps1 b/build/psake.ps1 deleted file mode 100644 index 4055c03b3..000000000 --- a/build/psake.ps1 +++ /dev/null @@ -1,47 +0,0 @@ -# Helper script for those who want to run psake without importing the module. -# Example: -# .\psake.ps1 "default.ps1" "BuildHelloWord" "4.0" - -# Must match parameter definitions for psake.psm1/invoke-psake -# otherwise named parameter binding fails -param( - [Parameter(Position=0,Mandatory=0)] - [string]$buildFile = 'default.ps1', - [Parameter(Position=1,Mandatory=0)] - [string[]]$taskList = @(), - [Parameter(Position=2,Mandatory=0)] - [string]$framework, - [Parameter(Position=3,Mandatory=0)] - [switch]$docs = $false, - [Parameter(Position=4,Mandatory=0)] - [System.Collections.Hashtable]$parameters = @{}, - [Parameter(Position=5, Mandatory=0)] - [System.Collections.Hashtable]$properties = @{}, - [Parameter(Position=6, Mandatory=0)] - [alias("init")] - [scriptblock]$initialization = {}, - [Parameter(Position=7, Mandatory=0)] - [switch]$nologo = $false, - [Parameter(Position=8, Mandatory=0)] - [switch]$help = $false, - [Parameter(Position=9, Mandatory=0)] - [string]$scriptPath = $(Split-Path -parent $MyInvocation.MyCommand.path) -) - -# '[p]sake' is the same as 'psake' but $Error is not polluted -remove-module [p]sake -import-module (join-path $scriptPath psake.psm1) -if ($help) { - Get-Help Invoke-psake -full - return -} - -if (-not(test-path $buildFile)) { - $absoluteBuildFile = (join-path $scriptPath $buildFile) - if (test-path $absoluteBuildFile) { - $buildFile = $absoluteBuildFile - } -} - -$psake.use_exit_on_error = $true -invoke-psake $buildFile $taskList $framework $docs $parameters $properties $initialization $nologo diff --git a/build/psake.psm1 b/build/psake.psm1 deleted file mode 100644 index fb84cbdce..000000000 --- a/build/psake.psm1 +++ /dev/null @@ -1,709 +0,0 @@ -# psake -# Copyright (c) 2010 James Kovacs -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. - -#Requires -Version 2.0 - -#-- Public Module Functions --# - -# .ExternalHelp psake.psm1-help.xml -function Invoke-Task -{ - [CmdletBinding()] - param( - [Parameter(Position=0,Mandatory=1)] [string]$taskName - ) - - Assert $taskName ($msgs.error_invalid_task_name) - - $taskKey = $taskName.ToLower() - - if ($currentContext.aliases.Contains($taskKey)) { - $taskName = $currentContext.aliases.$taskKey.Name - $taskKey = $taskName.ToLower() - } - - $currentContext = $psake.context.Peek() - - Assert ($currentContext.tasks.Contains($taskKey)) ($msgs.error_task_name_does_not_exist -f $taskName) - - if ($currentContext.executedTasks.Contains($taskKey)) { return } - - Assert (!$currentContext.callStack.Contains($taskKey)) ($msgs.error_circular_reference -f $taskName) - - $currentContext.callStack.Push($taskKey) - - $task = $currentContext.tasks.$taskKey - - $precondition_is_valid = & $task.Precondition - - if (!$precondition_is_valid) { - Write-ColoredOutput ($msgs.precondition_was_false -f $taskName) -foregroundcolor Cyan - } else { - if ($taskKey -ne 'default') { - - if ($task.PreAction -or $task.PostAction) { - Assert ($task.Action -ne $null) ($msgs.error_missing_action_parameter -f $taskName) - } - - if ($task.Action) { - try { - foreach($childTask in $task.DependsOn) { - Invoke-Task $childTask - } - - $stopwatch = [System.Diagnostics.Stopwatch]::StartNew() - $currentContext.currentTaskName = $taskName - - & $currentContext.taskSetupScriptBlock - - if ($task.PreAction) { - & $task.PreAction - } - - if ($currentContext.config.taskNameFormat -is [ScriptBlock]) { - & $currentContext.config.taskNameFormat $taskName - } else { - Write-ColoredOutput ($currentContext.config.taskNameFormat -f $taskName) -foregroundcolor Cyan - } - - foreach ($variable in $task.requiredVariables) { - Assert ((test-path "variable:$variable") -and ((get-variable $variable).Value -ne $null)) ($msgs.required_variable_not_set -f $variable, $taskName) - } - - & $task.Action - - if ($task.PostAction) { - & $task.PostAction - } - - & $currentContext.taskTearDownScriptBlock - $task.Duration = $stopwatch.Elapsed - } catch { - if ($task.ContinueOnError) { - "-"*70 - Write-ColoredOutput ($msgs.continue_on_error -f $taskName,$_) -foregroundcolor Yellow - "-"*70 - $task.Duration = $stopwatch.Elapsed - } else { - throw $_ - } - } - } else { - # no action was specified but we still execute all the dependencies - foreach($childTask in $task.DependsOn) { - Invoke-Task $childTask - } - } - } else { - foreach($childTask in $task.DependsOn) { - Invoke-Task $childTask - } - } - - Assert (& $task.Postcondition) ($msgs.postcondition_failed -f $taskName) - } - - $poppedTaskKey = $currentContext.callStack.Pop() - Assert ($poppedTaskKey -eq $taskKey) ($msgs.error_corrupt_callstack -f $taskKey,$poppedTaskKey) - - $currentContext.executedTasks.Push($taskKey) -} - -# .ExternalHelp psake.psm1-help.xml -function Exec -{ - [CmdletBinding()] - param( - [Parameter(Position=0,Mandatory=1)][scriptblock]$cmd, - [Parameter(Position=1,Mandatory=0)][string]$errorMessage = ($msgs.error_bad_command -f $cmd) - ) - & $cmd - if ($lastexitcode -ne 0) { - throw ("Exec: " + $errorMessage) - } -} - -# .ExternalHelp psake.psm1-help.xml -function Assert -{ - [CmdletBinding()] - param( - [Parameter(Position=0,Mandatory=1)]$conditionToCheck, - [Parameter(Position=1,Mandatory=1)]$failureMessage - ) - if (!$conditionToCheck) { - throw ("Assert: " + $failureMessage) - } -} - -# .ExternalHelp psake.psm1-help.xml -function Task -{ - [CmdletBinding()] - param( - [Parameter(Position=0,Mandatory=1)][string]$name = $null, - [Parameter(Position=1,Mandatory=0)][scriptblock]$action = $null, - [Parameter(Position=2,Mandatory=0)][scriptblock]$preaction = $null, - [Parameter(Position=3,Mandatory=0)][scriptblock]$postaction = $null, - [Parameter(Position=4,Mandatory=0)][scriptblock]$precondition = {$true}, - [Parameter(Position=5,Mandatory=0)][scriptblock]$postcondition = {$true}, - [Parameter(Position=6,Mandatory=0)][switch]$continueOnError = $false, - [Parameter(Position=7,Mandatory=0)][string[]]$depends = @(), - [Parameter(Position=8,Mandatory=0)][string[]]$requiredVariables = @(), - [Parameter(Position=9,Mandatory=0)][string]$description = $null, - [Parameter(Position=10,Mandatory=0)][string]$alias = $null - ) - if ($name -eq 'default') { - Assert (!$action) ($msgs.error_default_task_cannot_have_action) - } - - $newTask = @{ - Name = $name - DependsOn = $depends - PreAction = $preaction - Action = $action - PostAction = $postaction - Precondition = $precondition - Postcondition = $postcondition - ContinueOnError = $continueOnError - Description = $description - Duration = [System.TimeSpan]::Zero - RequiredVariables = $requiredVariables - Alias = $alias - } - - $taskKey = $name.ToLower() - - $currentContext = $psake.context.Peek() - - Assert (!$currentContext.tasks.ContainsKey($taskKey)) ($msgs.error_duplicate_task_name -f $name) - - $currentContext.tasks.$taskKey = $newTask - - if($alias) - { - $aliasKey = $alias.ToLower() - - Assert (!$currentContext.aliases.ContainsKey($aliasKey)) ($msgs.error_duplicate_alias_name -f $alias) - - $currentContext.aliases.$aliasKey = $newTask - } -} - -# .ExternalHelp psake.psm1-help.xml -function Properties { - [CmdletBinding()] - param( - [Parameter(Position=0,Mandatory=1)][scriptblock]$properties - ) - $psake.context.Peek().properties += $properties -} - -# .ExternalHelp psake.psm1-help.xml -function Include { - [CmdletBinding()] - param( - [Parameter(Position=0,Mandatory=1)][string]$fileNamePathToInclude - ) - Assert (test-path $fileNamePathToInclude -pathType Leaf) ($msgs.error_invalid_include_path -f $fileNamePathToInclude) - $psake.context.Peek().includes.Enqueue((Resolve-Path $fileNamePathToInclude)); -} - -# .ExternalHelp psake.psm1-help.xml -function FormatTaskName { - [CmdletBinding()] - param( - [Parameter(Position=0,Mandatory=1)]$format - ) - $psake.context.Peek().config.taskNameFormat = $format -} - -# .ExternalHelp psake.psm1-help.xml -function TaskSetup { - [CmdletBinding()] - param( - [Parameter(Position=0,Mandatory=1)][scriptblock]$setup - ) - $psake.context.Peek().taskSetupScriptBlock = $setup -} - -# .ExternalHelp psake.psm1-help.xml -function TaskTearDown { - [CmdletBinding()] - param( - [Parameter(Position=0,Mandatory=1)][scriptblock]$teardown - ) - $psake.context.Peek().taskTearDownScriptBlock = $teardown -} - -# .ExternalHelp psake.psm1-help.xml -function Framework { - [CmdletBinding()] - param( - [Parameter(Position=0,Mandatory=1)][string]$framework - ) - $psake.context.Peek().config.framework = $framework -} - -# .ExternalHelp psake.psm1-help.xml -function Invoke-psake { - [CmdletBinding()] - param( - [Parameter(Position = 0, Mandatory = 0)][string] $buildFile, - [Parameter(Position = 1, Mandatory = 0)][string[]] $taskList = @(), - [Parameter(Position = 2, Mandatory = 0)][string] $framework, - [Parameter(Position = 3, Mandatory = 0)][switch] $docs = $false, - [Parameter(Position = 4, Mandatory = 0)][hashtable] $parameters = @{}, - [Parameter(Position = 5, Mandatory = 0)][hashtable] $properties = @{}, - [Parameter(Position = 6, Mandatory = 0)][alias("init")][scriptblock] $initialization = {}, - [Parameter(Position = 7, Mandatory = 0)][switch] $nologo = $false - ) - try { - if (-not $nologo) { - "psake version {0}`nCopyright (c) 2010 James Kovacs`n" -f $psake.version - } - - # If the default.ps1 file exists and the given "buildfile" isn 't found assume that the given - # $buildFile is actually the target Tasks to execute in the default.ps1 script. - if ($buildFile -and !(test-path $buildFile -pathType Leaf) -and (test-path $psake.config_default.buildFileName -pathType Leaf)) { - $taskList = $buildFile.Split(', ') - $buildFile = $psake.config_default.buildFileName - } - - # Execute the build file to set up the tasks and defaults - Assert (test-path $buildFile -pathType Leaf) ($msgs.error_build_file_not_found -f $buildFile) - - $psake.build_script_file = get-item $buildFile - $psake.build_script_dir = $psake.build_script_file.DirectoryName - $psake.build_success = $false - - $psake.context.push(@{ - "taskSetupScriptBlock" = {}; - "taskTearDownScriptBlock" = {}; - "executedTasks" = new-object System.Collections.Stack; - "callStack" = new-object System.Collections.Stack; - "originalEnvPath" = $env:path; - "originalDirectory" = get-location; - "originalErrorActionPreference" = $global:ErrorActionPreference; - "tasks" = @{}; - "aliases" = @{}; - "properties" = @(); - "includes" = new-object System.Collections.Queue; - "config" = Create-ConfigurationForNewContext $buildFile $framework - }) - - Load-Configuration $psake.build_script_dir - - Load-Modules - - $stopwatch = [System.Diagnostics.Stopwatch]::StartNew() - - set-location $psake.build_script_dir - - $frameworkOldValue = $framework - . $psake.build_script_file.FullName - - $currentContext = $psake.context.Peek() - - if ($framework -ne $frameworkOldValue) { - write-coloredoutput $msgs.warning_deprecated_framework_variable -foregroundcolor Yellow - $currentContext.config.framework = $framework - } - - if ($docs) { - Write-Documentation - Cleanup-Environment - return - } - - Configure-BuildEnvironment - - while ($currentContext.includes.Count -gt 0) { - $includeFilename = $currentContext.includes.Dequeue() - . $includeFilename - } - - foreach ($key in $parameters.keys) { - if (test-path "variable:\$key") { - set-item -path "variable:\$key" -value $parameters.$key | out-null - } else { - new-item -path "variable:\$key" -value $parameters.$key | out-null - } - } - - # The initial dot (.) indicates that variables initialized/modified in the propertyBlock are available in the parent scope. - foreach ($propertyBlock in $currentContext.properties) { - . $propertyBlock - } - - foreach ($key in $properties.keys) { - if (test-path "variable:\$key") { - set-item -path "variable:\$key" -value $properties.$key | out-null - } - } - - # Simple dot sourcing will not work. We have to force the script block into our - # module's scope in order to initialize variables properly. - . $MyInvocation.MyCommand.Module $initialization - - # Execute the list of tasks or the default task - if ($taskList) { - foreach ($task in $taskList) { - invoke-task $task - } - } elseif ($currentContext.tasks.default) { - invoke-task default - } else { - throw $msgs.error_no_default_task - } - - Write-ColoredOutput ("`n" + $msgs.build_success + "`n") -foregroundcolor Green - - Write-TaskTimeSummary $stopwatch.Elapsed - - $psake.build_success = $true - } catch { - $currentConfig = Get-CurrentConfigurationOrDefault - if ($currentConfig.verboseError) { - $error_message = "{0}: An Error Occurred. See Error Details Below: `n" -f (Get-Date) - $error_message += ("-" * 70) + "`n" - $error_message += Resolve-Error $_ - $error_message += ("-" * 70) + "`n" - $error_message += "Script Variables" + "`n" - $error_message += ("-" * 70) + "`n" - $error_message += get-variable -scope script | format-table | out-string - } else { - # ($_ | Out-String) gets error messages with source information included. - $error_message = "{0}: An Error Occurred: `n{1}" -f (Get-Date), ($_ | Out-String) - } - - $psake.build_success = $false - - if (!$psake.run_by_psake_build_tester) { - # if we are running in a nested scope (i.e. running a psake script from a psake script) then we need to re-throw the exception - # so that the parent script will fail otherwise the parent script will report a successful build - $inNestedScope = ($psake.context.count -gt 1) - if ( $inNestedScope ) { - throw $_ - } else { - Write-ColoredOutput $error_message -foregroundcolor Red - } - - } - } finally { - Cleanup-Environment - } -} - -#-- Private Module Functions --# -function Write-ColoredOutput { - param( - [string] $message, - [System.ConsoleColor] $foregroundcolor - ) - - $currentConfig = Get-CurrentConfigurationOrDefault - if ($currentConfig.coloredOutput -eq $true) { - if (($Host.UI -ne $null) -and ($Host.UI.RawUI -ne $null)) { - $previousColor = $Host.UI.RawUI.ForegroundColor - $Host.UI.RawUI.ForegroundColor = $foregroundcolor - } - } - - $message - - if ($previousColor -ne $null) { - $Host.UI.RawUI.ForegroundColor = $previousColor - } -} - -function Load-Modules { - $currentConfig = $psake.context.peek().config - if ($currentConfig.modules) { - - $scope = $currentConfig.moduleScope - - $global = [string]::Equals($scope, "global", [StringComparison]::CurrentCultureIgnoreCase) - - $currentConfig.modules | foreach { - resolve-path $_ | foreach { - "Loading module: $_" - $module = import-module $_ -passthru -DisableNameChecking -global:$global - if (!$module) { - throw ($msgs.error_loading_module -f $_.Name) - } - } - } - "" - } -} - -function Load-Configuration { - param( - [string] $configdir = $PSScriptRoot - ) - - $psakeConfigFilePath = (join-path $configdir "psake-config.ps1") - - if (test-path $psakeConfigFilePath -pathType Leaf) { - try { - $config = Get-CurrentConfigurationOrDefault - . $psakeConfigFilePath - } catch { - throw "Error Loading Configuration from psake-config.ps1: " + $_ - } - } -} - -function Get-CurrentConfigurationOrDefault() { - if ($psake.context.count -gt 0) { - return $psake.context.peek().config - } else { - return $psake.config_default - } -} - -function Create-ConfigurationForNewContext { - param( - [string] $buildFile, - [string] $framework - ) - - $previousConfig = Get-CurrentConfigurationOrDefault - - $config = new-object psobject -property @{ - buildFileName = $previousConfig.buildFileName; - framework = $previousConfig.framework; - taskNameFormat = $previousConfig.taskNameFormat; - verboseError = $previousConfig.verboseError; - coloredOutput = $previousConfig.coloredOutput; - modules = $previousConfig.modules; - moduleScope = $previousConfig.moduleScope; - } - - if ($framework) { - $config.framework = $framework; - } - - if ($buildFile) { - $config.buildFileName = $buildFile; - } - - return $config -} - -function Configure-BuildEnvironment { - $framework = $psake.context.peek().config.framework - if ($framework.Length -ne 3 -and $framework.Length -ne 6) { - throw ($msgs.error_invalid_framework -f $framework) - } - $versionPart = $framework.Substring(0, 3) - $bitnessPart = $framework.Substring(3) - $versions = $null - switch ($versionPart) { - '1.0' { - $versions = @('v1.0.3705') - } - '1.1' { - $versions = @('v1.1.4322') - } - '2.0' { - $versions = @('v2.0.50727') - } - '3.0' { - $versions = @('v2.0.50727') - } - '3.5' { - $versions = @('v3.5', 'v2.0.50727') - } - '4.0' { - $versions = @('v4.0.30319') - } - default { - throw ($msgs.error_unknown_framework -f $versionPart, $framework) - } - } - - $bitness = 'Framework' - if ($versionPart -ne '1.0' -and $versionPart -ne '1.1') { - switch ($bitnessPart) { - 'x86' { - $bitness = 'Framework' - } - 'x64' { - $bitness = 'Framework64' - } - { [string]::IsNullOrEmpty($_) } { - $ptrSize = [System.IntPtr]::Size - switch ($ptrSize) { - 4 { - $bitness = 'Framework' - } - 8 { - $bitness = 'Framework64' - } - default { - throw ($msgs.error_unknown_pointersize -f $ptrSize) - } - } - } - default { - throw ($msgs.error_unknown_bitnesspart -f $bitnessPart, $framework) - } - } - } - $frameworkDirs = $versions | foreach { "$env:windir\Microsoft.NET\$bitness\$_\" } - - $frameworkDirs | foreach { Assert (test-path $_ -pathType Container) ($msgs.error_no_framework_install_dir_found -f $_)} - - $env:path = ($frameworkDirs -join ";") + ";$env:path" - # if any error occurs in a PS function then "stop" processing immediately - # this does not effect any external programs that return a non-zero exit code - $global:ErrorActionPreference = "Stop" -} - -function Cleanup-Environment { - if ($psake.context.Count -gt 0) { - $currentContext = $psake.context.Peek() - $env:path = $currentContext.originalEnvPath - Set-Location $currentContext.originalDirectory - $global:ErrorActionPreference = $currentContext.originalErrorActionPreference - [void] $psake.context.Pop() - } -} - -# borrowed from Jeffrey Snover http://blogs.msdn.com/powershell/archive/2006/12/07/resolve-error.aspx -function Resolve-Error($ErrorRecord = $Error[0]) { - $error_message = "`nErrorRecord:{0}ErrorRecord.InvocationInfo:{1}Exception:{2}" - $formatted_errorRecord = $ErrorRecord | format-list * -force | out-string - $formatted_invocationInfo = $ErrorRecord.InvocationInfo | format-list * -force | out-string - $formatted_exception = "" - $Exception = $ErrorRecord.Exception - for ($i = 0; $Exception; $i++, ($Exception = $Exception.InnerException)) { - $formatted_exception += ("$i" * 70) + "`n" - $formatted_exception += $Exception | format-list * -force | out-string - $formatted_exception += "`n" - } - - return $error_message -f $formatted_errorRecord, $formatted_invocationInfo, $formatted_exception -} - -function Write-Documentation { - $currentContext = $psake.context.Peek() - - if ($currentContext.tasks.default) { - $defaultTaskDependencies = $currentContext.tasks.default.DependsOn - } else { - $defaultTaskDependencies = @() - } - - $currentContext.tasks.Keys | foreach-object { - if ($_ -eq "default") { - return - } - - $task = $currentContext.tasks.$_ - new-object PSObject -property @{ - Name = $task.Name; - Description = $task.Description; - "Depends On" = $task.DependsOn -join ", " - Default = if ($defaultTaskDependencies -contains $task.Name) { $true } - } - } | sort 'Name' | format-table -autoSize -property Name,Description,"Depends On",Default -} - -function Write-TaskTimeSummary($invokePsakeDuration) { - "-" * 70 - "Build Time Report" - "-" * 70 - $list = @() - $currentContext = $psake.context.Peek() - while ($currentContext.executedTasks.Count -gt 0) { - $taskKey = $currentContext.executedTasks.Pop() - $task = $currentContext.tasks.$taskKey - if ($taskKey -eq "default") { - continue - } - $list += new-object PSObject -property @{ - Name = $task.Name; - Duration = $task.Duration - } - } - [Array]::Reverse($list) - $list += new-object PSObject -property @{ - Name = "Total:"; - Duration = $invokePsakeDuration - } - # using "out-string | where-object" to filter out the blank line that format-table prepends - $list | format-table -autoSize -property Name,Duration | out-string -stream | where-object { $_ } -} - -DATA msgs { -convertfrom-stringdata @' - error_invalid_task_name = Task name should not be null or empty string. - error_task_name_does_not_exist = Task {0} does not exist. - error_circular_reference = Circular reference found for task {0}. - error_missing_action_parameter = Action parameter must be specified when using PreAction or PostAction parameters for task {0}. - error_corrupt_callstack = Call stack was corrupt. Expected {0}, but got {1}. - error_invalid_framework = Invalid .NET Framework version, {0} specified. - error_unknown_framework = Unknown .NET Framework version, {0} specified in {1}. - error_unknown_pointersize = Unknown pointer size ({0}) returned from System.IntPtr. - error_unknown_bitnesspart = Unknown .NET Framework bitness, {0}, specified in {1}. - error_no_framework_install_dir_found = No .NET Framework installation directory found at {0}. - error_bad_command = Error executing command {0}. - error_default_task_cannot_have_action = 'default' task cannot specify an action. - error_duplicate_task_name = Task {0} has already been defined. - error_duplicate_alias_name = Alias {0} has already been defined. - error_invalid_include_path = Unable to include {0}. File not found. - error_build_file_not_found = Could not find the build file {0}. - error_no_default_task = 'default' task required. - error_loading_module = Error loading module {0}. - warning_deprecated_framework_variable = Warning: Using global variable $framework to set .NET framework version used is deprecated. Instead use Framework function or configuration file psake-config.ps1. - required_variable_not_set = Variable {0} must be set to run task {1}. - postcondition_failed = Postcondition failed for task {0}. - precondition_was_false = Precondition was false, not executing task {0}. - continue_on_error = Error in task {0}. {1} - build_success = Build Succeeded! -'@ -} - -import-localizeddata -bindingvariable msgs -erroraction silentlycontinue - -$script:psake = @{} -$psake.version = "4.1.0" # contains the current version of psake -$psake.context = new-object system.collections.stack # holds onto the current state of all variables -$psake.run_by_psake_build_tester = $false # indicates that build is being run by psake-BuildTester -$psake.config_default = new-object psobject -property @{ - buildFileName = "default.ps1"; - framework = "4.0"; - taskNameFormat = "Executing {0}"; - verboseError = $false; - coloredOutput = $true; - modules = $null; - moduleScope = "local"; -} # contains default configuration, can be overriden in psake-config.ps1 in directory with psake.psm1 or in directory with current build script - -$psake.build_success = $false # indicates that the current build was successful -$psake.build_script_file = $null # contains a System.IO.FileInfo for the current build script -$psake.build_script_dir = "" # contains a string with fully-qualified path to current build script - -Load-Configuration - -export-modulemember -function invoke-psake, invoke-task, task, properties, include, formattaskname, tasksetup, taskteardown, framework, assert, exec -variable psake diff --git a/doc/EventStore - Architectural Overview.docx b/doc/EventStore - Architectural Overview.docx deleted file mode 100644 index 1904d181a..000000000 Binary files a/doc/EventStore - Architectural Overview.docx and /dev/null differ diff --git a/doc/EventStore - Transactional Integrity.docx b/doc/EventStore - Transactional Integrity.docx deleted file mode 100644 index 9a7f80b29..000000000 Binary files a/doc/EventStore - Transactional Integrity.docx and /dev/null differ diff --git a/doc/EventStore.Example/AggregateMemento.cs b/doc/EventStore.Example/AggregateMemento.cs deleted file mode 100644 index 8d7997675..000000000 --- a/doc/EventStore.Example/AggregateMemento.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace EventStore.Example -{ - internal class AggregateMemento - { - public string Value { get; set; } - - public override string ToString() - { - return this.Value; - } - } -} \ No newline at end of file diff --git a/doc/EventStore.Example/App.config b/doc/EventStore.Example/App.config deleted file mode 100644 index 9757d19c5..000000000 --- a/doc/EventStore.Example/App.config +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/doc/EventStore.Example/AuthorizationPipelineHook.cs b/doc/EventStore.Example/AuthorizationPipelineHook.cs deleted file mode 100644 index 581743025..000000000 --- a/doc/EventStore.Example/AuthorizationPipelineHook.cs +++ /dev/null @@ -1,32 +0,0 @@ -namespace EventStore.Example -{ - using System; - - public class AuthorizationPipelineHook : IPipelineHook - { - public void Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); - } - protected virtual void Dispose(bool disposing) - { - // no op - } - - public Commit Select(Commit committed) - { - // return null if the user isn't authorized to see this commit - return committed; - } - public bool PreCommit(Commit attempt) - { - // Can easily do logging or other such activities here - return true; // true == allow commit to continue, false = stop. - } - public void PostCommit(Commit committed) - { - // anything to do after the commit has been persisted. - } - } -} \ No newline at end of file diff --git a/doc/EventStore.Example/EventStore.Example.csproj b/doc/EventStore.Example/EventStore.Example.csproj deleted file mode 100644 index c4c3ccab3..000000000 --- a/doc/EventStore.Example/EventStore.Example.csproj +++ /dev/null @@ -1,115 +0,0 @@ - - - - Debug - AnyCPU - 8.0.30703 - 2.0 - {0143B48E-25AF-4CE0-BD49-A52267D359D3} - Exe - Properties - EventStore.Example - EventStore.Example - v4.0 - 512 - true - ..\..\src\EventStore.snk - ..\..\src\ - true - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - Properties\GlobalAssemblyInfo.cs - - - Properties\GlobalSuppressions.cs - - - Properties\VersionAssemblyInfo.cs - - - - - - - True - True - Resources.resx - - - - - - ..\..\output\bin\EventStore.dll - - - {03946843-F343-419C-88EF-3E446D08DFA6} - EventStore - - - {D6413244-42F5-4233-B347-D0A804B09CC9} - EventStore.Core - - - {DAFD3F38-33F3-4F53-BFEA-44E51BD1E8F1} - EventStore.Persistence.SqlPersistence - - - {DEFFE0C3-2988-4C58-9E36-1302842FFDBD} - EventStore.Serialization.Json.Wireup - - - {CFD895BD-7CB2-4811-A6FA-1851DF769B67} - EventStore.Serialization.Json - - - {A5BF4B86-26F6-418D-BE35-C6CC3A623D27} - EventStore.Serialization - - - {421664DB-C18D-4499-ABC1-C9086D525F80} - EventStore.Wireup - - - - - - - - - ResXFileCodeGenerator - Resources.Designer.cs - Designer - - - - - Properties\CustomDictionary.xml - - - - - - - - \ No newline at end of file diff --git a/doc/EventStore.Example/MainProgram.cs b/doc/EventStore.Example/MainProgram.cs deleted file mode 100644 index ab985b975..000000000 --- a/doc/EventStore.Example/MainProgram.cs +++ /dev/null @@ -1,109 +0,0 @@ -using EventStore.Persistence.SqlPersistence.SqlDialects; - -namespace EventStore.Example -{ - using System; - using System.Transactions; - using Dispatcher; - - internal static class MainProgram - { - private static readonly Guid StreamId = Guid.NewGuid(); // aggregate identifier - private static readonly byte[] EncryptionKey = new byte[] - { - 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf - }; - private static IStoreEvents store; - - private static void Main() - { - using (var scope = new TransactionScope()) - using (store = WireupEventStore()) - { - OpenOrCreateStream(); - AppendToStream(); - TakeSnapshot(); - LoadFromSnapshotForwardAndAppend(); - scope.Complete(); - } - - Console.WriteLine(Resources.PressAnyKey); - Console.ReadKey(); - } - - private static IStoreEvents WireupEventStore() - { - return Wireup.Init() - .LogToOutputWindow() - .UsingInMemoryPersistence() - .UsingSqlPersistence("EventStore") // Connection string is in app.config - .WithDialect(new MsSqlDialect()) - .EnlistInAmbientTransaction() // two-phase commit - .InitializeStorageEngine() - .TrackPerformanceInstance("example") - .UsingJsonSerialization() - .Compress() - .EncryptWith(EncryptionKey) - .HookIntoPipelineUsing(new[] { new AuthorizationPipelineHook() }) - .UsingSynchronousDispatchScheduler() - .DispatchTo(new DelegateMessageDispatcher(DispatchCommit)) - .Build(); - } - private static void DispatchCommit(Commit commit) - { - // This is where we'd hook into our messaging infrastructure, such as NServiceBus, - // MassTransit, WCF, or some other communications infrastructure. - // This can be a class as well--just implement IDispatchCommits. - try - { - foreach (var @event in commit.Events) - Console.WriteLine(Resources.MessagesDispatched + ((SomeDomainEvent)@event.Body).Value); - } - catch (Exception) - { - Console.WriteLine(Resources.UnableToDispatch); - } - } - - private static void OpenOrCreateStream() - { - // we can call CreateStream(StreamId) if we know there isn't going to be any data. - // or we can call OpenStream(StreamId, 0, int.MaxValue) to read all commits, - // if no commits exist then it creates a new stream for us. - using (var stream = store.OpenStream(StreamId, 0, int.MaxValue)) - { - var @event = new SomeDomainEvent { Value = "Initial event." }; - - stream.Add(new EventMessage { Body = @event }); - stream.CommitChanges(Guid.NewGuid()); - } - } - private static void AppendToStream() - { - using (var stream = store.OpenStream(StreamId, int.MinValue, int.MaxValue)) - { - var @event = new SomeDomainEvent { Value = "Second event." }; - - stream.Add(new EventMessage { Body = @event }); - stream.CommitChanges(Guid.NewGuid()); - } - } - private static void TakeSnapshot() - { - var memento = new AggregateMemento { Value = "snapshot" }; - store.Advanced.AddSnapshot(new Snapshot(StreamId, 2, memento)); - } - private static void LoadFromSnapshotForwardAndAppend() - { - var latestSnapshot = store.Advanced.GetSnapshot(StreamId, int.MaxValue); - - using (var stream = store.OpenStream(latestSnapshot, int.MaxValue)) - { - var @event = new SomeDomainEvent { Value = "Third event (first one after a snapshot)." }; - - stream.Add(new EventMessage { Body = @event }); - stream.CommitChanges(Guid.NewGuid()); - } - } - } -} \ No newline at end of file diff --git a/doc/EventStore.Example/Settings.StyleCop b/doc/EventStore.Example/Settings.StyleCop deleted file mode 100644 index 7587c6958..000000000 --- a/doc/EventStore.Example/Settings.StyleCop +++ /dev/null @@ -1,184 +0,0 @@ - - - - - - - False - - - - - False - - - - - False - - - - - - - - - - False - - - - - False - - - - - False - - - - - - - - - - False - - - - - False - - - - - - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - - - - - - False - - - - - False - - - - - False - - - - - False - - - - - - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - - - - - - False - - - - - False - - - - - False - - - - - False - - - - - - - \ No newline at end of file diff --git a/doc/EventStore.Example/SomeDomainEvent.cs b/doc/EventStore.Example/SomeDomainEvent.cs deleted file mode 100644 index d0defd8de..000000000 --- a/doc/EventStore.Example/SomeDomainEvent.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace EventStore.Example -{ - internal class SomeDomainEvent - { - public string Value { get; set; } - } -} \ No newline at end of file diff --git a/doc/EventStore.FileSystemPersistenceEngine.txt b/doc/EventStore.FileSystemPersistenceEngine.txt deleted file mode 100644 index c4fc1a090..000000000 --- a/doc/EventStore.FileSystemPersistenceEngine.txt +++ /dev/null @@ -1,33 +0,0 @@ -ID 16 -Revision 4 -Position 4 - -24 bytes per index entry = 43,690 entries per MB of index - -ID 16 -entry + position 8 -entry + position 8 -entry + position 8 -entry + position 8 -entry + position 8 - -2K = 256 entries -256 * 8 - 16 -2032 = 2048 - -16 byte header (for ID) - -ID[16] + Entries[126 * 8 bytes] = 1024 bytes - -(KB - 16) / 8 = # of entries per "index" - - -100 MB = 4,369,066 entries (and just as many aggregates) -100 MB = 13,107,198 entries (but a limited # of aggregates) @ 1K block size filled to capacity = 3x increase in # of commits - -easiest and fastest = ID/revision/position - -ID 16 -Rev 4 -Pos 4 - 1 bit -Disp 1 bit \ No newline at end of file diff --git a/doc/FileSystemPersistence Record Layout.txt b/doc/FileSystemPersistence Record Layout.txt deleted file mode 100644 index ca29fe9cb..000000000 --- a/doc/FileSystemPersistence Record Layout.txt +++ /dev/null @@ -1,21 +0,0 @@ -Index -Stream id 16 -# of events 2 -Size of commit 4 (- 1 bit) -Dispatched 1 bit ---------------- 22 - -Commit header -Stream id 16 -# of events 2 (commit sequence is derived) -commit id 16 -commit stamp dif4 -Length of body 4 -Checksum 16 (*including* header)--MD5 ---------------- 58 - -Commit body ? - - - -Engine should track "head" for each stream and head of file stream... diff --git a/doc/acknowledgments.txt b/doc/acknowledgments.txt deleted file mode 100644 index 55b9a87e4..000000000 --- a/doc/acknowledgments.txt +++ /dev/null @@ -1,13 +0,0 @@ -Jonathan Oliver @jonathan_oliver https://github.com/joliver http://blog.jonathanoliver.com -Greg Young @gregyoung https://github.com/gregoryyoung http://codebetter.com/gregyoung -Andreas Ohlund @andreas_ohlund https://github.com/andreasohlund http://andreasohlund.net -Jonathan Matheus @kblooie https://github.com/kblooie -Aaron Navarro @aaron_navarro https://github.com/anavarro9731 -Simon Green https://github.com/CaptainCodeman http://www.captaincodeman.com -Chris Nicola @lucisferre https://github.com/lucisferre http://www.lucisferre.net -Pieter Joost @pjvds https://github.com/pjvds http://ncqrs.org -Daniel Hoelbling @tigraine https://github.com/Tigraine http://www.tigraine.at -Damian Hickey @randompunter https://github.com/damianh http://dhickey.ie/ -John Downey @jtdowney https://github.com/jtdowney http://jtdowney.com -Jimit Ndiaye @jimitndiaye https://github.com/jimitndiaye -Mikael Östberg @MikaelOstberg https://github.com/MikeEast http://www.inloop.se/ \ No newline at end of file diff --git a/doc/roadmap.txt b/doc/roadmap.txt deleted file mode 100644 index 502376c56..000000000 --- a/doc/roadmap.txt +++ /dev/null @@ -1,37 +0,0 @@ -3.1 - - Add additional documentation using GitHub wikis and more complete quickstart examples - - .NET 4.0 only? - - Merge CommonDomain - - Amazon S3/Tables persistence - - CouchDB persistence - - Sybase ASE persistence - - Sybase SQL Anywhere persistence - - Potentially split "Commits" table for SQL, e.g. Commits and Dispatch. The Dispatch would hold the commit until it - was dispatched and then it would be moved into the Commits table along with the Streams being updated. We could - use uNION ALL to make it feel like it's a single table for the EventStore on top. This would eliminate the need - for complex joins and it would make updating the stream heads async as well. The only "loss" is the double write - that would occur when moving the data between commits and dispatch. This could also potentially eliminate the need - for the 'Dispatched' bit column. Migration to the new schema wouldn't be particularly difficult and would require - a small script. - - Polling Dispatch Scheduler which polls the event store at the specified frequency and dispatches and new - commits which have been added. - - Multi-format Deserializer which attempts a number of internal deserializers (JSON, BSON, PB, Binary, XML, etc) - before giving up. This would allow people to change their serialization in production if necessary - for things like encryption, etc. For example, we're changing the encryption on all commits and we need to - support several keys simultaenously for a period of time... - -3.2: - - FileSystem persistence - - Redis - - Azure Tables persistence - - Oracle persistence - - ProtocolBuffers serializer - - Official website - -4.0: - - Master/slave replication "helper" - * When failing over from master to slave, ensure that the most recent commits are in the slave. - * If not, push the commits to the slave to help avoid a split brain condition. - - Create a PersistenceEngine implementation that can be exposed as an endpoint, e.g. REST or HTTP, etc. such that - the OptimisticEventStore could use that endpoint as the underlying storage. This would create a logical layering - point which could then be separated into physical layers more easily. \ No newline at end of file diff --git a/docs/Testing.md b/docs/Testing.md new file mode 100644 index 000000000..1613ea91c --- /dev/null +++ b/docs/Testing.md @@ -0,0 +1,23 @@ +# Testing And Test Frameworks in NEventStore + +While upgrading the solution to support dotnet core, we also tried to migrate the tests to other test frameworks +(because not all of them supported dotnet core correctly when the migration job started). + +Several trial and errors were made, but in the end we were able to implement the tests using all the 3 major testing frameworks available in the dotnet world: + +- XUnit +- NUnit +- MSTest + +We had to write 3 version of the `SpecificationBase` class and adapt the testing attributes to each framework. + +I you inspect the code you'll see a lot of `#if NUNIT` (and the like) lines of code. + +The actual implementation compiles all the projects to use NUnit. + +You can change the behavior following these steps: + +- go through all the .csproj files and change the compilation constant from NUNIT to XUNIT or MSTEST. +- in the assemblies that contain tests you need to reference the correct Test Framework assemblies and TestAdapter for the framework you are going to use. + +Having more than one test runner might not be a good idea because some CI tools (like Appveyor) might autodetect them and execute the tests for a framework you are not using, and it will surely endup with failures and errors of your build. \ No newline at end of file diff --git a/icon.png b/icon.png new file mode 100644 index 000000000..c6c977dab Binary files /dev/null and b/icon.png differ diff --git a/doc/license.txt b/license.txt similarity index 76% rename from doc/license.txt rename to license.txt index 4393ddaca..6a5550266 100644 --- a/doc/license.txt +++ b/license.txt @@ -1,25 +1,21 @@ -Copyright (c) 2011-2012 Jonathan Oliver - -MIT License - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - -NOTE: Various optional and subordinate components carry their own licensing -requirements and restrictions. Use of those components is subject to the terms -and conditions outlined the respective license of each component. \ No newline at end of file +The MIT License + +Copyright (c) 2013 Jonathan Oliver, Jonathan Matheus, Damian Hickey and contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/readme.markdown b/readme.markdown deleted file mode 100644 index 9d9701eb7..000000000 --- a/readme.markdown +++ /dev/null @@ -1,191 +0,0 @@ -EventStore -====================================================================== - - - The most recent stable release is avaiable on [NuGet.org](https://nuget.org/packages/EventStore) - - CI builds can be viewed on [Codebetter's TeamCity server](http://teamcity.codebetter.com/project.html?projectId=project247&tab=projectOverview). - - CI package are availble on [myget.org](http://www.myget.org/gallery/eventstore). - - CI builds and packages are to be considered unstable. - -## Overview -The EventStore is a persistence library used to abstract different storage implementations -when using event sourcing as storage mechanism. Event sourcing is most closely associated -with a concept known as [CQRS](http://cqrsinfo.com). - -### Need Help? Have a Question? -Ask your question on [Stack Overflow](http://stackoverflow.com/search?q=[cqrs]+eventstore) and tag your question with -the CQRS tag and the word "EventStore" in the title. - -### Purpose and Theory -The purpose of the EventStore is to represent a series of events as a stream. Furthermore, -it provides hooks whereby any events committed to the stream can be dispatched to interested -parties. - -Guided by a number strategic design decisions based upon the needs of applications using event sourcing, -the EventStore is able to liberate applications from the stringent requirements often imposed by -infrastructure components. Specifically, most CQRS-style applications read from a message queue -and perform some processing. When processing is complete, the application then commits the work -to storage and publishes the completed work. In almost all cases, this requires a two-phase commit -managed by a distributed transaction coordinator (MSDTC in .NET) along with various security settings -and firewall ports opened and avaiable whereby such components can communicate, not to mention a -ubiquitous requirement for Microsoft Windows on all machines in .NET environments. - -When using two-phase commit in .NET, there are very few database drivers that support this scenario -and even fewer message queues that support it. In essence, if you want to implement a typical -CQRS-style application, you're stuck with MSMQ and SQL Server using MSDTC. Granted, there are -other choices, but the constraints imposed by a two-phase commit are burdensome. This also -creates additional issues when utilizing shared hosting or running on Mono as support in frameworks -and drivers is either poor, buggy, or unavailable. - -The EventStore liberates application developers from this level of infrastructure awareness and -concern by committing all work within a separate isolated atomic unit--all without using transactions. -Furthermore, it does this outside of any ambient transaction from a message queue or other -persistence mechanisms. In other words, application developers are free to use virtually any -messaging queuing infrastructure, message bus (if at all), and storage engine. Each will perform -its own specific task in an isolated manner with full transactional integrity all without -enlisting any resources (other than a message queue) in some form of transaction. - -Interestingly enough, even without the presence of distributed transactions across the various resources -involved, such as a message queue and persistent storage, the EventStore is able to ensure a fully -transactional experience. This is achieved by breaking apart a distributed transaction into smaller -pieces and performing each one individually. This is one of the primary goals and motivations in the -underlying model found in the EventStore. Thus each message delivered by the queuing infrastructure is -made to be idempotent, even though the message may be delivered multiple times, as per message queue -"at-least-once" guarantees. Following this, the EventStore is able to ensure that all events committed -are always dispatched to any messaging infrastructure. - -**New in v3.0:** I knew you guys couldn't live without it, so for those storage engines and message systems -which support and participate two-phase commits, there now exists the ability to specify a -TransactionScopeOption of 'Required'. Simply indicate this using the following wireup overload: - - var store = Wireup.Init() - .UsingSqlPersistence("connection-name-here") // also works with UsingRavenPersistence() - .EnlistInAmbientTransaction() - -## Supported Storage Engines - -### Relational Databases -[Complete] Microsoft SQL Server 2005 (or later) -[Complete] MySQL 5.0 (or later) - -* [Complete] InnoDB -* [Complete] NDB/MySQL Cluster -* [Complete] Drizzle -* [Complete] MariaDB -* [Complete] XtraDB -* [Complete] PBXT -* [Complete] Xeround -* [Complete] Galera -* [Complete] Percona -* [Complete] OurDelta -* [Untested] MyISAM -* [Untested] BerkleyDB - -[Complete] PostgreSQL 8.0 (or later) -[Complete] Firebird 2.0 (or later) -[In progress] Sybase (ASE) -[In progress] Sybase (SQL Anywhere) -[Planned] Oracle 8.0 (or later) -[TBA] IBM DB2 -[TBA] Informix - -### Embedded Relational Databases -[Complete] SQLite 3.0 (or later) -[Complete] Microsoft SQL Server Compact Edition 3.5 (or later) -[Complete] Microsoft Access 2000 (or later) - -### Cloud-based Databases (relational or otherwise) -[Complete] Microsoft SQL Azure -[Complete] Amazon RDS (MySQL) -[Planned] Amazon RDS (Oracle) -[In progress] Azure Tables/Blobs -[In progress] Amazon SimpleDB/S3 - -### Document Databases -[Complete] RavenDB r322 (or later) -[Complete] MongoDB 1.6 (or later) -[Planned] CouchDB 1.0 (or later) -[TBA] OrientDB - -### File System -[Planned] .NET Managed System.IO APIs - -### Dynamo Clones -[Planned] Cassandra -[Planned] Riak -[TBA] Voldemort -[TBA] Dynomite - -### KV Stores / NoSQL -[Planned] Redis -[Planned] Memcached (Membase, Gear6, etc.) -[Planned] HBase -[TBA] HyperTable -[TBA] Tokyo Cabinet -[TBA] Microsoft Velocity -[TBA] SharedCache -[TBA] Hibari -[TBA] Scalaris -[TBA] Keyspace -[TBA] OrientKV -[TBA] VoltDB -[TBA] BerkleyDB -[TBA] Hazelcast -[TBA] HampsterDB - -## Project Goals -* Mono 2.4 support -* Medium-trust support -* Support more storage engines than any other event storage implementation -* Easily support virtually any storage engine (NoSQL, etc.) -* Avoid dependence upon TransactionScope or Transactions while maintaining full data integrity -* Full test coverage of storage implementations -* Easily hook into any bus implementation (NServiceBus, MassTransit, etc.) -* Synchronous and asynchronous dispatching of events -* Extreme performance -* Multi-thread safe -* Fluent builder - -## Building -Simply run **build.cmd** from the command line. Once built, the files will be placed in the "publish-net40" subdirectory. - -## Using the EventStore - - var store = Wireup.Init() - .UsingSqlPersistence("Name Of EventStore ConnectionString In Config File") - .InitializeStorageEngine() - .UsingJsonSerialization() - .Compress() - .EncryptWith(EncryptionKey) - .HookIntoPipelineUsing(new[] { new AuthorizationPipelineHook() }) - .UsingAsynchronousDispatchScheduler() - // Example of NServiceBus dispatcher: https://gist.github.com/1311195 - .DispatchTo(new My_NServiceBus_Or_MassTransit_OrEven_WCF_Adapter_Code()) - .Build(); - - /* NOTE: This following is merely *example* code. */ - - using (store) - { - // some business code here - using (var stream = store.CreateStream(myMessage.CustomerId)) - { - stream.Add(new EventMessage { Body = myMessage }); - stream.CommitChanges(myMessage.MessageId); - } - - using (var stream = store.OpenStream(myMessage.CustomerId, 0, int.MaxValue)) - { - foreach (var @event in stream.CommittedEvents) - { - // business processing... - } - } - } - -For a more complete example, please see [EventStore.Example](https://github.com/joliver/EventStore/blob/master/doc/EventStore.Example/MainProgram.cs) project in the doc subdirectory. - -## Running the Example -The EventStore.Example project is configured by default to use a SQL event store. To run the example -program, either change the SQL connection string in the app.config file to connect to a existing SQL database -or change WireupEventStore() to call UsingInMemoryPersistence() rather than UsingSqlPersistence(). diff --git a/src/.editorconfig b/src/.editorconfig new file mode 100644 index 000000000..3c456a6e5 --- /dev/null +++ b/src/.editorconfig @@ -0,0 +1,30 @@ +[*] +end_of_line = crlf +indent_style = space +indent_size = 4 + +[*.xml] +indent_style = space + +[*.cs] +csharp_new_line_before_open_brace = all + +# RCS1229: Use async/await when necessary. +dotnet_diagnostic.RCS1229.severity = error + +# IDE0290: Use primary constructor +csharp_style_prefer_primary_constructors = false + +# IDE0028: Simplify collection initialization +dotnet_style_collection_initializer = false + +# IDE0305: Simplify collection initialization +dotnet_diagnostic.IDE0305.severity = none + +# Logging Warnings: temporary disabled, enable them again later on + +# CA1848: Use the LoggerMessage delegates +dotnet_diagnostic.CA1848.severity = none + +# CA2254: Template should be a static expression +dotnet_diagnostic.CA2254.severity = none \ No newline at end of file diff --git a/src/.nuget/EventStore.Logging.Log4Net.nuspec b/src/.nuget/EventStore.Logging.Log4Net.nuspec deleted file mode 100644 index bcec9f52e..000000000 --- a/src/.nuget/EventStore.Logging.Log4Net.nuspec +++ /dev/null @@ -1,24 +0,0 @@ - - - - EventStore.Logging.Log4Net - 3.1 - EventStore Log4Net Logging Plugin - Jonathan Oliver - Jonathan Oliver - https://github.com/joliver/EventStore - false - Additional logging provider for EventStore using Log4Net. - Log4Net logging provider for EventStore. - es-US - events, event sourcing, cqrs, storage, persistence, database, logging, log4net - - - - - - - - - - \ No newline at end of file diff --git a/src/.nuget/EventStore.Logging.NLog.nuspec b/src/.nuget/EventStore.Logging.NLog.nuspec deleted file mode 100644 index e69d8c227..000000000 --- a/src/.nuget/EventStore.Logging.NLog.nuspec +++ /dev/null @@ -1,24 +0,0 @@ - - - - EventStore.Logging.NLog - 3.1 - EventStore NLog Logging Plugin - Jonathan Oliver - Jonathan Oliver - https://github.com/joliver/EventStore - false - Additional logging provider for EventStore using NLog. - NLog logging provider for EventStore. - es-US - events, event sourcing, cqrs, storage, persistence, database, logging, nlog - - - - - - - - - - \ No newline at end of file diff --git a/src/.nuget/EventStore.Persistence.MongoPersistence.nuspec b/src/.nuget/EventStore.Persistence.MongoPersistence.nuspec deleted file mode 100644 index 2da112825..000000000 --- a/src/.nuget/EventStore.Persistence.MongoPersistence.nuspec +++ /dev/null @@ -1,24 +0,0 @@ - - - - EventStore.Persistence.MongoPersistence - 3.1 - EventStore MongoDB Persistence Plugin - Simon Green, Jonathan Oliver - Jonathan Oliver - https://github.com/joliver/EventStore - false - Additional persistence provider for EventStore using MongoDB. - MongoDB persistence provider for EventStore. - es-US - events, event sourcing, cqrs, storage, persistence, database, mongodb - - - - - - - - - - \ No newline at end of file diff --git a/src/.nuget/EventStore.Persistence.RavenPersistence.nuspec b/src/.nuget/EventStore.Persistence.RavenPersistence.nuspec deleted file mode 100644 index d723e802a..000000000 --- a/src/.nuget/EventStore.Persistence.RavenPersistence.nuspec +++ /dev/null @@ -1,24 +0,0 @@ - - - - EventStore.Persistence.RavenPersistence - 3.1 - EventStore RavenDB Persistence Plugin - Simon Green, Jonathan Oliver - Jonathan Oliver - https://github.com/joliver/EventStore - false - Additional persistence provider for EventStore using RavenDB. - RavenDB persistence provider for EventStore. - es-US - events, event sourcing, cqrs, storage, persistence, database, ravendb - - - - - - - - - - \ No newline at end of file diff --git a/src/.nuget/EventStore.Serialization.Json.nuspec b/src/.nuget/EventStore.Serialization.Json.nuspec deleted file mode 100644 index babf77536..000000000 --- a/src/.nuget/EventStore.Serialization.Json.nuspec +++ /dev/null @@ -1,23 +0,0 @@ - - - - EventStore.Serialization.Json - 3.1 - EventStore Newtonsoft JSON Serialization Plugin - Simon Green, Jonathan Oliver - Jonathan Oliver - https://github.com/joliver/EventStore - false - Additional serialization provider for EventStore based on the Newtonsoft JSON.NET library. - Newtonsoft JSON.NET Serializer for EventStore. - es-US - events, event sourcing, cqrs, storage, persistence, database, newtonsoft, json, serialization - - - - - - - - - \ No newline at end of file diff --git a/src/.nuget/EventStore.Serialization.ServiceStack.nuspec b/src/.nuget/EventStore.Serialization.ServiceStack.nuspec deleted file mode 100644 index c70c7d2b1..000000000 --- a/src/.nuget/EventStore.Serialization.ServiceStack.nuspec +++ /dev/null @@ -1,23 +0,0 @@ - - - - EventStore.Serialization.ServiceStack - 3.1 - EventStore ServiceStack JSON Serialization Plugin - Simon Green, Jonathan Oliver - Jonathan Oliver - https://github.com/joliver/EventStore - false - Additional serialization provider for EventStore based on ServiceStack JSON library. - ServiceStack JSON Serializer for EventStore. - es-US - events, event sourcing, cqrs, storage, persistence, database, servicestack, json, serialization - - - - - - - - - \ No newline at end of file diff --git a/src/.nuget/EventStore.nuspec b/src/.nuget/EventStore.nuspec deleted file mode 100644 index 22da0878e..000000000 --- a/src/.nuget/EventStore.nuspec +++ /dev/null @@ -1,19 +0,0 @@ - - - - EventStore - 3.1 - EventStore - Simon Green, Jonathan Oliver - Jonathan Oliver - https://github.com/joliver/EventStore - false - The purpose of the EventStore is to represent a series of events as a stream. Furthermore, it provides hooks whereby any events committed to the stream can be dispatched to interested parties. - The EventStore is a persistence library used to abstract different storage implementations when using event sourcing as storage mechanism. The primary use is most often associated with CQRS. - es-US - events, event sourcing, cqrs, storage, persistence, database - - - - - \ No newline at end of file diff --git a/src/.nuget/NuGet.Config b/src/.nuget/NuGet.Config deleted file mode 100644 index 6a318ad9b..000000000 --- a/src/.nuget/NuGet.Config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/src/.nuget/NuGet.exe b/src/.nuget/NuGet.exe deleted file mode 100644 index f4fe4abdf..000000000 Binary files a/src/.nuget/NuGet.exe and /dev/null differ diff --git a/src/.nuget/NuGet.targets b/src/.nuget/NuGet.targets deleted file mode 100644 index 67c1fb1e3..000000000 --- a/src/.nuget/NuGet.targets +++ /dev/null @@ -1,143 +0,0 @@ - - - - $(MSBuildProjectDirectory)\..\ - - - false - - - false - - - true - - - false - - - - - - - - - - - $([System.IO.Path]::Combine($(SolutionDir), ".nuget")) - $([System.IO.Path]::Combine($(ProjectDir), "packages.config")) - $([System.IO.Path]::Combine($(SolutionDir), "packages")) - - - - - $(SolutionDir).nuget - packages.config - $(SolutionDir)packages - - - - - $(NuGetToolsPath)\nuget.exe - @(PackageSource) - - "$(NuGetExePath)" - mono --runtime=v4.0.30319 $(NuGetExePath) - - $(TargetDir.Trim('\\')) - - -RequireConsent - - $(NuGetCommand) install "$(PackagesConfig)" -source "$(PackageSources)" $(RequireConsentSwitch) -o "$(PackagesDir)" - $(NuGetCommand) pack "$(ProjectPath)" -p Configuration=$(Configuration) -o "$(PackageOutputDir)" -symbols - - - - RestorePackages; - $(BuildDependsOn); - - - - - $(BuildDependsOn); - BuildPackage; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/AssemblyInfo.cs b/src/AssemblyInfo.cs new file mode 100644 index 000000000..a2d615453 --- /dev/null +++ b/src/AssemblyInfo.cs @@ -0,0 +1,6 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +[assembly: AssemblyVersion("0.0.0.0")] +[assembly: AssemblyFileVersion("0.0.0.0")] +[assembly: AssemblyInformationalVersion("0.0.0.0")] diff --git a/src/proj/CustomDictionary.xml b/src/CustomDictionary.xml similarity index 92% rename from src/proj/CustomDictionary.xml rename to src/CustomDictionary.xml index 1a27f5179..a13c808b0 100644 --- a/src/proj/CustomDictionary.xml +++ b/src/CustomDictionary.xml @@ -1,34 +1,33 @@ - - - - - wireup - undispatched - bson - untyped - param - shard - sharding - sqlite - postgre - rds - nano - smallint - tinyint - datetime - dbo - uniqueidentifier - varbinary - xtype - bigint - bytea - uuid - precommit - changeset - nonpositive - sysobjects - gzip - precommit - - + + + + + wireup + bson + untyped + param + shard + sharding + sqlite + postgre + rds + nano + smallint + tinyint + datetime + dbo + uniqueidentifier + varbinary + xtype + bigint + bytea + uuid + precommit + changeset + nonpositive + sysobjects + gzip + precommit + + \ No newline at end of file diff --git a/src/Directory.Build.props b/src/Directory.Build.props new file mode 100644 index 000000000..dff21ee33 --- /dev/null +++ b/src/Directory.Build.props @@ -0,0 +1,7 @@ + + + 13.0 + enable + enable + + \ No newline at end of file diff --git a/src/EventStore.sln b/src/EventStore.sln deleted file mode 100644 index 601596ef0..000000000 --- a/src/EventStore.sln +++ /dev/null @@ -1,179 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 11.00 -# Visual Studio 2010 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "proj", "proj", "{9DE51CC0-6902-49F5-9B69-B29454DE7164}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{D3C17830-A461-4BA5-A672-461B19B33936}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventStore", "proj\EventStore\EventStore.csproj", "{03946843-F343-419C-88EF-3E446D08DFA6}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventStore.Core", "proj\EventStore.Core\EventStore.Core.csproj", "{D6413244-42F5-4233-B347-D0A804B09CC9}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventStore.Core.UnitTests", "tests\EventStore.Core.UnitTests\EventStore.Core.UnitTests.csproj", "{3B5F8277-F29E-4114-AE81-4A4FBF2D7FA5}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "doc", "doc", "{CD37080A-F48D-4E5D-B7C6-B9F86BB05A38}" - ProjectSection(SolutionItems) = preProject - ..\doc\acknowledgments.txt = ..\doc\acknowledgments.txt - ..\doc\EventStore - Architectural Overview.docx = ..\doc\EventStore - Architectural Overview.docx - ..\doc\EventStore - Transactional Integrity.docx = ..\doc\EventStore - Transactional Integrity.docx - ..\doc\license.txt = ..\doc\license.txt - ..\readme.markdown = ..\readme.markdown - ..\doc\roadmap.txt = ..\doc\roadmap.txt - EndProjectSection -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventStore.Serialization", "proj\EventStore.Serialization\EventStore.Serialization.csproj", "{A5BF4B86-26F6-418D-BE35-C6CC3A623D27}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventStore.Serialization.Json", "proj\EventStore.Serialization.Json\EventStore.Serialization.Json.csproj", "{CFD895BD-7CB2-4811-A6FA-1851DF769B67}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventStore.Serialization.AcceptanceTests", "tests\EventStore.Serialization.AcceptanceTests\EventStore.Serialization.AcceptanceTests.csproj", "{FCCBA30F-738D-4C82-BAE4-906B88399AA2}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventStore.Persistence.SqlPersistence", "proj\EventStore.Persistence.SqlPersistence\EventStore.Persistence.SqlPersistence.csproj", "{DAFD3F38-33F3-4F53-BFEA-44E51BD1E8F1}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventStore.Persistence.AcceptanceTests", "tests\EventStore.Persistence.AcceptanceTests\EventStore.Persistence.AcceptanceTests.csproj", "{B56FDCEA-086F-40A2-92E1-867CE506CBE3}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventStore.Persistence.MongoPersistence", "proj\EventStore.Persistence.MongoPersistence\EventStore.Persistence.MongoPersistence.csproj", "{32ADD8CE-0F3F-41D8-BFA1-6E5D685E64DD}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventStore.Example", "..\doc\EventStore.Example\EventStore.Example.csproj", "{0143B48E-25AF-4CE0-BD49-A52267D359D3}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventStore.Persistence.RavenPersistence", "proj\EventStore.Persistence.RavenPersistence\EventStore.Persistence.RavenPersistence.csproj", "{F9E7FD69-0818-48CA-9249-5387739E1B6A}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventStore.Wireup", "proj\EventStore.Wireup\EventStore.Wireup.csproj", "{421664DB-C18D-4499-ABC1-C9086D525F80}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "wireup", "wireup", "{52F7988F-452D-46C2-A144-D85E0CF371C6}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventStore.Serialization.Json.Wireup", "proj\EventStore.Serialization.Json.Wireup\EventStore.Serialization.Json.Wireup.csproj", "{DEFFE0C3-2988-4C58-9E36-1302842FFDBD}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventStore.Persistence.MongoPersistence.Wireup", "proj\EventStore.Persistence.MongoPersistence.Wireup\EventStore.Persistence.MongoPersistence.Wireup.csproj", "{E95780CB-3114-4925-A38A-1FA4CC4EC213}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventStore.Persistence.RavenPersistence.Wireup", "proj\EventStore.Persistence.RavenPersistence.Wireup\EventStore.Persistence.RavenPersistence.Wireup.csproj", "{A99B2B16-B6BE-4B83-ACE0-56A734DB9AEF}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventStore.Serialization.ServiceStack", "proj\EventStore.Serialization.ServiceStack\EventStore.Serialization.ServiceStack.csproj", "{2BA8B905-65D5-4BBA-A76B-58EC49F26018}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventStore.Serialization.ServiceStack.Wireup", "proj\EventStore.Serialization.ServiceStack.Wireup\EventStore.Serialization.ServiceStack.Wireup.csproj", "{33B78974-7867-4C10-90AD-F31352BF0EEC}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventStore.Logging.NLog", "proj\EventStore.Logging.NLog\EventStore.Logging.NLog.csproj", "{4A336666-825A-49BA-B6F7-2156E3877A34}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventStore.Logging.Log4Net", "proj\EventStore.Logging.Log4Net\EventStore.Logging.Log4Net.csproj", "{EBCACF9E-7FE2-4C39-917E-2DD60EEE8C80}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{08A28C69-B04C-4622-8BF7-C9C4C410E64C}" - ProjectSection(SolutionItems) = preProject - .nuget\EventStore.Logging.Log4Net.nuspec = .nuget\EventStore.Logging.Log4Net.nuspec - .nuget\EventStore.Logging.NLog.nuspec = .nuget\EventStore.Logging.NLog.nuspec - .nuget\EventStore.nuspec = .nuget\EventStore.nuspec - .nuget\EventStore.Persistence.MongoPersistence.nuspec = .nuget\EventStore.Persistence.MongoPersistence.nuspec - .nuget\EventStore.Persistence.RavenPersistence.nuspec = .nuget\EventStore.Persistence.RavenPersistence.nuspec - .nuget\EventStore.Serialization.Json.nuspec = .nuget\EventStore.Serialization.Json.nuspec - .nuget\EventStore.Serialization.ServiceStack.nuspec = .nuget\EventStore.Serialization.ServiceStack.nuspec - .nuget\NuGet.Config = .nuget\NuGet.Config - .nuget\NuGet.exe = .nuget\NuGet.exe - .nuget\NuGet.targets = .nuget\NuGet.targets - EndProjectSection -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {03946843-F343-419C-88EF-3E446D08DFA6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {03946843-F343-419C-88EF-3E446D08DFA6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {03946843-F343-419C-88EF-3E446D08DFA6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {03946843-F343-419C-88EF-3E446D08DFA6}.Release|Any CPU.Build.0 = Release|Any CPU - {D6413244-42F5-4233-B347-D0A804B09CC9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D6413244-42F5-4233-B347-D0A804B09CC9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D6413244-42F5-4233-B347-D0A804B09CC9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D6413244-42F5-4233-B347-D0A804B09CC9}.Release|Any CPU.Build.0 = Release|Any CPU - {3B5F8277-F29E-4114-AE81-4A4FBF2D7FA5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3B5F8277-F29E-4114-AE81-4A4FBF2D7FA5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3B5F8277-F29E-4114-AE81-4A4FBF2D7FA5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3B5F8277-F29E-4114-AE81-4A4FBF2D7FA5}.Release|Any CPU.Build.0 = Release|Any CPU - {A5BF4B86-26F6-418D-BE35-C6CC3A623D27}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A5BF4B86-26F6-418D-BE35-C6CC3A623D27}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A5BF4B86-26F6-418D-BE35-C6CC3A623D27}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A5BF4B86-26F6-418D-BE35-C6CC3A623D27}.Release|Any CPU.Build.0 = Release|Any CPU - {CFD895BD-7CB2-4811-A6FA-1851DF769B67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CFD895BD-7CB2-4811-A6FA-1851DF769B67}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CFD895BD-7CB2-4811-A6FA-1851DF769B67}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CFD895BD-7CB2-4811-A6FA-1851DF769B67}.Release|Any CPU.Build.0 = Release|Any CPU - {FCCBA30F-738D-4C82-BAE4-906B88399AA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FCCBA30F-738D-4C82-BAE4-906B88399AA2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FCCBA30F-738D-4C82-BAE4-906B88399AA2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FCCBA30F-738D-4C82-BAE4-906B88399AA2}.Release|Any CPU.Build.0 = Release|Any CPU - {DAFD3F38-33F3-4F53-BFEA-44E51BD1E8F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DAFD3F38-33F3-4F53-BFEA-44E51BD1E8F1}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DAFD3F38-33F3-4F53-BFEA-44E51BD1E8F1}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DAFD3F38-33F3-4F53-BFEA-44E51BD1E8F1}.Release|Any CPU.Build.0 = Release|Any CPU - {B56FDCEA-086F-40A2-92E1-867CE506CBE3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B56FDCEA-086F-40A2-92E1-867CE506CBE3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B56FDCEA-086F-40A2-92E1-867CE506CBE3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B56FDCEA-086F-40A2-92E1-867CE506CBE3}.Release|Any CPU.Build.0 = Release|Any CPU - {32ADD8CE-0F3F-41D8-BFA1-6E5D685E64DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {32ADD8CE-0F3F-41D8-BFA1-6E5D685E64DD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {32ADD8CE-0F3F-41D8-BFA1-6E5D685E64DD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {32ADD8CE-0F3F-41D8-BFA1-6E5D685E64DD}.Release|Any CPU.Build.0 = Release|Any CPU - {0143B48E-25AF-4CE0-BD49-A52267D359D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0143B48E-25AF-4CE0-BD49-A52267D359D3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0143B48E-25AF-4CE0-BD49-A52267D359D3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0143B48E-25AF-4CE0-BD49-A52267D359D3}.Release|Any CPU.Build.0 = Release|Any CPU - {F9E7FD69-0818-48CA-9249-5387739E1B6A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F9E7FD69-0818-48CA-9249-5387739E1B6A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F9E7FD69-0818-48CA-9249-5387739E1B6A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F9E7FD69-0818-48CA-9249-5387739E1B6A}.Release|Any CPU.Build.0 = Release|Any CPU - {421664DB-C18D-4499-ABC1-C9086D525F80}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {421664DB-C18D-4499-ABC1-C9086D525F80}.Debug|Any CPU.Build.0 = Debug|Any CPU - {421664DB-C18D-4499-ABC1-C9086D525F80}.Release|Any CPU.ActiveCfg = Release|Any CPU - {421664DB-C18D-4499-ABC1-C9086D525F80}.Release|Any CPU.Build.0 = Release|Any CPU - {DEFFE0C3-2988-4C58-9E36-1302842FFDBD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DEFFE0C3-2988-4C58-9E36-1302842FFDBD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DEFFE0C3-2988-4C58-9E36-1302842FFDBD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DEFFE0C3-2988-4C58-9E36-1302842FFDBD}.Release|Any CPU.Build.0 = Release|Any CPU - {E95780CB-3114-4925-A38A-1FA4CC4EC213}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E95780CB-3114-4925-A38A-1FA4CC4EC213}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E95780CB-3114-4925-A38A-1FA4CC4EC213}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E95780CB-3114-4925-A38A-1FA4CC4EC213}.Release|Any CPU.Build.0 = Release|Any CPU - {A99B2B16-B6BE-4B83-ACE0-56A734DB9AEF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A99B2B16-B6BE-4B83-ACE0-56A734DB9AEF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A99B2B16-B6BE-4B83-ACE0-56A734DB9AEF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A99B2B16-B6BE-4B83-ACE0-56A734DB9AEF}.Release|Any CPU.Build.0 = Release|Any CPU - {2BA8B905-65D5-4BBA-A76B-58EC49F26018}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2BA8B905-65D5-4BBA-A76B-58EC49F26018}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2BA8B905-65D5-4BBA-A76B-58EC49F26018}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2BA8B905-65D5-4BBA-A76B-58EC49F26018}.Release|Any CPU.Build.0 = Release|Any CPU - {33B78974-7867-4C10-90AD-F31352BF0EEC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {33B78974-7867-4C10-90AD-F31352BF0EEC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {33B78974-7867-4C10-90AD-F31352BF0EEC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {33B78974-7867-4C10-90AD-F31352BF0EEC}.Release|Any CPU.Build.0 = Release|Any CPU - {4A336666-825A-49BA-B6F7-2156E3877A34}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4A336666-825A-49BA-B6F7-2156E3877A34}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4A336666-825A-49BA-B6F7-2156E3877A34}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4A336666-825A-49BA-B6F7-2156E3877A34}.Release|Any CPU.Build.0 = Release|Any CPU - {EBCACF9E-7FE2-4C39-917E-2DD60EEE8C80}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EBCACF9E-7FE2-4C39-917E-2DD60EEE8C80}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EBCACF9E-7FE2-4C39-917E-2DD60EEE8C80}.Release|Any CPU.ActiveCfg = Release|Any CPU - {EBCACF9E-7FE2-4C39-917E-2DD60EEE8C80}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {03946843-F343-419C-88EF-3E446D08DFA6} = {9DE51CC0-6902-49F5-9B69-B29454DE7164} - {D6413244-42F5-4233-B347-D0A804B09CC9} = {9DE51CC0-6902-49F5-9B69-B29454DE7164} - {A5BF4B86-26F6-418D-BE35-C6CC3A623D27} = {9DE51CC0-6902-49F5-9B69-B29454DE7164} - {CFD895BD-7CB2-4811-A6FA-1851DF769B67} = {9DE51CC0-6902-49F5-9B69-B29454DE7164} - {DAFD3F38-33F3-4F53-BFEA-44E51BD1E8F1} = {9DE51CC0-6902-49F5-9B69-B29454DE7164} - {32ADD8CE-0F3F-41D8-BFA1-6E5D685E64DD} = {9DE51CC0-6902-49F5-9B69-B29454DE7164} - {F9E7FD69-0818-48CA-9249-5387739E1B6A} = {9DE51CC0-6902-49F5-9B69-B29454DE7164} - {2BA8B905-65D5-4BBA-A76B-58EC49F26018} = {9DE51CC0-6902-49F5-9B69-B29454DE7164} - {4A336666-825A-49BA-B6F7-2156E3877A34} = {9DE51CC0-6902-49F5-9B69-B29454DE7164} - {EBCACF9E-7FE2-4C39-917E-2DD60EEE8C80} = {9DE51CC0-6902-49F5-9B69-B29454DE7164} - {3B5F8277-F29E-4114-AE81-4A4FBF2D7FA5} = {D3C17830-A461-4BA5-A672-461B19B33936} - {FCCBA30F-738D-4C82-BAE4-906B88399AA2} = {D3C17830-A461-4BA5-A672-461B19B33936} - {B56FDCEA-086F-40A2-92E1-867CE506CBE3} = {D3C17830-A461-4BA5-A672-461B19B33936} - {0143B48E-25AF-4CE0-BD49-A52267D359D3} = {CD37080A-F48D-4E5D-B7C6-B9F86BB05A38} - {421664DB-C18D-4499-ABC1-C9086D525F80} = {52F7988F-452D-46C2-A144-D85E0CF371C6} - {DEFFE0C3-2988-4C58-9E36-1302842FFDBD} = {52F7988F-452D-46C2-A144-D85E0CF371C6} - {E95780CB-3114-4925-A38A-1FA4CC4EC213} = {52F7988F-452D-46C2-A144-D85E0CF371C6} - {A99B2B16-B6BE-4B83-ACE0-56A734DB9AEF} = {52F7988F-452D-46C2-A144-D85E0CF371C6} - {33B78974-7867-4C10-90AD-F31352BF0EEC} = {52F7988F-452D-46C2-A144-D85E0CF371C6} - EndGlobalSection -EndGlobal diff --git a/src/EventStore.snk b/src/EventStore.snk deleted file mode 100644 index 0d386c107..000000000 Binary files a/src/EventStore.snk and /dev/null differ diff --git a/src/proj/GlobalAssemblyInfo.cs b/src/GlobalAssemblyInfo.cs similarity index 55% rename from src/proj/GlobalAssemblyInfo.cs rename to src/GlobalAssemblyInfo.cs index 2c354eeec..2b7d4963a 100644 --- a/src/proj/GlobalAssemblyInfo.cs +++ b/src/GlobalAssemblyInfo.cs @@ -3,17 +3,21 @@ // Copyright (c) Jonathan Oliver. All rights reserved. // //----------------------------------------------------------------------- + using System; using System.Reflection; using System.Resources; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -[assembly: AssemblyCompany("Jonathan Oliver")] -[assembly: AssemblyProduct("EventStore")] -[assembly: AssemblyCopyright("Copyright © Jonathan Oliver 2011")] +[assembly: AssemblyCompany("NEventStore")] +[assembly: AssemblyProduct("NEventStore")] +[assembly: AssemblyCopyright("Copyright © Jonathan Oliver, Jonathan Mathius, Damian Hickey and Contributors 2011")] [assembly: AssemblyTrademark("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: CLSCompliant(false)] -[assembly: NeutralResourcesLanguage("en-US")] \ No newline at end of file +[assembly: NeutralResourcesLanguage("en-US")] +[assembly: InternalsVisibleTo("NEventStore.Tests")] +[assembly: InternalsVisibleTo("NEventStore.Core.Tests")] diff --git a/src/proj/GlobalSuppressions.cs b/src/GlobalSuppressions.cs similarity index 94% rename from src/proj/GlobalSuppressions.cs rename to src/GlobalSuppressions.cs index d408fd263..fe8e858ee 100644 --- a/src/proj/GlobalSuppressions.cs +++ b/src/GlobalSuppressions.cs @@ -1,5 +1,5 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) Jonathan Oliver. All rights reserved. -// -//----------------------------------------------------------------------- +//----------------------------------------------------------------------- +// +// Copyright (c) Jonathan Oliver. All rights reserved. +// +//----------------------------------------------------------------------- \ No newline at end of file diff --git a/src/NEventStore.Benchmark/Benchmarks/PersistenceBenchmarks.cs b/src/NEventStore.Benchmark/Benchmarks/PersistenceBenchmarks.cs new file mode 100644 index 000000000..d57749da9 --- /dev/null +++ b/src/NEventStore.Benchmark/Benchmarks/PersistenceBenchmarks.cs @@ -0,0 +1,124 @@ +using BenchmarkDotNet.Attributes; +using NEventStore.Benchmark.Support; + +namespace NEventStore.Benchmark.Benchmarks +{ + [Config(typeof(AllowNonOptimized))] + [SimpleJob(launchCount: 3, warmupCount: 3, iterationCount: 3, invocationCount: 1)] + [MemoryDiagnoser] + [MeanColumn, StdErrorColumn, StdDevColumn, MinColumn, MaxColumn, IterationsColumn] + public class PersistenceBenchmarks + { + private static readonly Guid StreamId = Guid.NewGuid(); // aggregate identifier + private readonly IStoreEvents _eventStore; + + public PersistenceBenchmarks() + { + _eventStore = EventStoreHelpers.WireupEventStore(); + } + + [Params(100, 1000, 10000, 100000)] + public int CommitsToWrite { get; set; } + + [Benchmark] + public void WriteToStream() + { + // we can call CreateStream(StreamId) if we know there isn't going to be any data. + // or we can call OpenStream(StreamId, 0, int.MaxValue) to read all commits, + // if no commits exist then it creates a new stream for us. + using var stream = _eventStore.OpenStream(StreamId, 0, int.MaxValue); + for (int i = 0; i < CommitsToWrite; i++) + { + var @event = new SomeDomainEvent { Value = i.ToString() }; + stream.Add(new EventMessage { Body = @event }); + stream.CommitChanges(Guid.NewGuid()); + } + } + + [Benchmark] + public async Task WriteToStreamAsync() + { + // we can call CreateStream(StreamId) if we know there isn't going to be any data. + // or we can call OpenStream(StreamId, 0, int.MaxValue) to read all commits, + // if no commits exist then it creates a new stream for us. + using var stream = await _eventStore.OpenStreamAsync(StreamId, 0, int.MaxValue, CancellationToken.None).ConfigureAwait(false); + for (int i = 0; i < CommitsToWrite; i++) + { + var @event = new SomeDomainEvent { Value = i.ToString() }; + stream.Add(new EventMessage { Body = @event }); + await stream.CommitChangesAsync(Guid.NewGuid(), CancellationToken.None).ConfigureAwait(false); + } + } + + [GlobalSetup(Targets = new string[] { nameof(ReadFromStream), nameof(ReadFromEventStore) })] + public void ReadSetup() + { + using var stream = _eventStore.OpenStream(StreamId, 0, int.MaxValue); + for (int i = 0; i < CommitsToWrite; i++) + { + var @event = new SomeDomainEvent { Value = i.ToString() }; + stream.Add(new EventMessage { Body = @event }); + stream.CommitChanges(Guid.NewGuid()); + } + } + + [Benchmark] + public void ReadFromStream() + { + // we can call CreateStream(StreamId) if we know there isn't going to be any data. + // or we can call OpenStream(StreamId, 0, int.MaxValue) to read all commits, + // if no commits exist then it creates a new stream for us. + using var stream = _eventStore.OpenStream(StreamId, 0, int.MaxValue); + // the whole stream has been read + // Console.WriteLine(stream.CommittedEvents.First().Body); + } + + [Benchmark] + public void ReadFromEventStore() + { + var commits = _eventStore.Advanced.GetFrom(Bucket.Default, 0); + foreach (var _ in commits) + { + // just iterate through all the commits + // Console.WriteLine(c); + } + } + + [GlobalSetup(Targets = new string[] { nameof(ReadFromStreamAsync), nameof(ReadFromEventStoreAsync) })] + public async Task ReadSetupAsync() + { + using var stream = await _eventStore.OpenStreamAsync(StreamId, 0, int.MaxValue, CancellationToken.None).ConfigureAwait(false); + for (int i = 0; i < CommitsToWrite; i++) + { + var @event = new SomeDomainEvent { Value = i.ToString() }; + stream.Add(new EventMessage { Body = @event }); + await stream.CommitChangesAsync(Guid.NewGuid(), CancellationToken.None).ConfigureAwait(false); + } + } + + [Benchmark] + public async Task ReadFromStreamAsync() + { + // we can call CreateStream(StreamId) if we know there isn't going to be any data. + // or we can call OpenStream(StreamId, 0, int.MaxValue) to read all commits, + // if no commits exist then it creates a new stream for us. + using var stream = await _eventStore.OpenStreamAsync(StreamId, 0, int.MaxValue, CancellationToken.None).ConfigureAwait(false); + // the whole stream has been read + // Console.WriteLine(stream.CommittedEvents.First().Body); + } + + [Benchmark] + public Task ReadFromEventStoreAsync() + { + // just iterate through all the commits +#pragma warning disable RCS1163 // Unused parameter + return _eventStore.Advanced.GetFromAsync(Bucket.Default, 0, new LambdaAsyncObserver( + (c, _) => + { + // Console.WriteLine(c.Events.First().Body); + return Task.FromResult(true); + }), CancellationToken.None); +#pragma warning restore RCS1163 // Unused parameter + } + } +} diff --git a/src/NEventStore.Benchmark/NEventStore.Benchmark.csproj b/src/NEventStore.Benchmark/NEventStore.Benchmark.csproj new file mode 100644 index 000000000..35fb58158 --- /dev/null +++ b/src/NEventStore.Benchmark/NEventStore.Benchmark.csproj @@ -0,0 +1,24 @@ + + + + net8.0;net472 + false + + exe + + Exe + + + + TRACE;DEBUG + + + + + + + + + + + diff --git a/src/NEventStore.Benchmark/Program.cs b/src/NEventStore.Benchmark/Program.cs new file mode 100644 index 000000000..941e55bae --- /dev/null +++ b/src/NEventStore.Benchmark/Program.cs @@ -0,0 +1,13 @@ +using BenchmarkDotNet.Running; +using NEventStore.Benchmark.Benchmarks; + +namespace NEventStore.Benchmark +{ + public static class Program + { + public static void Main(string[] _) + { + BenchmarkRunner.Run(); + } + } +} diff --git a/src/NEventStore.Benchmark/Support/AllowNonOptimized.cs b/src/NEventStore.Benchmark/Support/AllowNonOptimized.cs new file mode 100644 index 000000000..91a0caeb5 --- /dev/null +++ b/src/NEventStore.Benchmark/Support/AllowNonOptimized.cs @@ -0,0 +1,18 @@ +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Validators; +using System.Linq; + +namespace NEventStore.Benchmark.Support +{ + public class AllowNonOptimized : ManualConfig + { + public AllowNonOptimized() + { + AddValidator(JitOptimizationsValidator.DontFailOnError); // ALLOW NON-OPTIMIZED DLLS + + AddLogger(DefaultConfig.Instance.GetLoggers().ToArray()); // manual config has no loggers by default + AddExporter(DefaultConfig.Instance.GetExporters().ToArray()); // manual config has no exporters by default + AddColumnProvider(DefaultConfig.Instance.GetColumnProviders().ToArray()); // manual config has no columns by default + } + } +} diff --git a/src/NEventStore.Benchmark/Support/EventStoreHelpers.cs b/src/NEventStore.Benchmark/Support/EventStoreHelpers.cs new file mode 100644 index 000000000..1bed8ce2d --- /dev/null +++ b/src/NEventStore.Benchmark/Support/EventStoreHelpers.cs @@ -0,0 +1,19 @@ +namespace NEventStore.Benchmark.Support +{ + internal static class EventStoreHelpers + { + internal static IStoreEvents WireupEventStore() + { + return Wireup.Init() + // .LogToOutputWindow(LogLevel.Verbose) + // .LogToConsoleWindow(LogLevel.Verbose) + .UsingInMemoryPersistence() + .InitializeStorageEngine() +#if NET462 + .TrackPerformanceInstance("example") +#endif + // .HookIntoPipelineUsing(new[] { new AuthorizationPipelineHook() }) + .Build(); + } + } +} diff --git a/src/NEventStore.Benchmark/Support/SomeDomainEvent.cs b/src/NEventStore.Benchmark/Support/SomeDomainEvent.cs new file mode 100644 index 000000000..7b36d365b --- /dev/null +++ b/src/NEventStore.Benchmark/Support/SomeDomainEvent.cs @@ -0,0 +1,7 @@ +namespace NEventStore.Benchmark.Support +{ + internal class SomeDomainEvent + { + public string? Value { get; set; } + } +} diff --git a/src/NEventStore.Core.sln b/src/NEventStore.Core.sln new file mode 100644 index 000000000..746933f4b --- /dev/null +++ b/src/NEventStore.Core.sln @@ -0,0 +1,303 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.12.35521.163 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".test", ".test", "{D3C17830-A461-4BA5-A672-461B19B33936}" + ProjectSection(SolutionItems) = preProject + NEventStore.Persistence.AcceptanceTests\PersistenceTests.Async.cs = NEventStore.Persistence.AcceptanceTests\PersistenceTests.Async.cs + NEventStore.Persistence.AcceptanceTests\PersistenceTests.cs = NEventStore.Persistence.AcceptanceTests\PersistenceTests.cs + NEventStore.Persistence.AcceptanceTests\PersistenceTests.Transactions.cs = NEventStore.Persistence.AcceptanceTests\PersistenceTests.Transactions.cs + NEventStore.Persistence.AcceptanceTests\SerializationTests.cs = NEventStore.Persistence.AcceptanceTests\SerializationTests.cs + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "serialization", "serialization", "{52F7988F-452D-46C2-A144-D85E0CF371C6}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "core", "core", "{D35087BF-941A-49EA-96A8-B10457C4D169}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NEventStore.Core", "NEventStore\NEventStore.Core.csproj", "{EB3ABF3B-295C-4BDD-8EA2-B60F30BC5E6C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NEventStore.Persistence.AcceptanceTests.Core", "NEventStore.Persistence.AcceptanceTests\NEventStore.Persistence.AcceptanceTests.Core.csproj", "{608A1D85-0999-4A9B-956A-11B91A1CB065}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NEventStore.Serialization.Json.Core", "NEventStore.Serialization.Json\NEventStore.Serialization.Json.Core.csproj", "{9A46374D-029C-4B87-88E3-0BE5D1D44C35}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NEventStore.Serialization.Json.Core.Tests", "NEventStore.Serialization.Json.Tests\NEventStore.Serialization.Json.Core.Tests.csproj", "{D7029339-3794-4275-893A-E705D872BFBE}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NEventStore.Serialization.Bson.Core.Tests", "NEventStore.Serialization.Bson.Tests\NEventStore.Serialization.Bson.Core.Tests.csproj", "{2C6CE3D7-D2DF-49F7-B3AF-5767E1C3705A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NEventStore.Serialization.GZip.Core.Tests", "NEventStore.Serialization.Gzip.Tests\NEventStore.Serialization.GZip.Core.Tests.csproj", "{BCCDD838-8368-418D-82B5-5389FDDF0ADD}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NEventStore.Core.Tests", "NEventStore.Tests\NEventStore.Core.Tests.csproj", "{8860810C-5E46-4E60-8924-C6C77BBC6EB2}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{407B4AC0-6C11-4123-9BBA-094B2CADCD3A}" + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + ..\appveyor.yml = ..\appveyor.yml + ..\build.ps1 = ..\build.ps1 + ..\Changelog.md = ..\Changelog.md + Directory.Build.props = Directory.Build.props + ..\license.txt = ..\license.txt + ..\Readme.md = ..\Readme.md + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NEventStore.Serialization.Binary.Core.Tests", "NEventStore.Serialization.Binary.Tests\NEventStore.Serialization.Binary.Core.Tests.csproj", "{9F0BFA49-8711-4143-B813-ACD0D0B003FD}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NEventStore.Serialization.Rijndael.Core.Tests", "NEventStore.Serialization.Rijndael.Tests\NEventStore.Serialization.Rijndael.Core.Tests.csproj", "{56B15268-DEBC-4E9F-8554-03737A39D71B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NEventStore.Benchmark", "NEventStore.Benchmark\NEventStore.Benchmark.csproj", "{2D3523BB-CF07-47D0-82F4-6B5621A8C1D7}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NEventStore.Example", "NEventStore.Example\NEventStore.Example.csproj", "{04174210-4A5D-4F26-883E-B52B7B167EAC}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NEventStore.PollingClientExample", "NEventStore.PollingClientExample\NEventStore.PollingClientExample.csproj", "{310733D1-2BF4-44D7-9947-93E29FA44310}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NEventStore.Serialization.Bson.Core", "NEventStore.Serialization.Bson\NEventStore.Serialization.Bson.Core.csproj", "{C45058FC-A657-4D13-AF2A-6E0B30C444A6}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NEventStore.PollingClient", "NEventStore.PollingClient\NEventStore.PollingClient.csproj", "{1F03D5F0-238E-48B0-BAE4-88B1E60B69C6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NEventStore.Serialization.MsgPack.Core", "NEventStore.Serialization.MsgPack\NEventStore.Serialization.MsgPack.Core.csproj", "{187E9876-28EC-43D7-B70E-2C09E66D59BD}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NEventStore.Serialization.MsgPack.Core.Tests", "NEventStore.Serialization.MsgPack.Tests\NEventStore.Serialization.MsgPack.Core.Tests.csproj", "{7FD67842-0549-4062-ACAB-3C417BD218C1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NEventStore.Serialization.Binary.Core", "NEventStore.Serialization.Binary\NEventStore.Serialization.Binary.Core.csproj", "{26297E78-023D-4F3C-4F39-A7CC4FA9AF21}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {EB3ABF3B-295C-4BDD-8EA2-B60F30BC5E6C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EB3ABF3B-295C-4BDD-8EA2-B60F30BC5E6C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EB3ABF3B-295C-4BDD-8EA2-B60F30BC5E6C}.Debug|x64.ActiveCfg = Debug|Any CPU + {EB3ABF3B-295C-4BDD-8EA2-B60F30BC5E6C}.Debug|x64.Build.0 = Debug|Any CPU + {EB3ABF3B-295C-4BDD-8EA2-B60F30BC5E6C}.Debug|x86.ActiveCfg = Debug|Any CPU + {EB3ABF3B-295C-4BDD-8EA2-B60F30BC5E6C}.Debug|x86.Build.0 = Debug|Any CPU + {EB3ABF3B-295C-4BDD-8EA2-B60F30BC5E6C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EB3ABF3B-295C-4BDD-8EA2-B60F30BC5E6C}.Release|Any CPU.Build.0 = Release|Any CPU + {EB3ABF3B-295C-4BDD-8EA2-B60F30BC5E6C}.Release|x64.ActiveCfg = Release|Any CPU + {EB3ABF3B-295C-4BDD-8EA2-B60F30BC5E6C}.Release|x64.Build.0 = Release|Any CPU + {EB3ABF3B-295C-4BDD-8EA2-B60F30BC5E6C}.Release|x86.ActiveCfg = Release|Any CPU + {EB3ABF3B-295C-4BDD-8EA2-B60F30BC5E6C}.Release|x86.Build.0 = Release|Any CPU + {608A1D85-0999-4A9B-956A-11B91A1CB065}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {608A1D85-0999-4A9B-956A-11B91A1CB065}.Debug|Any CPU.Build.0 = Debug|Any CPU + {608A1D85-0999-4A9B-956A-11B91A1CB065}.Debug|x64.ActiveCfg = Debug|Any CPU + {608A1D85-0999-4A9B-956A-11B91A1CB065}.Debug|x64.Build.0 = Debug|Any CPU + {608A1D85-0999-4A9B-956A-11B91A1CB065}.Debug|x86.ActiveCfg = Debug|Any CPU + {608A1D85-0999-4A9B-956A-11B91A1CB065}.Debug|x86.Build.0 = Debug|Any CPU + {608A1D85-0999-4A9B-956A-11B91A1CB065}.Release|Any CPU.ActiveCfg = Release|Any CPU + {608A1D85-0999-4A9B-956A-11B91A1CB065}.Release|Any CPU.Build.0 = Release|Any CPU + {608A1D85-0999-4A9B-956A-11B91A1CB065}.Release|x64.ActiveCfg = Release|Any CPU + {608A1D85-0999-4A9B-956A-11B91A1CB065}.Release|x64.Build.0 = Release|Any CPU + {608A1D85-0999-4A9B-956A-11B91A1CB065}.Release|x86.ActiveCfg = Release|Any CPU + {608A1D85-0999-4A9B-956A-11B91A1CB065}.Release|x86.Build.0 = Release|Any CPU + {9A46374D-029C-4B87-88E3-0BE5D1D44C35}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9A46374D-029C-4B87-88E3-0BE5D1D44C35}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9A46374D-029C-4B87-88E3-0BE5D1D44C35}.Debug|x64.ActiveCfg = Debug|Any CPU + {9A46374D-029C-4B87-88E3-0BE5D1D44C35}.Debug|x64.Build.0 = Debug|Any CPU + {9A46374D-029C-4B87-88E3-0BE5D1D44C35}.Debug|x86.ActiveCfg = Debug|Any CPU + {9A46374D-029C-4B87-88E3-0BE5D1D44C35}.Debug|x86.Build.0 = Debug|Any CPU + {9A46374D-029C-4B87-88E3-0BE5D1D44C35}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9A46374D-029C-4B87-88E3-0BE5D1D44C35}.Release|Any CPU.Build.0 = Release|Any CPU + {9A46374D-029C-4B87-88E3-0BE5D1D44C35}.Release|x64.ActiveCfg = Release|Any CPU + {9A46374D-029C-4B87-88E3-0BE5D1D44C35}.Release|x64.Build.0 = Release|Any CPU + {9A46374D-029C-4B87-88E3-0BE5D1D44C35}.Release|x86.ActiveCfg = Release|Any CPU + {9A46374D-029C-4B87-88E3-0BE5D1D44C35}.Release|x86.Build.0 = Release|Any CPU + {D7029339-3794-4275-893A-E705D872BFBE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D7029339-3794-4275-893A-E705D872BFBE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D7029339-3794-4275-893A-E705D872BFBE}.Debug|x64.ActiveCfg = Debug|Any CPU + {D7029339-3794-4275-893A-E705D872BFBE}.Debug|x64.Build.0 = Debug|Any CPU + {D7029339-3794-4275-893A-E705D872BFBE}.Debug|x86.ActiveCfg = Debug|Any CPU + {D7029339-3794-4275-893A-E705D872BFBE}.Debug|x86.Build.0 = Debug|Any CPU + {D7029339-3794-4275-893A-E705D872BFBE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D7029339-3794-4275-893A-E705D872BFBE}.Release|Any CPU.Build.0 = Release|Any CPU + {D7029339-3794-4275-893A-E705D872BFBE}.Release|x64.ActiveCfg = Release|Any CPU + {D7029339-3794-4275-893A-E705D872BFBE}.Release|x64.Build.0 = Release|Any CPU + {D7029339-3794-4275-893A-E705D872BFBE}.Release|x86.ActiveCfg = Release|Any CPU + {D7029339-3794-4275-893A-E705D872BFBE}.Release|x86.Build.0 = Release|Any CPU + {2C6CE3D7-D2DF-49F7-B3AF-5767E1C3705A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2C6CE3D7-D2DF-49F7-B3AF-5767E1C3705A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2C6CE3D7-D2DF-49F7-B3AF-5767E1C3705A}.Debug|x64.ActiveCfg = Debug|Any CPU + {2C6CE3D7-D2DF-49F7-B3AF-5767E1C3705A}.Debug|x64.Build.0 = Debug|Any CPU + {2C6CE3D7-D2DF-49F7-B3AF-5767E1C3705A}.Debug|x86.ActiveCfg = Debug|Any CPU + {2C6CE3D7-D2DF-49F7-B3AF-5767E1C3705A}.Debug|x86.Build.0 = Debug|Any CPU + {2C6CE3D7-D2DF-49F7-B3AF-5767E1C3705A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2C6CE3D7-D2DF-49F7-B3AF-5767E1C3705A}.Release|Any CPU.Build.0 = Release|Any CPU + {2C6CE3D7-D2DF-49F7-B3AF-5767E1C3705A}.Release|x64.ActiveCfg = Release|Any CPU + {2C6CE3D7-D2DF-49F7-B3AF-5767E1C3705A}.Release|x64.Build.0 = Release|Any CPU + {2C6CE3D7-D2DF-49F7-B3AF-5767E1C3705A}.Release|x86.ActiveCfg = Release|Any CPU + {2C6CE3D7-D2DF-49F7-B3AF-5767E1C3705A}.Release|x86.Build.0 = Release|Any CPU + {BCCDD838-8368-418D-82B5-5389FDDF0ADD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BCCDD838-8368-418D-82B5-5389FDDF0ADD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BCCDD838-8368-418D-82B5-5389FDDF0ADD}.Debug|x64.ActiveCfg = Debug|Any CPU + {BCCDD838-8368-418D-82B5-5389FDDF0ADD}.Debug|x64.Build.0 = Debug|Any CPU + {BCCDD838-8368-418D-82B5-5389FDDF0ADD}.Debug|x86.ActiveCfg = Debug|Any CPU + {BCCDD838-8368-418D-82B5-5389FDDF0ADD}.Debug|x86.Build.0 = Debug|Any CPU + {BCCDD838-8368-418D-82B5-5389FDDF0ADD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BCCDD838-8368-418D-82B5-5389FDDF0ADD}.Release|Any CPU.Build.0 = Release|Any CPU + {BCCDD838-8368-418D-82B5-5389FDDF0ADD}.Release|x64.ActiveCfg = Release|Any CPU + {BCCDD838-8368-418D-82B5-5389FDDF0ADD}.Release|x64.Build.0 = Release|Any CPU + {BCCDD838-8368-418D-82B5-5389FDDF0ADD}.Release|x86.ActiveCfg = Release|Any CPU + {BCCDD838-8368-418D-82B5-5389FDDF0ADD}.Release|x86.Build.0 = Release|Any CPU + {8860810C-5E46-4E60-8924-C6C77BBC6EB2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8860810C-5E46-4E60-8924-C6C77BBC6EB2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8860810C-5E46-4E60-8924-C6C77BBC6EB2}.Debug|x64.ActiveCfg = Debug|Any CPU + {8860810C-5E46-4E60-8924-C6C77BBC6EB2}.Debug|x64.Build.0 = Debug|Any CPU + {8860810C-5E46-4E60-8924-C6C77BBC6EB2}.Debug|x86.ActiveCfg = Debug|Any CPU + {8860810C-5E46-4E60-8924-C6C77BBC6EB2}.Debug|x86.Build.0 = Debug|Any CPU + {8860810C-5E46-4E60-8924-C6C77BBC6EB2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8860810C-5E46-4E60-8924-C6C77BBC6EB2}.Release|Any CPU.Build.0 = Release|Any CPU + {8860810C-5E46-4E60-8924-C6C77BBC6EB2}.Release|x64.ActiveCfg = Release|Any CPU + {8860810C-5E46-4E60-8924-C6C77BBC6EB2}.Release|x64.Build.0 = Release|Any CPU + {8860810C-5E46-4E60-8924-C6C77BBC6EB2}.Release|x86.ActiveCfg = Release|Any CPU + {8860810C-5E46-4E60-8924-C6C77BBC6EB2}.Release|x86.Build.0 = Release|Any CPU + {9F0BFA49-8711-4143-B813-ACD0D0B003FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9F0BFA49-8711-4143-B813-ACD0D0B003FD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9F0BFA49-8711-4143-B813-ACD0D0B003FD}.Debug|x64.ActiveCfg = Debug|Any CPU + {9F0BFA49-8711-4143-B813-ACD0D0B003FD}.Debug|x64.Build.0 = Debug|Any CPU + {9F0BFA49-8711-4143-B813-ACD0D0B003FD}.Debug|x86.ActiveCfg = Debug|Any CPU + {9F0BFA49-8711-4143-B813-ACD0D0B003FD}.Debug|x86.Build.0 = Debug|Any CPU + {9F0BFA49-8711-4143-B813-ACD0D0B003FD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9F0BFA49-8711-4143-B813-ACD0D0B003FD}.Release|Any CPU.Build.0 = Release|Any CPU + {9F0BFA49-8711-4143-B813-ACD0D0B003FD}.Release|x64.ActiveCfg = Release|Any CPU + {9F0BFA49-8711-4143-B813-ACD0D0B003FD}.Release|x64.Build.0 = Release|Any CPU + {9F0BFA49-8711-4143-B813-ACD0D0B003FD}.Release|x86.ActiveCfg = Release|Any CPU + {9F0BFA49-8711-4143-B813-ACD0D0B003FD}.Release|x86.Build.0 = Release|Any CPU + {56B15268-DEBC-4E9F-8554-03737A39D71B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {56B15268-DEBC-4E9F-8554-03737A39D71B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {56B15268-DEBC-4E9F-8554-03737A39D71B}.Debug|x64.ActiveCfg = Debug|Any CPU + {56B15268-DEBC-4E9F-8554-03737A39D71B}.Debug|x64.Build.0 = Debug|Any CPU + {56B15268-DEBC-4E9F-8554-03737A39D71B}.Debug|x86.ActiveCfg = Debug|Any CPU + {56B15268-DEBC-4E9F-8554-03737A39D71B}.Debug|x86.Build.0 = Debug|Any CPU + {56B15268-DEBC-4E9F-8554-03737A39D71B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {56B15268-DEBC-4E9F-8554-03737A39D71B}.Release|Any CPU.Build.0 = Release|Any CPU + {56B15268-DEBC-4E9F-8554-03737A39D71B}.Release|x64.ActiveCfg = Release|Any CPU + {56B15268-DEBC-4E9F-8554-03737A39D71B}.Release|x64.Build.0 = Release|Any CPU + {56B15268-DEBC-4E9F-8554-03737A39D71B}.Release|x86.ActiveCfg = Release|Any CPU + {56B15268-DEBC-4E9F-8554-03737A39D71B}.Release|x86.Build.0 = Release|Any CPU + {2D3523BB-CF07-47D0-82F4-6B5621A8C1D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2D3523BB-CF07-47D0-82F4-6B5621A8C1D7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2D3523BB-CF07-47D0-82F4-6B5621A8C1D7}.Debug|x64.ActiveCfg = Debug|Any CPU + {2D3523BB-CF07-47D0-82F4-6B5621A8C1D7}.Debug|x64.Build.0 = Debug|Any CPU + {2D3523BB-CF07-47D0-82F4-6B5621A8C1D7}.Debug|x86.ActiveCfg = Debug|Any CPU + {2D3523BB-CF07-47D0-82F4-6B5621A8C1D7}.Debug|x86.Build.0 = Debug|Any CPU + {2D3523BB-CF07-47D0-82F4-6B5621A8C1D7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2D3523BB-CF07-47D0-82F4-6B5621A8C1D7}.Release|Any CPU.Build.0 = Release|Any CPU + {2D3523BB-CF07-47D0-82F4-6B5621A8C1D7}.Release|x64.ActiveCfg = Release|Any CPU + {2D3523BB-CF07-47D0-82F4-6B5621A8C1D7}.Release|x64.Build.0 = Release|Any CPU + {2D3523BB-CF07-47D0-82F4-6B5621A8C1D7}.Release|x86.ActiveCfg = Release|Any CPU + {2D3523BB-CF07-47D0-82F4-6B5621A8C1D7}.Release|x86.Build.0 = Release|Any CPU + {04174210-4A5D-4F26-883E-B52B7B167EAC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {04174210-4A5D-4F26-883E-B52B7B167EAC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {04174210-4A5D-4F26-883E-B52B7B167EAC}.Debug|x64.ActiveCfg = Debug|Any CPU + {04174210-4A5D-4F26-883E-B52B7B167EAC}.Debug|x64.Build.0 = Debug|Any CPU + {04174210-4A5D-4F26-883E-B52B7B167EAC}.Debug|x86.ActiveCfg = Debug|Any CPU + {04174210-4A5D-4F26-883E-B52B7B167EAC}.Debug|x86.Build.0 = Debug|Any CPU + {04174210-4A5D-4F26-883E-B52B7B167EAC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {04174210-4A5D-4F26-883E-B52B7B167EAC}.Release|Any CPU.Build.0 = Release|Any CPU + {04174210-4A5D-4F26-883E-B52B7B167EAC}.Release|x64.ActiveCfg = Release|Any CPU + {04174210-4A5D-4F26-883E-B52B7B167EAC}.Release|x64.Build.0 = Release|Any CPU + {04174210-4A5D-4F26-883E-B52B7B167EAC}.Release|x86.ActiveCfg = Release|Any CPU + {04174210-4A5D-4F26-883E-B52B7B167EAC}.Release|x86.Build.0 = Release|Any CPU + {310733D1-2BF4-44D7-9947-93E29FA44310}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {310733D1-2BF4-44D7-9947-93E29FA44310}.Debug|Any CPU.Build.0 = Debug|Any CPU + {310733D1-2BF4-44D7-9947-93E29FA44310}.Debug|x64.ActiveCfg = Debug|Any CPU + {310733D1-2BF4-44D7-9947-93E29FA44310}.Debug|x64.Build.0 = Debug|Any CPU + {310733D1-2BF4-44D7-9947-93E29FA44310}.Debug|x86.ActiveCfg = Debug|Any CPU + {310733D1-2BF4-44D7-9947-93E29FA44310}.Debug|x86.Build.0 = Debug|Any CPU + {310733D1-2BF4-44D7-9947-93E29FA44310}.Release|Any CPU.ActiveCfg = Release|Any CPU + {310733D1-2BF4-44D7-9947-93E29FA44310}.Release|Any CPU.Build.0 = Release|Any CPU + {310733D1-2BF4-44D7-9947-93E29FA44310}.Release|x64.ActiveCfg = Release|Any CPU + {310733D1-2BF4-44D7-9947-93E29FA44310}.Release|x64.Build.0 = Release|Any CPU + {310733D1-2BF4-44D7-9947-93E29FA44310}.Release|x86.ActiveCfg = Release|Any CPU + {310733D1-2BF4-44D7-9947-93E29FA44310}.Release|x86.Build.0 = Release|Any CPU + {C45058FC-A657-4D13-AF2A-6E0B30C444A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C45058FC-A657-4D13-AF2A-6E0B30C444A6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C45058FC-A657-4D13-AF2A-6E0B30C444A6}.Debug|x64.ActiveCfg = Debug|Any CPU + {C45058FC-A657-4D13-AF2A-6E0B30C444A6}.Debug|x64.Build.0 = Debug|Any CPU + {C45058FC-A657-4D13-AF2A-6E0B30C444A6}.Debug|x86.ActiveCfg = Debug|Any CPU + {C45058FC-A657-4D13-AF2A-6E0B30C444A6}.Debug|x86.Build.0 = Debug|Any CPU + {C45058FC-A657-4D13-AF2A-6E0B30C444A6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C45058FC-A657-4D13-AF2A-6E0B30C444A6}.Release|Any CPU.Build.0 = Release|Any CPU + {C45058FC-A657-4D13-AF2A-6E0B30C444A6}.Release|x64.ActiveCfg = Release|Any CPU + {C45058FC-A657-4D13-AF2A-6E0B30C444A6}.Release|x64.Build.0 = Release|Any CPU + {C45058FC-A657-4D13-AF2A-6E0B30C444A6}.Release|x86.ActiveCfg = Release|Any CPU + {C45058FC-A657-4D13-AF2A-6E0B30C444A6}.Release|x86.Build.0 = Release|Any CPU + {1F03D5F0-238E-48B0-BAE4-88B1E60B69C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1F03D5F0-238E-48B0-BAE4-88B1E60B69C6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1F03D5F0-238E-48B0-BAE4-88B1E60B69C6}.Debug|x64.ActiveCfg = Debug|Any CPU + {1F03D5F0-238E-48B0-BAE4-88B1E60B69C6}.Debug|x64.Build.0 = Debug|Any CPU + {1F03D5F0-238E-48B0-BAE4-88B1E60B69C6}.Debug|x86.ActiveCfg = Debug|Any CPU + {1F03D5F0-238E-48B0-BAE4-88B1E60B69C6}.Debug|x86.Build.0 = Debug|Any CPU + {1F03D5F0-238E-48B0-BAE4-88B1E60B69C6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1F03D5F0-238E-48B0-BAE4-88B1E60B69C6}.Release|Any CPU.Build.0 = Release|Any CPU + {1F03D5F0-238E-48B0-BAE4-88B1E60B69C6}.Release|x64.ActiveCfg = Release|Any CPU + {1F03D5F0-238E-48B0-BAE4-88B1E60B69C6}.Release|x64.Build.0 = Release|Any CPU + {1F03D5F0-238E-48B0-BAE4-88B1E60B69C6}.Release|x86.ActiveCfg = Release|Any CPU + {1F03D5F0-238E-48B0-BAE4-88B1E60B69C6}.Release|x86.Build.0 = Release|Any CPU + {187E9876-28EC-43D7-B70E-2C09E66D59BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {187E9876-28EC-43D7-B70E-2C09E66D59BD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {187E9876-28EC-43D7-B70E-2C09E66D59BD}.Debug|x64.ActiveCfg = Debug|Any CPU + {187E9876-28EC-43D7-B70E-2C09E66D59BD}.Debug|x64.Build.0 = Debug|Any CPU + {187E9876-28EC-43D7-B70E-2C09E66D59BD}.Debug|x86.ActiveCfg = Debug|Any CPU + {187E9876-28EC-43D7-B70E-2C09E66D59BD}.Debug|x86.Build.0 = Debug|Any CPU + {187E9876-28EC-43D7-B70E-2C09E66D59BD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {187E9876-28EC-43D7-B70E-2C09E66D59BD}.Release|Any CPU.Build.0 = Release|Any CPU + {187E9876-28EC-43D7-B70E-2C09E66D59BD}.Release|x64.ActiveCfg = Release|Any CPU + {187E9876-28EC-43D7-B70E-2C09E66D59BD}.Release|x64.Build.0 = Release|Any CPU + {187E9876-28EC-43D7-B70E-2C09E66D59BD}.Release|x86.ActiveCfg = Release|Any CPU + {187E9876-28EC-43D7-B70E-2C09E66D59BD}.Release|x86.Build.0 = Release|Any CPU + {7FD67842-0549-4062-ACAB-3C417BD218C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7FD67842-0549-4062-ACAB-3C417BD218C1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7FD67842-0549-4062-ACAB-3C417BD218C1}.Debug|x64.ActiveCfg = Debug|Any CPU + {7FD67842-0549-4062-ACAB-3C417BD218C1}.Debug|x64.Build.0 = Debug|Any CPU + {7FD67842-0549-4062-ACAB-3C417BD218C1}.Debug|x86.ActiveCfg = Debug|Any CPU + {7FD67842-0549-4062-ACAB-3C417BD218C1}.Debug|x86.Build.0 = Debug|Any CPU + {7FD67842-0549-4062-ACAB-3C417BD218C1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7FD67842-0549-4062-ACAB-3C417BD218C1}.Release|Any CPU.Build.0 = Release|Any CPU + {7FD67842-0549-4062-ACAB-3C417BD218C1}.Release|x64.ActiveCfg = Release|Any CPU + {7FD67842-0549-4062-ACAB-3C417BD218C1}.Release|x64.Build.0 = Release|Any CPU + {7FD67842-0549-4062-ACAB-3C417BD218C1}.Release|x86.ActiveCfg = Release|Any CPU + {7FD67842-0549-4062-ACAB-3C417BD218C1}.Release|x86.Build.0 = Release|Any CPU + {26297E78-023D-4F3C-4F39-A7CC4FA9AF21}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {26297E78-023D-4F3C-4F39-A7CC4FA9AF21}.Debug|Any CPU.Build.0 = Debug|Any CPU + {26297E78-023D-4F3C-4F39-A7CC4FA9AF21}.Debug|x64.ActiveCfg = Debug|Any CPU + {26297E78-023D-4F3C-4F39-A7CC4FA9AF21}.Debug|x64.Build.0 = Debug|Any CPU + {26297E78-023D-4F3C-4F39-A7CC4FA9AF21}.Debug|x86.ActiveCfg = Debug|Any CPU + {26297E78-023D-4F3C-4F39-A7CC4FA9AF21}.Debug|x86.Build.0 = Debug|Any CPU + {26297E78-023D-4F3C-4F39-A7CC4FA9AF21}.Release|Any CPU.ActiveCfg = Release|Any CPU + {26297E78-023D-4F3C-4F39-A7CC4FA9AF21}.Release|Any CPU.Build.0 = Release|Any CPU + {26297E78-023D-4F3C-4F39-A7CC4FA9AF21}.Release|x64.ActiveCfg = Release|Any CPU + {26297E78-023D-4F3C-4F39-A7CC4FA9AF21}.Release|x64.Build.0 = Release|Any CPU + {26297E78-023D-4F3C-4F39-A7CC4FA9AF21}.Release|x86.ActiveCfg = Release|Any CPU + {26297E78-023D-4F3C-4F39-A7CC4FA9AF21}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {EB3ABF3B-295C-4BDD-8EA2-B60F30BC5E6C} = {D35087BF-941A-49EA-96A8-B10457C4D169} + {608A1D85-0999-4A9B-956A-11B91A1CB065} = {D3C17830-A461-4BA5-A672-461B19B33936} + {9A46374D-029C-4B87-88E3-0BE5D1D44C35} = {D35087BF-941A-49EA-96A8-B10457C4D169} + {D7029339-3794-4275-893A-E705D872BFBE} = {52F7988F-452D-46C2-A144-D85E0CF371C6} + {2C6CE3D7-D2DF-49F7-B3AF-5767E1C3705A} = {52F7988F-452D-46C2-A144-D85E0CF371C6} + {BCCDD838-8368-418D-82B5-5389FDDF0ADD} = {52F7988F-452D-46C2-A144-D85E0CF371C6} + {8860810C-5E46-4E60-8924-C6C77BBC6EB2} = {D35087BF-941A-49EA-96A8-B10457C4D169} + {9F0BFA49-8711-4143-B813-ACD0D0B003FD} = {52F7988F-452D-46C2-A144-D85E0CF371C6} + {56B15268-DEBC-4E9F-8554-03737A39D71B} = {52F7988F-452D-46C2-A144-D85E0CF371C6} + {2D3523BB-CF07-47D0-82F4-6B5621A8C1D7} = {D35087BF-941A-49EA-96A8-B10457C4D169} + {04174210-4A5D-4F26-883E-B52B7B167EAC} = {D35087BF-941A-49EA-96A8-B10457C4D169} + {310733D1-2BF4-44D7-9947-93E29FA44310} = {D35087BF-941A-49EA-96A8-B10457C4D169} + {C45058FC-A657-4D13-AF2A-6E0B30C444A6} = {D35087BF-941A-49EA-96A8-B10457C4D169} + {1F03D5F0-238E-48B0-BAE4-88B1E60B69C6} = {D35087BF-941A-49EA-96A8-B10457C4D169} + {187E9876-28EC-43D7-B70E-2C09E66D59BD} = {D35087BF-941A-49EA-96A8-B10457C4D169} + {7FD67842-0549-4062-ACAB-3C417BD218C1} = {52F7988F-452D-46C2-A144-D85E0CF371C6} + {26297E78-023D-4F3C-4F39-A7CC4FA9AF21} = {D35087BF-941A-49EA-96A8-B10457C4D169} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {9397EEB2-E3A3-4748-A106-3DE17F40E698} + EndGlobalSection +EndGlobal diff --git a/src/NEventStore.Example/AggregateMemento.cs b/src/NEventStore.Example/AggregateMemento.cs new file mode 100644 index 000000000..208491aa3 --- /dev/null +++ b/src/NEventStore.Example/AggregateMemento.cs @@ -0,0 +1,12 @@ +namespace NEventStore.Example +{ + internal class AggregateMemento + { + public string? Value { get; set; } + + public override string? ToString() + { + return Value; + } + } +} \ No newline at end of file diff --git a/src/NEventStore.Example/AuthorizationPipelineHook.cs b/src/NEventStore.Example/AuthorizationPipelineHook.cs new file mode 100644 index 000000000..6563205ba --- /dev/null +++ b/src/NEventStore.Example/AuthorizationPipelineHook.cs @@ -0,0 +1,22 @@ +namespace NEventStore.Example +{ + public class AuthorizationPipelineHook : PipelineHookBase + { + public override ICommit? SelectCommit(ICommit committed) + { + // return null if the user isn't authorized to see this commit + return committed; + } + + public override void PostCommit(ICommit committed) + { + // anything to do after the commit has been persisted. + } + + public override bool PreCommit(CommitAttempt attempt) + { + // Can easily do logging or other such activities here + return true; // true == allow commit to continue, false = stop. + } + } +} \ No newline at end of file diff --git a/src/NEventStore.Example/MainProgram.cs b/src/NEventStore.Example/MainProgram.cs new file mode 100644 index 000000000..1197b095a --- /dev/null +++ b/src/NEventStore.Example/MainProgram.cs @@ -0,0 +1,157 @@ +using Microsoft.Extensions.Logging; + +namespace NEventStore.Example +{ + internal static class MainProgram + { + private static readonly Guid StreamId = Guid.NewGuid(); // aggregate identifier + + private static IStoreEvents? store; + + private static void Main() + { + // Console.WindowWidth = Console.LargestWindowWidth - 20; + + Console.WriteLine("------------------"); + Console.WriteLine("Using Sync Methods"); + Console.WriteLine("------------------"); + Console.WriteLine(); + + using (store = WireupEventStore()) + { + OpenOrCreateStream(); + AppendToStream(); + TakeSnapshot(); + LoadFromSnapshotForwardAndAppend(); + } + + Console.WriteLine(); + Console.WriteLine("-------------------"); + Console.WriteLine("Using Async Methods"); + Console.WriteLine("-------------------"); + Console.WriteLine(); + + Task.Run(async () => + { + using (store = WireupEventStore()) + { + await OpenOrCreateStreamAsync(); + await AppendToStreamAsync(); + await TakeSnapshotAsync(); + await LoadFromSnapshotForwardAndAppendAsync(); + } + }).GetAwaiter().GetResult(); + + Console.WriteLine("Press any key to continue..."); + Console.ReadKey(); + } + + private static IStoreEvents WireupEventStore() + { + var loggerFactory = LoggerFactory.Create(logging => + { + logging + .AddConsole() + .AddDebug() + .SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace); + }); + + return Wireup.Init() + .WithLoggerFactory(loggerFactory) + .UseOptimisticPipelineHook() + .UsingInMemoryPersistence() + .InitializeStorageEngine() +#if NET462 + .TrackPerformanceInstance("example") +#endif + .HookIntoPipelineUsing(new AuthorizationPipelineHook()) + .Build(); + } + + #region Sync Methods + + private static void OpenOrCreateStream() + { + // we can call CreateStream(StreamId) if we know there isn't going to be any data. + // or we can call OpenStream(StreamId, 0, int.MaxValue) to read all commits, + // if no commits exist then it creates a new stream for us. + using var stream = store!.OpenStream(StreamId, 0, int.MaxValue); + var @event = new SomeDomainEvent { Value = "Initial event." }; + + stream.Add(new EventMessage { Body = @event }); + stream.CommitChanges(Guid.NewGuid()); + } + + private static void AppendToStream() + { + using var stream = store!.OpenStream(StreamId); + var @event = new SomeDomainEvent { Value = "Second event." }; + + stream.Add(new EventMessage { Body = @event }); + stream.CommitChanges(Guid.NewGuid()); + } + + private static void TakeSnapshot() + { + var memento = new AggregateMemento { Value = "snapshot" }; + store!.Advanced.AddSnapshot(new Snapshot(StreamId.ToString(), 2, memento)); + } + + private static void LoadFromSnapshotForwardAndAppend() + { + var latestSnapshot = store!.Advanced.GetSnapshot(StreamId, int.MaxValue) + ?? throw new InvalidOperationException("No snapshot found."); + + using var stream = store.OpenStream(latestSnapshot, int.MaxValue); + var @event = new SomeDomainEvent { Value = "Third event (first one after a snapshot)." }; + + stream.Add(new EventMessage { Body = @event }); + stream.CommitChanges(Guid.NewGuid()); + } + + #endregion + + #region Async Methods + + private static async Task OpenOrCreateStreamAsync() + { + // we can call CreateStream(StreamId) if we know there isn't going to be any data. + // or we can call OpenStream(StreamId, 0, int.MaxValue) to read all commits, + // if no commits exist then it creates a new stream for us. + using var stream = await store!.OpenStreamAsync(StreamId, 0, int.MaxValue, cancellationToken: CancellationToken.None); + var @event = new SomeDomainEvent { Value = "Initial event." }; + + stream.Add(new EventMessage { Body = @event }); + await stream.CommitChangesAsync(Guid.NewGuid(), CancellationToken.None); + } + + private static async Task AppendToStreamAsync() + { + using var stream = await store!.OpenStreamAsync(StreamId, cancellationToken: CancellationToken.None); + var @event = new SomeDomainEvent { Value = "Second event." }; + + stream.Add(new EventMessage { Body = @event }); + await stream.CommitChangesAsync(Guid.NewGuid(), CancellationToken.None); + } + + private static Task TakeSnapshotAsync() + { + var memento = new AggregateMemento { Value = "snapshot" }; + return store!.Advanced.AddSnapshotAsync(new Snapshot(StreamId.ToString(), 2, memento), CancellationToken.None); + } + + private static async Task LoadFromSnapshotForwardAndAppendAsync() + { + var latestSnapshot = await store!.Advanced.GetSnapshotAsync(StreamId, int.MaxValue, CancellationToken.None) + ?? throw new InvalidOperationException("No snapshot found."); + + using var stream = await store.OpenStreamAsync(latestSnapshot, int.MaxValue, CancellationToken.None); + var @event = new SomeDomainEvent { Value = "Third event (first one after a snapshot)." }; + + stream.Add(new EventMessage { Body = @event }); + await stream.CommitChangesAsync(Guid.NewGuid(), CancellationToken.None); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/NEventStore.Example/NEventStore.Example.csproj b/src/NEventStore.Example/NEventStore.Example.csproj new file mode 100644 index 000000000..5873988cc --- /dev/null +++ b/src/NEventStore.Example/NEventStore.Example.csproj @@ -0,0 +1,31 @@ + + + + net8.0;net472 + false + + exe + + Exe + + + + TRACE;DEBUG + + + + + + + + + + + + + + + + + + diff --git a/doc/EventStore.Example/Properties/AssemblyInfo.cs b/src/NEventStore.Example/Properties/ProjectAssemblyInfo.cs similarity index 76% rename from doc/EventStore.Example/Properties/AssemblyInfo.cs rename to src/NEventStore.Example/Properties/ProjectAssemblyInfo.cs index adf4d7f30..33cbc2e74 100644 --- a/doc/EventStore.Example/Properties/AssemblyInfo.cs +++ b/src/NEventStore.Example/Properties/ProjectAssemblyInfo.cs @@ -1,6 +1,6 @@ using System.Reflection; using System.Runtime.InteropServices; -[assembly: AssemblyTitle("EventStore.Example")] +[assembly: AssemblyTitle("NEventStore.Example")] [assembly: AssemblyDescription("")] [assembly: Guid("6fd621e0-9047-4449-b136-249383936d5c")] \ No newline at end of file diff --git a/src/NEventStore.Example/SomeDomainEvent.cs b/src/NEventStore.Example/SomeDomainEvent.cs new file mode 100644 index 000000000..b83e751f9 --- /dev/null +++ b/src/NEventStore.Example/SomeDomainEvent.cs @@ -0,0 +1,7 @@ +namespace NEventStore.Example +{ + internal class SomeDomainEvent + { + public string? Value { get; set; } + } +} \ No newline at end of file diff --git a/src/NEventStore.Persistence.AcceptanceTests/BDD/MSTest/SpecificationBase.cs b/src/NEventStore.Persistence.AcceptanceTests/BDD/MSTest/SpecificationBase.cs new file mode 100644 index 000000000..43f014b67 --- /dev/null +++ b/src/NEventStore.Persistence.AcceptanceTests/BDD/MSTest/SpecificationBase.cs @@ -0,0 +1,108 @@ +#if MSTEST + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace NEventStore.Persistence.AcceptanceTests.BDD +{ + /// + /// + /// base class for BDD testing in a Given-When-Then style + /// using MSTest + /// + /// in MSTest each test will be executed by a new instance of the test class. + /// + /// this will be used to implement a class that will test a single + /// action or behavior and multiple result conditions + /// + /// + /// - a class will represent a single scenario + /// - the class name will describe the scenario name + /// + /// + [TestClass] + public abstract class SpecificationBase + { + Exception? testFixtureSetupException = null; + + /// + /// + /// there's a problem with error / exception reporting in here, test fixture setup is not well suited for + /// exception handling + /// workaround: + /// http://stackoverflow.com/questions/1411676/how-to-diagnose-testfixturesetup-failed + /// + /// + /// a good idea on how to catch and test for exceptions: + /// http://www.planetgeek.ch/2015/06/22/machine-specifications-the-alternative-nunit/ + /// + /// + /// maybe catch the generated exception with something like: Catch.Exception() shown here and save it to a local variable + /// in the when() function, then test for the exception in the 'then' tests + /// + /// + [TestInitialize] // I cannot have something like a OnTimeTestInitialize in MsTest, ClassInitialize requires static methods + public async Task SetUp() + { + try + { + Context(); + await ContextAsync(); + Because(); + await BecauseAsync(); + } + catch (Exception ex) + { + testFixtureSetupException = ex; + } + } + + [TestInitialize] + // NUnit doesn't support very useful logging of failures from a OneTimeSetUp method. We'll do the logging here. + public void CheckForTestFixtureFailure() + { + if (testFixtureSetupException != null) + { + string msg = string.Format("There was a failure during Context() or Because() phases.\n\rException: {0}\n\rStackTrace: {1}", + testFixtureSetupException.Message, testFixtureSetupException.StackTrace); + Assert.Fail(msg); + } + } + + protected virtual void Context() { } + protected virtual Task ContextAsync() { return Task.CompletedTask; } + protected virtual void Because() { } + protected virtual Task BecauseAsync() { return Task.CompletedTask; } + + [ClassCleanup] + protected virtual void Cleanup() { } + } + + /// + /// Attribute used to identify the tests + /// + /// for custom actions: + /// http://nunit.org/index.php?p=actionAttributes&r=2.6.3 + /// + /// + [AttributeUsage(AttributeTargets.Method)] + public class ThenAttribute : LogTestMethod; + + [AttributeUsage(AttributeTargets.Method)] + public class FactAttribute : LogTestMethod; + + public class LogTestMethod : TestMethodAttribute + { + public override TestResult[] Execute(ITestMethod testMethod) + { + Console.WriteLine("Scenario: {0}", testMethod.TestClassName); + + var result = base.Execute(testMethod); + + Console.WriteLine(" - {0} - {1}", testMethod.TestMethodName, result[0].Outcome == UnitTestOutcome.Passed); + + return result; + } + } +} + +#endif \ No newline at end of file diff --git a/src/NEventStore.Persistence.AcceptanceTests/BDD/NUnit/SpecificationBase.cs b/src/NEventStore.Persistence.AcceptanceTests/BDD/NUnit/SpecificationBase.cs new file mode 100644 index 000000000..7f75e85e6 --- /dev/null +++ b/src/NEventStore.Persistence.AcceptanceTests/BDD/NUnit/SpecificationBase.cs @@ -0,0 +1,145 @@ +#if NUNIT + +using NUnit.Framework; +using NUnit.Framework.Interfaces; + +namespace NEventStore.Persistence.AcceptanceTests.BDD +{ + /// + /// + /// base class for BDD testing in a Given-When-Then style + /// using NUnit + /// + /// + /// this will be used to implement a class that will test a single + /// action or behavior and multiple result conditions: + /// - a class will represent a single scenario + /// - the class name will describe the scenario name + /// + /// + [TestFixture] + [LogSuiteAttribute] + [LogTestAttribute] + public abstract class SpecificationBase + { + private Exception? testFixtureSetupException = null; + + /// + /// + /// there's a problem with error / exception reporting in here, test fixture setup is not well suited for + /// exception handling + /// workaround: + /// http://stackoverflow.com/questions/1411676/how-to-diagnose-testfixturesetup-failed + /// + /// + /// a good idea on how to catch and test for exceptions: + /// http://www.planetgeek.ch/2015/06/22/machine-specifications-the-alternative-nunit/ + /// + /// + /// maybe catch the generated exception with something like: Catch.Exception() shown here and save it to a local variable + /// in the when() function, then test for the exception in the 'then' tests + /// + /// + [OneTimeSetUp] + public async Task SetUp() + { + try + { + Context(); + await ContextAsync(); + Because(); + await BecauseAsync(); + } + catch (Exception ex) + { + testFixtureSetupException = ex; + } + } + + [SetUp] + // NUnit doesn't support very useful logging of failures from a OneTimeSetUp method. We'll do the logging here. + public void CheckForTestFixtureFailure() + { + if (testFixtureSetupException != null) + { + string msg = string.Format("There was a failure during Context() or Because() phases.\n\rException: {0}\n\rStackTrace: {1}", + testFixtureSetupException.Message, testFixtureSetupException.StackTrace); + Assert.Fail(msg); + } + } + + protected virtual void Context() { } + protected virtual Task ContextAsync() { return Task.CompletedTask; } + protected virtual void Because() { } + protected virtual Task BecauseAsync() { return Task.CompletedTask; } + + [OneTimeTearDown] + protected virtual void Cleanup() { } + } + + /// + /// + /// Attribute used to identify the tests + /// + /// + /// for custom actions: + /// http://nunit.org/index.php?p=actionAttributes&r=2.6.3 + /// + /// + [AttributeUsage(AttributeTargets.Method)] + public class ThenAttribute : TestAttribute; + + [AttributeUsage(AttributeTargets.Method)] + public class FactAttribute : TestAttribute; + + [AttributeUsageAttribute(AttributeTargets.Class)] + public class LogSuiteAttribute : Attribute, ITestAction + { + public void AfterTest(ITest test) + { + // Method intentionally left empty. + } + + public void BeforeTest(ITest test) + { + Console.WriteLine("Scenario: {0}", test?.Fixture?.GetType().Name); + } + + public ActionTargets Targets + { + get { return ActionTargets.Suite; } + } + } + + /// + /// + /// Attribute used to identify the tests + /// and describe them + /// + /// + /// for custom actions: + /// http://nunit.org/index.php?p=actionAttributes&r=2.6.3 + /// http://nunit.org/index.php?p=testContext&r=2.6.3 + /// + /// + [AttributeUsageAttribute(AttributeTargets.Class, AllowMultiple = false)] + public class LogTestAttribute : Attribute, ITestAction + { + public void AfterTest(ITest test) + { + Console.WriteLine(" - {0} - {1}", test?.Method?.Name, TestContext.CurrentContext.Result.Outcome.Status); + } + + public void BeforeTest(ITest test) + { + Console.WriteLine(test?.Fixture?.GetType().Name); + } + + public ActionTargets Targets + { + get { return ActionTargets.Test; } + } + } +} + +#endif \ No newline at end of file diff --git a/src/NEventStore.Persistence.AcceptanceTests/BDD/SpecificationBase.cs b/src/NEventStore.Persistence.AcceptanceTests/BDD/SpecificationBase.cs new file mode 100644 index 000000000..58832237c --- /dev/null +++ b/src/NEventStore.Persistence.AcceptanceTests/BDD/SpecificationBase.cs @@ -0,0 +1,34 @@ +#if XUNIT + +namespace NEventStore.Persistence.AcceptanceTests.BDD +{ + + using Xunit; + + + [RunWith(typeof (SpecificationBaseRunner))] + public abstract class SpecificationBase + { + protected virtual void Because() + {} + + protected virtual void Cleanup() + {} + + protected virtual void Context() + {} + + public void OnFinish() + { + Cleanup(); + } + + public void OnStart() + { + Context(); + Because(); + } + } +} + +#endif \ No newline at end of file diff --git a/src/NEventStore.Persistence.AcceptanceTests/BDD/SpecificationBaseRunner.cs b/src/NEventStore.Persistence.AcceptanceTests/BDD/SpecificationBaseRunner.cs new file mode 100644 index 000000000..a46340a13 --- /dev/null +++ b/src/NEventStore.Persistence.AcceptanceTests/BDD/SpecificationBaseRunner.cs @@ -0,0 +1,168 @@ +#if XUNIT + +namespace NEventStore.Persistence.AcceptanceTests.BDD +{ + using System; + using System.Collections.Generic; + using System.Reflection; + using Xunit; + using Xunit.Sdk; + + internal class SpecificationBaseRunner : ITestClassCommand + { + private readonly List _fixtures = new List(); + private SpecificationBase _objectUnderTest; + + public SpecificationBase ObjectUnderTest + { + get + { + if (_objectUnderTest == null) + { + GuardTypeUnderTest(); + _objectUnderTest = (SpecificationBase) Activator.CreateInstance(TypeUnderTest.Type); + } + + return _objectUnderTest; + } + } + + object ITestClassCommand.ObjectUnderTest + { + get { return ObjectUnderTest; } + } + + public ITypeInfo TypeUnderTest { get; set; } + + public int ChooseNextTest(ICollection testsLeftToRun) + { + return 0; + } + + public Exception ClassStart() + { + try + { + SetupFixtures(); + ObjectUnderTest.OnStart(); + return null; + } + catch (Exception ex) + { + return ex; + } + } + + public Exception ClassFinish() + { + try + { + ObjectUnderTest.OnFinish(); + + foreach (var fixtureData in _fixtures) + { + var disposable = fixtureData as IDisposable; + if (disposable != null) + { + disposable.Dispose(); + } + } + + return null; + } + catch (Exception ex) + { + return ex; + } + } + + public IEnumerable EnumerateTestCommands(IMethodInfo testMethod) + { + string displayName = (TypeUnderTest.Type.Name + ", it " + testMethod.Name).Replace('_', ' '); + return new[] {new SpecTestCommand(testMethod, displayName)}; + } + + public IEnumerable EnumerateTestMethods() + { + GuardTypeUnderTest(); + + return TypeUtility.GetTestMethods(TypeUnderTest); + } + + public bool IsTestMethod(IMethodInfo testMethod) + { + return MethodUtility.IsTest(testMethod); + } + + private void SetupFixtures() + { + try + { + foreach (var @interface in TypeUnderTest.Type.GetInterfaces()) + { + if (@interface.IsGenericType) + { + Type genericDefinition = @interface.GetGenericTypeDefinition(); + + if (genericDefinition == typeof (IUseFixture<>)) + { + Type dataType = @interface.GetGenericArguments()[0]; + if (dataType == TypeUnderTest.Type) + { + throw new InvalidOperationException("Cannot use a test class as its own fixture data"); + } + + object fixtureData = null; + + fixtureData = Activator.CreateInstance(dataType); + + MethodInfo method = @interface.GetMethod("SetFixture", new[] {dataType}); + _fixtures.Add(fixtureData); + method.Invoke(ObjectUnderTest, new[] {fixtureData}); + } + } + } + } + catch (TargetInvocationException ex) + { + ExceptionUtility.RethrowWithNoStackTraceLoss(ex.InnerException); + } + } + + private void GuardTypeUnderTest() + { + if (TypeUnderTest == null) + { + throw new InvalidOperationException("Forgot to set TypeUnderTest before calling ObjectUnderTest"); + } + + if (!typeof (SpecificationBase).IsAssignableFrom(TypeUnderTest.Type)) + { + throw new InvalidOperationException("SpecificationBaseRunner can only be used with types that derive from SpecificationBase"); + } + } + + private class SpecTestCommand : TestCommand + { + public SpecTestCommand(IMethodInfo testMethod, string displayName) : base(testMethod, displayName, 0) + {} + + public override MethodResult Execute(object testClass) + { + try + { + testMethod.Invoke(testClass, null); + } + catch (ParameterCountMismatchException) + { + throw new InvalidOperationException("Observation " + TypeName + "." + MethodName + " cannot have parameters"); + } + + return new PassedResult(testMethod, DisplayName); + } + } + } + +} + +#endif \ No newline at end of file diff --git a/src/NEventStore.Persistence.AcceptanceTests/Catch.cs b/src/NEventStore.Persistence.AcceptanceTests/Catch.cs new file mode 100644 index 000000000..a99a08b1d --- /dev/null +++ b/src/NEventStore.Persistence.AcceptanceTests/Catch.cs @@ -0,0 +1,33 @@ +namespace NEventStore.Persistence.AcceptanceTests +{ + public static class Catch + { + public static Exception? Exception(Action action) + { + try + { + action(); + } + catch (Exception ex) + { + return ex; + } + + return null; + } + + public static async Task ExceptionAsync(Func action) + { + try + { + await action(); + } + catch (Exception ex) + { + return ex; + } + + return null; + } + } +} \ No newline at end of file diff --git a/src/NEventStore.Persistence.AcceptanceTests/ConfigurationExtensions.cs b/src/NEventStore.Persistence.AcceptanceTests/ConfigurationExtensions.cs new file mode 100644 index 000000000..bc734f738 --- /dev/null +++ b/src/NEventStore.Persistence.AcceptanceTests/ConfigurationExtensions.cs @@ -0,0 +1,24 @@ +namespace NEventStore.Persistence.AcceptanceTests +{ + using System; + using System.Configuration; + using System.Linq; + + public static class ConfigurationExtensions + { + public static string GetSetting(this string settingName) + { + return GetCommandLineArgument("/" + settingName + ":") ?? + Environment.GetEnvironmentVariable(settingName) ?? ConfigurationManager.AppSettings[settingName]; + } + + private static string GetCommandLineArgument(string settingName) + { + return + Environment.GetCommandLineArgs() + .Where(arg => arg.StartsWith(settingName)) + .Select(arg => arg.Replace(settingName, string.Empty)) + .FirstOrDefault(); + } + } +} \ No newline at end of file diff --git a/src/NEventStore.Persistence.AcceptanceTests/ExtensionMethods.cs b/src/NEventStore.Persistence.AcceptanceTests/ExtensionMethods.cs new file mode 100644 index 000000000..435b434b1 --- /dev/null +++ b/src/NEventStore.Persistence.AcceptanceTests/ExtensionMethods.cs @@ -0,0 +1,186 @@ +namespace NEventStore.Persistence.AcceptanceTests +{ + public static class ExtensionMethods + { + public static HashSet ToHashSet(this IEnumerable collection) + { + return new HashSet(collection); + } + + public static LinkedList ToLinkedList(this IEnumerable collection) + { + return new LinkedList(collection); + } + + public static ICommit? CommitSingle(this IPersistStreams persistence, string? streamId = null) + { + CommitAttempt commitAttempt = (streamId ?? Guid.NewGuid().ToString()).BuildAttempt(); + return persistence.Commit(commitAttempt); + } + + public static Task CommitSingleAsync(this IPersistStreams persistence, string? streamId = null) + { + CommitAttempt commitAttempt = (streamId ?? Guid.NewGuid().ToString()).BuildAttempt(); + return persistence.CommitAsync(commitAttempt, CancellationToken.None); + } + + public static ICommit? CommitNext(this IPersistStreams persistence, ICommit previous) + { + var nextAttempt = previous.BuildNextAttempt(); + return persistence.Commit(nextAttempt); + } + + public static Task CommitNextAsync(this IPersistStreams persistence, ICommit previous) + { + var nextAttempt = previous.BuildNextAttempt(); + return persistence.CommitAsync(nextAttempt, CancellationToken.None); + } + + public static ICommit? CommitNext(this IPersistStreams persistence, CommitAttempt previous) + { + var nextAttempt = previous.BuildNextAttempt(); + return persistence.Commit(nextAttempt); + } + + public static Task CommitNextAsync(this IPersistStreams persistence, CommitAttempt previous) + { + var nextAttempt = previous.BuildNextAttempt(); + return persistence.CommitAsync(nextAttempt, CancellationToken.None); + } + + public static IEnumerable CommitMany(this IPersistStreams persistence, int numberOfCommits, string? streamId = null, string? bucketId = null) + { + var commits = new List(); + CommitAttempt? attempt = null; + + for (int i = 0; i < numberOfCommits; i++) + { + attempt = attempt == null ? (streamId ?? Guid.NewGuid().ToString()).BuildAttempt(null, bucketId) : attempt.BuildNextAttempt(); + persistence.Commit(attempt); + commits.Add(attempt); + } + + return commits; + } + + public static async Task> CommitManyAsync(this IPersistStreams persistence, int numberOfCommits, string? streamId = null, string? bucketId = null) + { + var commits = new List(); + CommitAttempt? attempt = null; + for (int i = 0; i < numberOfCommits; i++) + { + attempt = attempt == null ? (streamId ?? Guid.NewGuid().ToString()).BuildAttempt(null, bucketId) : attempt.BuildNextAttempt(); + await persistence.CommitAsync(attempt, CancellationToken.None).ConfigureAwait(false); + commits.Add(attempt); + } + return commits; + } + + public static CommitAttempt BuildAttempt(this string streamId, DateTime? now = null, string? bucketId = null) + { + now ??= SystemTime.UtcNow; + bucketId ??= Bucket.Default; + + var messages = new EventMessage[] + { + new() {Body = new SomeDomainEvent {SomeProperty = "Test"}}, + new() {Body = new SomeDomainEvent {SomeProperty = "Test2"}}, + }; + + return new CommitAttempt( + bucketId: bucketId, + streamId: streamId, + streamRevision: 2, + commitId: Guid.NewGuid(), + commitSequence: 1, + commitStamp: now.Value, + headers: new Dictionary { { "A header", "A string value" }, { "Another header", 2 } }, + events: messages + ); + } + + public static CommitAttempt BuildNextAttempt(this ICommit commit) + { + var messages = new EventMessage[] + { + new() {Body = new SomeDomainEvent {SomeProperty = "Another test"}}, + new() {Body = new SomeDomainEvent {SomeProperty = "Another test2"}}, + }; + + return new CommitAttempt(commit.BucketId, + commit.StreamId, + commit.StreamRevision + messages.Length, + Guid.NewGuid(), + commit.CommitSequence + 1, + commit.CommitStamp.AddSeconds(1), + new Dictionary(), + messages); + } + + public static CommitAttempt BuildNextAttempt(this CommitAttempt commit) + { + var messages = new EventMessage[] + { + new() {Body = new SomeDomainEvent {SomeProperty = "Another test"}}, + new() {Body = new SomeDomainEvent {SomeProperty = "Another test2"}}, + }; + + return new CommitAttempt(commit.BucketId, + commit.StreamId, + commit.StreamRevision + 2, + Guid.NewGuid(), + commit.CommitSequence + 1, + commit.CommitStamp.AddSeconds(1), + new Dictionary(), + messages); + } + + public static SimpleMessage Populate(this SimpleMessage message) + { + message ??= new SimpleMessage(); + + return new SimpleMessage + { + Id = Guid.NewGuid(), + Count = 1234, + Created = new DateTime(2000, 2, 3, 4, 5, 6, 7).ToUniversalTime(), + Value = message.Value + "Hello, World!", + Contents = { "a", null, string.Empty, "d" } + }; + } + + public static CommitAttempt BuildCommit(this string streamId) + { + const int streamRevision = 2; + const int commitSequence = 2; + Guid commitId = Guid.NewGuid(); + var headers = new Dictionary { { "Key", "Value" }, { "Key2", (long)1234 }, { "Key3", null! } }; + var events = new[] + { + new EventMessage + { + Headers = {{"MsgKey1", TimeSpan.MinValue}, {"MsgKey2", Guid.NewGuid()}, {"MsgKey3", 1.1M}, {"MsgKey4", (ushort) 1}}, + Body = "some value" + }, + new EventMessage + { + Headers = {{"MsgKey1", new Uri("http://www.google.com/")}, {"MsgKey4", "some header"}}, + Body = new[] {"message body"} + } + }; + + return new CommitAttempt(streamId, streamRevision, commitId, commitSequence, SystemTime.UtcNow, headers, events); + } + + [Serializable] + public class SomeDomainEvent + { + public string? SomeProperty { get; set; } + + public override string? ToString() + { + return SomeProperty; + } + } + } +} \ No newline at end of file diff --git a/src/NEventStore.Persistence.AcceptanceTests/NEventStore.Persistence.AcceptanceTests.Core.csproj b/src/NEventStore.Persistence.AcceptanceTests/NEventStore.Persistence.AcceptanceTests.Core.csproj new file mode 100644 index 000000000..4c8eedd06 --- /dev/null +++ b/src/NEventStore.Persistence.AcceptanceTests/NEventStore.Persistence.AcceptanceTests.Core.csproj @@ -0,0 +1,30 @@ + + + net8.0;net472 + false + NEventStore.Persistence.AcceptanceTests + false + NEventStore.Persistence.AcceptanceTests + + + TRACE;DEBUG;NUNIT + + + NUNIT + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/NEventStore.Persistence.AcceptanceTests/PersistenceTests.Async.cs b/src/NEventStore.Persistence.AcceptanceTests/PersistenceTests.Async.cs new file mode 100644 index 000000000..1feab90ee --- /dev/null +++ b/src/NEventStore.Persistence.AcceptanceTests/PersistenceTests.Async.cs @@ -0,0 +1,1631 @@ +#pragma warning disable 169 // ReSharper disable InconsistentNaming +#pragma warning disable IDE1006 // Naming Styles + +using NEventStore.Persistence.AcceptanceTests.BDD; +using FluentAssertions; +#if MSTEST +using Microsoft.VisualStudio.TestTools.UnitTesting; +#endif +#if NUNIT +using NUnit.Framework; +#endif +#if XUNIT +using Xunit; +using Xunit.Should; +#endif + +namespace NEventStore.Persistence.AcceptanceTests.Async +{ +#if MSTEST + [TestClass] +#endif + public class when_a_commit_header_has_a_name_that_contains_a_period : PersistenceEngineConcernAsync + { + private ICommit? _persisted; + private string? _streamId; + + protected override Task ContextAsync() + { + _streamId = Guid.NewGuid().ToString(); + var attempt = new CommitAttempt(_streamId, + 2, + Guid.NewGuid(), + 1, + DateTime.UtcNow, + new Dictionary { { "key.1", "value" } }, + [new EventMessage { Body = new ExtensionMethods.SomeDomainEvent { SomeProperty = "Test" } }]); + return Persistence.CommitAsync(attempt, CancellationToken.None); + } + + protected override async Task BecauseAsync() + { + var observer = new CommitStreamObserver(); + await Persistence.GetFromAsync(_streamId!, 0, int.MaxValue, observer, CancellationToken.None); + _persisted = observer.Commits[0]; + } + + [Fact] + public void should_correctly_deserialize_headers() + { + _persisted.Should().NotBeNull(); + _persisted!.Headers.Keys.Should().Contain("key.1"); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_a_commit_is_successfully_persisted : PersistenceEngineConcernAsync + { + private CommitAttempt? _attempt; + private DateTime _now; + private ICommit? _persisted; + private string? _streamId; + + protected override Task ContextAsync() + { + _now = SystemTime.UtcNow.AddYears(1); + _streamId = Guid.NewGuid().ToString(); + _attempt = _streamId.BuildAttempt(_now); + + return Persistence.CommitAsync(_attempt, CancellationToken.None); + } + + protected override async Task BecauseAsync() + { + var observer = new CommitStreamObserver(); + await Persistence.GetFromAsync(_streamId!, 0, int.MaxValue, observer, CancellationToken.None); + _persisted = observer.Commits[0]; + } + + [Fact] + public void should_correctly_persist_the_stream_identifier() + { + _persisted!.StreamId.Should().Be(_attempt!.StreamId); + } + + [Fact] + public void should_correctly_persist_the_stream_stream_revision() + { + _persisted!.StreamRevision.Should().Be(_attempt!.StreamRevision); + } + + [Fact] + public void should_correctly_persist_the_commit_identifier() + { + _persisted!.CommitId.Should().Be(_attempt!.CommitId); + } + + [Fact] + public void should_correctly_persist_the_commit_sequence() + { + _persisted!.CommitSequence.Should().Be(_attempt!.CommitSequence); + } + + // persistence engines have varying levels of precision with respect to time. + [Fact] + public void should_correctly_persist_the_commit_stamp() + { + var difference = _persisted!.CommitStamp.Subtract(_now); + difference.Days.Should().Be(0); + difference.Hours.Should().Be(0); + difference.Minutes.Should().Be(0); + difference.Should().BeLessOrEqualTo(TimeSpan.FromSeconds(1)); + } + + [Fact] + public void should_correctly_persist_the_headers() + { + _persisted!.Headers.Count.Should().Be(_attempt!.Headers.Count); + } + + [Fact] + public void should_correctly_persist_the_events() + { + _persisted!.Events.Count.Should().Be(_attempt!.Events.Count); + } + + [Fact] + public async Task should_cause_the_stream_to_be_found_in_the_list_of_streams_to_snapshot() + { + var observer = new StreamHeadObserver(); + await Persistence.GetStreamsToSnapshotAsync(1, observer, CancellationToken.None); + observer.StreamHeads + .FirstOrDefault(x => x.StreamId == _streamId).Should().NotBeNull(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_reading_from_a_given_revision : PersistenceEngineConcernAsync + { + private const int LoadFromCommitContainingRevision = 3; + private const int UpToCommitWithContainingRevision = 5; + private ICommit[]? _committed; + private ICommit? _oldest, _oldest2, _oldest3; + private string? _streamId; + + protected override async Task ContextAsync() + { + _oldest = await Persistence.CommitSingleAsync(); // 2 events, revision 1-2 + _oldest2 = await Persistence.CommitNextAsync(_oldest!); // 2 events, revision 3-4 + _oldest3 = await Persistence.CommitNextAsync(_oldest2!); // 2 events, revision 5-6 + await Persistence.CommitNextAsync(_oldest3!); // 2 events, revision 7-8 + + _streamId = _oldest!.StreamId; + } + + protected override async Task BecauseAsync() + { + var observer = new CommitStreamObserver(); + await Persistence.GetFromAsync(_streamId!, LoadFromCommitContainingRevision, UpToCommitWithContainingRevision, observer, CancellationToken.None); + _committed = observer.Commits.ToArray(); + } + + [Fact] + public void should_start_from_the_commit_which_contains_the_min_stream_revision_specified() + { + _committed![0].CommitId.Should().Be(_oldest2!.CommitId); // contains revision 3 + } + + [Fact] + public void should_read_up_to_the_commit_which_contains_the_max_stream_revision_specified() + { + _committed!.Last().CommitId.Should().Be(_oldest3!.CommitId); // contains revision 5 + } + } + +#if MSTEST + [TestClass] +#endif + public class when_reading_from_a_given_revision_to_commit_revision : PersistenceEngineConcernAsync + { + private const int LoadFromCommitContainingRevision = 3; + private const int UpToCommitWithContainingRevision = 6; + private ICommit[]? _committed; + private ICommit? _oldest, _oldest2, _oldest3; + private string? _streamId; + + protected override async Task ContextAsync() + { + _oldest = await Persistence.CommitSingleAsync(); // 2 events, revision 1-2 + _oldest2 = await Persistence.CommitNextAsync(_oldest!); // 2 events, revision 3-4 + _oldest3 = await Persistence.CommitNextAsync(_oldest2!); // 2 events, revision 5-6 + await Persistence.CommitNextAsync(_oldest3!); // 2 events, revision 7-8 + + _streamId = _oldest!.StreamId; + } + + protected override async Task BecauseAsync() + { + var observer = new CommitStreamObserver(); + await Persistence.GetFromAsync(_streamId!, LoadFromCommitContainingRevision, UpToCommitWithContainingRevision, observer, CancellationToken.None); + _committed = observer.Commits.ToArray(); + } + + [Fact] + public void should_start_from_the_commit_which_contains_the_min_stream_revision_specified() + { + _committed![0].CommitId.Should().Be(_oldest2!.CommitId); // contains revision 3 + } + + [Fact] + public void should_read_up_to_the_commit_which_contains_the_max_stream_revision_specified() + { + _committed!.Last().CommitId.Should().Be(_oldest3!.CommitId); // contains revision 6 + } + } + + public class when_observer_stops_reading_the_stream_after_2_commits : PersistenceEngineConcernAsync + { + private ICommit? _oldest, _oldest2, _oldest3; + private string? _streamId; + + protected override async Task ContextAsync() + { + _oldest = await Persistence.CommitSingleAsync(); // 2 events, revision 1-2 + _oldest2 = await Persistence.CommitNextAsync(_oldest!); // 2 events, revision 3-4 + _oldest3 = await Persistence.CommitNextAsync(_oldest2!); // 2 events, revision 5-6 + await Persistence.CommitNextAsync(_oldest3!); // 2 events, revision 7-8 + + _streamId = _oldest!.StreamId; + } + + [Fact] + public async Task ICommitEvents_GetFromAsync_stops_after_2_commits() + { + bool _observerCompleted = false; + var _committed = new List(); + var observer = new LambdaAsyncObserver( + onNextAsync: (c, _) => + { + if (_committed.Count <= 1) + { + _committed.Add(c); + return Task.FromResult(true); + } + // do not read more than 2 commits + return Task.FromResult(false); + }, + onCompletedAsync: (_) => + { + _observerCompleted = true; + return Task.CompletedTask; + }); + await Persistence.GetFromAsync(Bucket.Default, _streamId!, 0, int.MaxValue, observer, CancellationToken.None); + + _committed!.Count.Should().Be(2); + _observerCompleted.Should().BeTrue(); + } + + [Fact] + public async Task IPersistStreams_GetFromAsync_Checkpoint_stops_after_2_commits() + { + bool _observerCompleted = false; + var _committed = new List(); + var observer = new LambdaAsyncObserver( + onNextAsync: (c, _) => + { + if (_committed.Count <= 1) + { + _committed.Add(c); + return Task.FromResult(true); + } + // do not read more than 2 commits + return Task.FromResult(false); + }, + onCompletedAsync: (_) => + { + _observerCompleted = true; + return Task.CompletedTask; + }); + await Persistence.GetFromAsync(0, observer, CancellationToken.None); + + _committed!.Count.Should().Be(2); + _observerCompleted.Should().BeTrue(); + } + + [Fact] + public async Task IPersistStreams_GetFromToAsync_Checkpoint_stops_after_2_commits() + { + bool _observerCompleted = false; + var _committed = new List(); + var observer = new LambdaAsyncObserver( + onNextAsync: (c, _) => + { + if (_committed.Count <= 1) + { + _committed.Add(c); + return Task.FromResult(true); + } + // do not read more than 2 commits + return Task.FromResult(false); + }, + onCompletedAsync: (_) => + { + _observerCompleted = true; + return Task.CompletedTask; + }); + await Persistence.GetFromToAsync(0, long.MaxValue, observer, CancellationToken.None); + + _committed!.Count.Should().Be(2); + _observerCompleted.Should().BeTrue(); + } + + [Fact] + public async Task IPersistStreams_GetFromAsync_Bucket_Checkpoint_stops_after_2_commits() + { + bool _observerCompleted = false; + var _committed = new List(); + var observer = new LambdaAsyncObserver( + onNextAsync: (c, _) => + { + if (_committed.Count <= 1) + { + _committed.Add(c); + return Task.FromResult(true); + } + // do not read more than 2 commits + return Task.FromResult(false); + }, + onCompletedAsync: (_) => + { + _observerCompleted = true; + return Task.CompletedTask; + }); + await Persistence.GetFromAsync(Bucket.Default, 0, observer, CancellationToken.None); + + _committed!.Count.Should().Be(2); + _observerCompleted.Should().BeTrue(); + } + + [Fact] + public async Task IPersistStreams_GetFromToAsync_Bucket_Checkpoint_stops_after_2_commits() + { + bool _observerCompleted = false; + var _committed = new List(); + var observer = new LambdaAsyncObserver( + onNextAsync: (c, _) => + { + if (_committed.Count <= 1) + { + _committed.Add(c); + return Task.FromResult(true); + } + // do not read more than 2 commits + return Task.FromResult(false); + }, + onCompletedAsync: (_) => + { + _observerCompleted = true; + return Task.CompletedTask; + }); + await Persistence.GetFromToAsync(Bucket.Default, 0, long.MaxValue, observer, CancellationToken.None); + + _committed!.Count.Should().Be(2); + _observerCompleted.Should().BeTrue(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_committing_a_stream_with_the_same_revision : PersistenceEngineConcernAsync + { + private CommitAttempt? _attemptWithSameRevision; + private Exception? _thrown; + + protected override async Task ContextAsync() + { + var commit = await Persistence.CommitSingleAsync(); + _attemptWithSameRevision = commit!.StreamId.BuildAttempt(); + } + + protected override async Task BecauseAsync() + { + _thrown = await Catch.ExceptionAsync(() => Persistence.CommitAsync(_attemptWithSameRevision!, CancellationToken.None)); + } + + [Fact] + public void should_throw_a_ConcurrencyException() + { + _thrown.Should().BeOfType(); + } + } + + // This test ensure the uniqueness of BucketId+StreamId+CommitSequence + // to avoid concurrency issues +#if MSTEST + [TestClass] +#endif + public class when_committing_a_stream_with_the_same_sequence : PersistenceEngineConcernAsync + { + private CommitAttempt? _attempt1, _attempt2; + private Exception? _thrown; + + protected override Task ContextAsync() + { + string streamId = Guid.NewGuid().ToString(); + _attempt1 = streamId.BuildAttempt(); + _attempt2 = new CommitAttempt( + _attempt1.BucketId, // <--- Same bucket + _attempt1.StreamId, // <--- Same stream it + _attempt1.StreamRevision + 10, + Guid.NewGuid(), + _attempt1.CommitSequence, // <--- Same commit seq + DateTime.UtcNow, + _attempt1.Headers, + [ + new EventMessage(){ Body = new ExtensionMethods.SomeDomainEvent {SomeProperty = "Test 3"}} + ] + ); + + return Persistence.CommitAsync(_attempt1, CancellationToken.None); + } + + protected override async Task BecauseAsync() + { + _thrown = await Catch.ExceptionAsync(() => Persistence.CommitAsync(_attempt2!, CancellationToken.None)); + } + + [Fact] + public void should_throw_a_ConcurrencyException() + { + _thrown.Should().BeOfType(); + } + } + + //TODO:This test looks exactly like the one above. What are we trying to prove? +#if MSTEST + [TestClass] +#endif + public class when_attempting_to_overwrite_a_committed_sequence : PersistenceEngineConcernAsync + { + private CommitAttempt? _failedAttempt; + private Exception? _thrown; + + protected override async Task ContextAsync() + { + string streamId = Guid.NewGuid().ToString(); + CommitAttempt successfulAttempt = streamId.BuildAttempt(); + await Persistence.CommitAsync(successfulAttempt, CancellationToken.None); + _failedAttempt = streamId.BuildAttempt(); + } + + protected override async Task BecauseAsync() + { + _thrown = await Catch.ExceptionAsync(() => Persistence.CommitAsync(_failedAttempt!, CancellationToken.None)); + } + + [Fact] + public void should_throw_a_ConcurrencyException() + { + _thrown.Should().BeOfType(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_attempting_to_persist_a_commit_twice : PersistenceEngineConcernAsync + { + private CommitAttempt? _attemptTwice; + private Exception? _thrown; + + protected override async Task ContextAsync() + { + var commit = await Persistence.CommitSingleAsync(); + _attemptTwice = new CommitAttempt( + commit!.BucketId, + commit.StreamId, + commit.StreamRevision, + commit.CommitId, + commit.CommitSequence, + commit.CommitStamp, + commit.Headers.ToDictionary(k => k.Key, v => v.Value), + commit.Events.ToArray()); + } + + protected override async Task BecauseAsync() + { + _thrown = await Catch.ExceptionAsync(() => Persistence.CommitAsync(_attemptTwice!, CancellationToken.None)); + } + + [Fact] + public void should_throw_a_DuplicateCommitException() + { + _thrown.Should().BeOfType(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_attempting_to_persist_a_commitId_twice_on_same_stream : PersistenceEngineConcernAsync + { + private CommitAttempt? _attemptTwice; + private Exception? _thrown; + + protected override async Task ContextAsync() + { + var commit = await Persistence.CommitSingleAsync(); + _attemptTwice = new CommitAttempt( + commit!.BucketId, + commit.StreamId, + commit.StreamRevision + 1, + commit.CommitId, + commit.CommitSequence + 1, + commit.CommitStamp, + commit.Headers.ToDictionary(k => k.Key, v => v.Value), + commit.Events.ToArray() + ); + } + + protected override async Task BecauseAsync() + { + _thrown = await Catch.ExceptionAsync(() => Persistence.CommitAsync(_attemptTwice!, CancellationToken.None)); + } + + [Fact] + public void should_throw_a_DuplicateCommitException() + { + _thrown.Should().BeOfType(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_committing_more_events_than_the_configured_page_size : PersistenceEngineConcernAsync + { + private CommitAttempt[]? _committed; + private ICommit[]? _loaded; + private string? _streamId; + + protected override async Task ContextAsync() + { + _streamId = Guid.NewGuid().ToString(); + _committed = (await Persistence.CommitManyAsync(ConfiguredPageSizeForTesting + 2, _streamId)).ToArray(); + } + + protected override async Task BecauseAsync() + { + var observer = new CommitStreamObserver(); + await Persistence.GetFromAsync(_streamId!, 0, int.MaxValue, observer, CancellationToken.None); + _loaded = observer.Commits.ToArray(); + } + + [Fact] + public void should_load_the_same_number_of_commits_which_have_been_persisted() + { + _loaded!.Length.Should().Be(_committed!.Length); + } + + [Fact] + public void should_load_the_same_commits_which_have_been_persisted() + { + _committed! + .All(commit => _loaded!.SingleOrDefault(loaded => loaded.CommitId == commit.CommitId) != null) + .Should().BeTrue(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_saving_a_snapshot : PersistenceEngineConcernAsync + { + private bool _added; + private Snapshot? _snapshot; + private string? _streamId; + + protected override Task ContextAsync() + { + _streamId = Guid.NewGuid().ToString(); + _snapshot = new Snapshot(_streamId, 1, "Snapshot"); + return Persistence.CommitSingleAsync(_streamId); + } + + protected override async Task BecauseAsync() + { + _added = await Persistence.AddSnapshotAsync(_snapshot!, CancellationToken.None); + } + + [Fact] + public void should_indicate_the_snapshot_was_added() + { + _added.Should().BeTrue(); + } + + [Fact] + public async Task should_be_able_to_retrieve_the_snapshot() + { + (await Persistence.GetSnapshotAsync(_streamId!, _snapshot!.StreamRevision, CancellationToken.None)).Should().NotBeNull(); + } + } + + /// + /// having multiple snapshots for the same tuple: bucketId, streamId, streamRevision + /// should not be allowed, the resulting behavior should be ignoring or updating the + /// snapshot, that was the original design (it's up to the driver decide what to do) + /// this behavior can be changed in a future implementation. + /// +#if MSTEST + [TestClass] +#endif + public class when_adding_multiple_snapshots_for_same_bucketId_streamId_streamRevision : PersistenceEngineConcernAsync + { + private bool _added; + private Snapshot? _snapshot; + private Snapshot? _updatedSnapshot; + private string? _streamId; + + private Exception? _thrown; + + protected override async Task ContextAsync() + { + _streamId = Guid.NewGuid().ToString(); + _snapshot = new Snapshot(_streamId, 1, "Snapshot"); + await Persistence.CommitSingleAsync(_streamId); + + await Persistence.AddSnapshotAsync(_snapshot, CancellationToken.None); + } + + protected override async Task BecauseAsync() + { + _updatedSnapshot = new Snapshot(_streamId!, 1, "Updated Snapshot"); + _thrown = await Catch.ExceptionAsync(async () => _added = await Persistence.AddSnapshotAsync(_updatedSnapshot, CancellationToken.None)); + } + + [Fact] + public void should_not_raise_exception() + { + _thrown.Should().BeNull(); + } + + [Fact] + public async Task should_be_able_to_retrieve_the_correct_snapshot_original_or_updated_depends_on_driver_implementation() + { + var snapshot = await Persistence.GetSnapshotAsync(_streamId!, _snapshot!.StreamRevision, CancellationToken.None); + snapshot.Should().NotBeNull(); + if (_added) + { + snapshot!.Payload.Should().Be(_updatedSnapshot!.Payload, "The snapshot was added, I expected to get the most updated version"); + } + else + { + snapshot!.Payload.Should().Be(_snapshot.Payload, "The snapshot was not added, I expected to get the original version"); + } + } + } + +#if MSTEST + [TestClass] +#endif + public class when_retrieving_a_snapshot : PersistenceEngineConcernAsync + { + private Snapshot? _correct; + private ISnapshot? _snapshot; + private string? _streamId; + private Snapshot? _tooFarForward; + + protected override async Task ContextAsync() + { + _streamId = Guid.NewGuid().ToString(); + var commit1 = await Persistence.CommitSingleAsync(_streamId); // rev 1-2 + var commit2 = await Persistence.CommitNextAsync(commit1!); // rev 3-4 + await Persistence.CommitNextAsync(commit2!); // rev 5-6 + + await Persistence.AddSnapshotAsync(new Snapshot(_streamId, 1, string.Empty), CancellationToken.None); //Too far back + _correct = new Snapshot(_streamId, 3, "Snapshot"); + await Persistence.AddSnapshotAsync(_correct, CancellationToken.None); + _tooFarForward = new Snapshot(_streamId, 5, string.Empty); + await Persistence.AddSnapshotAsync(_tooFarForward, CancellationToken.None); + } + + protected override async Task BecauseAsync() + { + _snapshot = await Persistence.GetSnapshotAsync(_streamId!, _tooFarForward!.StreamRevision - 1, CancellationToken.None); + } + + [Fact] + public void should_load_the_most_recent_prior_snapshot() + { + _snapshot!.StreamRevision.Should().Be(_correct!.StreamRevision); + } + + [Fact] + public void should_have_the_correct_snapshot_payload() + { + _snapshot!.Payload.Should().Be(_correct!.Payload); + } + + [Fact] + public void should_have_the_correct_stream_id() + { + _snapshot!.StreamId.Should().Be(_correct!.StreamId); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_a_snapshot_has_been_added_to_the_most_recent_commit_of_a_stream : PersistenceEngineConcernAsync + { + private const string SnapshotData = "snapshot"; + private ICommit? _newest; + private ICommit? _oldest, _oldest2; + private string? _streamId; + + protected override async Task ContextAsync() + { + _streamId = Guid.NewGuid().ToString(); + _oldest = await Persistence.CommitSingleAsync(_streamId); + _oldest2 = await Persistence.CommitNextAsync(_oldest!); + _newest = await Persistence.CommitNextAsync(_oldest2!); + } + + protected override Task BecauseAsync() + { + return Persistence.AddSnapshotAsync(new Snapshot(_streamId!, _newest!.StreamRevision, SnapshotData), CancellationToken.None); + } + + [Fact] + public async Task should_no_longer_find_the_stream_in_the_set_of_streams_to_be_snapshot() + { + var observer = new StreamHeadObserver(); + await Persistence.GetStreamsToSnapshotAsync(1, observer, CancellationToken.None); + observer.StreamHeads + .Any(x => x.StreamId == _streamId).Should().BeFalse(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_adding_a_commit_after_a_snapshot : PersistenceEngineConcernAsync + { + private const int WithinThreshold = 2; + private const int OverThreshold = 3; + private const string SnapshotData = "snapshot"; + private ICommit? _oldest, _oldest2; + private string? _streamId; + + protected override async Task ContextAsync() + { + _streamId = Guid.NewGuid().ToString(); + _oldest = await Persistence.CommitSingleAsync(_streamId); + _oldest2 = await Persistence.CommitNextAsync(_oldest!); + await Persistence.AddSnapshotAsync(new Snapshot(_streamId, _oldest2!.StreamRevision, SnapshotData), CancellationToken.None); + } + + protected override Task BecauseAsync() + { + return Persistence.CommitAsync(_oldest2!.BuildNextAttempt(), CancellationToken.None); + } + + // Because Raven and Mongo update the stream head asynchronously, this test will occasionally fail + [Fact] + public async Task should_find_the_stream_in_the_set_of_streams_to_be_snapshot_when_within_the_threshold() + { + var observer = new StreamHeadObserver(); + await Persistence.GetStreamsToSnapshotAsync(WithinThreshold, observer, CancellationToken.None); + observer.StreamHeads + .FirstOrDefault(x => x.StreamId == _streamId).Should().NotBeNull(); + } + + [Fact] + public async Task should_not_find_the_stream_in_the_set_of_streams_to_be_snapshot_when_over_the_threshold() + { + var observer = new StreamHeadObserver(); + await Persistence.GetStreamsToSnapshotAsync(OverThreshold, observer, CancellationToken.None); + observer.StreamHeads + .Any(x => x.StreamId == _streamId).Should().BeFalse(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_reading_all_commits_from_a_particular_point_in_time : PersistenceEngineConcernAsync + { + private ICommit[]? _committed; + private CommitAttempt? _first; + private DateTime _now; + private ICommit? _second; + private string? _streamId; + private ICommit? _third; + + protected override async Task ContextAsync() + { + _streamId = Guid.NewGuid().ToString(); + + _now = SystemTime.UtcNow.AddYears(1); + _first = _streamId.BuildAttempt(_now.AddSeconds(1)); + await Persistence.CommitAsync(_first, CancellationToken.None); + + _second = await Persistence.CommitNextAsync(_first); + _third = await Persistence.CommitNextAsync(_second!); + await Persistence.CommitNextAsync(_third!); + } + + protected override void Because() + { + _committed = Persistence.GetFrom(Bucket.Default, _now).ToArray(); + } + + [Fact] + public void should_return_all_commits_on_or_after_the_point_in_time_specified() + { + _committed!.Length.Should().Be(4); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_paging_over_all_commits_from_a_particular_point_in_time : PersistenceEngineConcernAsync + { + private CommitAttempt[]? _committed; + private ICommit[]? _loaded; + private DateTime _start; + + protected override async Task ContextAsync() + { + _start = SystemTime.UtcNow; + // Due to loss in precision in various storage engines, we're rounding down to the + // nearest second to ensure include all commits from the 'start'. + _start = _start.AddSeconds(-1); + _committed = (await Persistence.CommitManyAsync(ConfiguredPageSizeForTesting + 2)).ToArray(); + } + + protected override void Because() + { + _loaded = Persistence.GetFrom(Bucket.Default, _start).ToArray(); + } + + [Fact] + public void should_load_the_same_number_of_commits_which_have_been_persisted() + { + _loaded!.Length.Should().Be(_committed!.Length); + } + + [Fact] + public void should_load_the_same_commits_which_have_been_persisted() + { + _committed! + .All(commit => _loaded!.SingleOrDefault(loaded => loaded.CommitId == commit.CommitId) != null) + .Should().BeTrue(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_paging_over_all_commits_from_a_particular_checkpoint : PersistenceEngineConcernAsync + { + private List? _committed; + private List? _loaded; + private const int checkPoint = 2; + + protected override async Task ContextAsync() + { + _committed = (await Persistence.CommitManyAsync(ConfiguredPageSizeForTesting + 1)).Select(c => c.CommitId).ToList(); + } + + protected override async Task BecauseAsync() + { + var observer = new CommitStreamObserver(); + await Persistence.GetFromAsync(checkPoint, observer, CancellationToken.None).ConfigureAwait(false); + _loaded = observer.Commits.Select(c => c.CommitId).ToList(); + } + + [Fact] + public void should_load_the_same_number_of_commits_which_have_been_persisted_starting_from_the_checkpoint() + { + _loaded!.Count.Should().Be(_committed!.Count - checkPoint); + } + + [Fact] + public void should_load_only_the_commits_starting_from_the_checkpoint() + { + _committed!.Skip(checkPoint).All(x => _loaded!.Contains(x)).Should().BeTrue(); // all commits should be found in loaded collection + } + } + +#if MSTEST + [TestClass] +#endif + public class when_paging_over_all_commits_of_a_bucket_from_a_particular_checkpoint : PersistenceEngineConcernAsync + { + private List? _committedOnBucket1; + private List? _committedOnBucket2; + private List? _loaded; + private const int checkPoint = 2; + + protected override async Task ContextAsync() + { + _committedOnBucket1 = (await Persistence.CommitManyAsync(ConfiguredPageSizeForTesting + 1, null, "b1")).Select(c => c.CommitId).ToList(); + _committedOnBucket2 = (await Persistence.CommitManyAsync(ConfiguredPageSizeForTesting + 1, null, "b2")).Select(c => c.CommitId).ToList(); + _committedOnBucket1.AddRange((await Persistence.CommitManyAsync(4, null, "b1")).Select(c => c.CommitId)); + } + + protected override async Task BecauseAsync() + { + var observer = new CommitStreamObserver(); + await Persistence.GetFromAsync("b1", checkPoint, observer, CancellationToken.None).ConfigureAwait(false); + _loaded = observer.Commits.Select(c => c.CommitId).ToList(); + } + + [Fact] + public void should_load_the_same_number_of_commits_which_have_been_persisted_starting_from_the_checkpoint() + { + _loaded!.Count.Should().Be(_committedOnBucket1!.Count - checkPoint); + } + + [Fact] + public void should_load_only_the_commits_on_bucket1_starting_from_the_checkpoint() + { + _committedOnBucket1!.Skip(checkPoint).All(x => _loaded!.Contains(x)).Should().BeTrue(); // all commits should be found in loaded collection + } + + [Fact] + public void should_not_load_the_commits_from_bucket2() + { + _committedOnBucket2!.All(x => !_loaded!.Contains(x)).Should().BeTrue(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_paging_over_all_commits_from_a_particular_checkpoint_to_a_checkpoint : PersistenceEngineConcernAsync + { + private readonly List _committed = []; + private List? _loaded; + private const int startCheckpoint = 2; + private int endCheckpoint; + + protected override async Task ContextAsync() + { + var committedOnBucket1 = (await Persistence.CommitManyAsync(ConfiguredPageSizeForTesting + 1, null, Bucket.Default)).Select(c => c.CommitId).ToList(); + var committedOnBucket2 = (await Persistence.CommitManyAsync(ConfiguredPageSizeForTesting + 1, null, "Bucket1")).Select(c => c.CommitId).ToList(); + _committed.AddRange(committedOnBucket1); + _committed.AddRange(committedOnBucket2); + endCheckpoint = (2 * (ConfiguredPageSizeForTesting + 1)) - 1; + } + + protected override async Task BecauseAsync() + { + var observer = new CommitStreamObserver(); + await Persistence.GetFromToAsync(startCheckpoint, endCheckpoint, observer, CancellationToken.None).ConfigureAwait(false); + _loaded = observer.Commits.Select(c => c.CommitId).ToList(); + } + + [Fact] + public void should_load_the_same_number_of_commits_which_have_been_persisted_starting_from_the_checkpoint_to_the_checkpoint() + { + _loaded!.Count.Should().Be(endCheckpoint - startCheckpoint); + } + + [Fact] + public void should_load_only_the_commits_starting_from_the_checkpoint_to_the_checkpoint() + { + _committed + .Skip(startCheckpoint) + .Take(_committed.Count - startCheckpoint - 1) + //.Take(endCheckpoint - startCheckpoint) + .All(x => _loaded!.Contains(x)).Should().BeTrue(); // all commits should be found in loaded collection + } + } + +#if MSTEST + [TestClass] +#endif + public class when_paging_over_all_commits_of_a_bucket_from_a_particular_checkpoint_to_a_checkpoint : PersistenceEngineConcernAsync + { + private List? _committedOnBucket1; + private List? _committedOnBucket2; + private List? _loaded; + private const int startCheckpoint = 2; + private int endCheckpoint; + + protected override async Task ContextAsync() + { + _committedOnBucket1 = (await Persistence.CommitManyAsync(ConfiguredPageSizeForTesting + 1, null, "b1")).Select(c => c.CommitId).ToList(); + _committedOnBucket2 = (await Persistence.CommitManyAsync(ConfiguredPageSizeForTesting + 1, null, "b2")).Select(c => c.CommitId).ToList(); + _committedOnBucket1.AddRange((await Persistence.CommitManyAsync(4, null, "b1")).Select(c => c.CommitId)); + endCheckpoint = ((2 * (ConfiguredPageSizeForTesting + 1)) + 4) - 1; + } + + protected override async Task BecauseAsync() + { + var observer = new CommitStreamObserver(); + await Persistence.GetFromToAsync("b1", startCheckpoint, endCheckpoint, observer, CancellationToken.None).ConfigureAwait(false); + _loaded = observer.Commits.Select(c => c.CommitId).ToList(); + } + + [Fact] + public void should_load_the_same_number_of_commits_which_have_been_persisted_starting_from_the_checkpoint_to_the_checkpoint() + { + _loaded!.Count.Should().Be(_committedOnBucket1!.Count - startCheckpoint - 1); + } + + [Fact] + public void should_load_only_the_commits_on_bucket1_starting_from_the_checkpoint_to_the_checkpoint() + { + _committedOnBucket1! + .Skip(startCheckpoint) + .Take(_committedOnBucket1!.Count - startCheckpoint - 1) + .All(x => _loaded!.Contains(x)).Should().BeTrue(); // all commits should be found in loaded collection + } + + [Fact] + public void should_not_load_the_commits_from_bucket2() + { + _committedOnBucket2!.All(x => !_loaded!.Contains(x)).Should().BeTrue(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_reading_all_commits_from_the_year_1_AD : PersistenceEngineConcernAsync + { + private Exception? _thrown; + + protected override async Task BecauseAsync() + { + // ReSharper disable once ReturnValueOfPureMethodIsNotUsed + _thrown = await Catch.ExceptionAsync(() => Persistence.GetFromAsync(Bucket.Default, 0, new CommitStreamObserver(), CancellationToken.None)); + } + + [Fact] + public void should_NOT_throw_an_exception() + { + _thrown.Should().BeNull(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_purging_all_commits : PersistenceEngineConcernAsync + { + protected override Task ContextAsync() + { + return Persistence.CommitSingleAsync(); + } + + protected override Task BecauseAsync() + { + return Persistence.PurgeAsync(CancellationToken.None); + } + + [Fact] + public async Task should_not_find_any_commits_stored() + { + var observer = new CommitStreamObserver(); + await Persistence.GetFromAsync(Bucket.Default, 0, observer, CancellationToken.None).ConfigureAwait(false); + observer.Commits.Count.Should().Be(0); + } + + [Fact] + public async Task should_not_find_any_streams_to_snapshot() + { + var observer = new StreamHeadObserver(); + await Persistence.GetStreamsToSnapshotAsync(0, observer, CancellationToken.None); + observer.StreamHeads + .Count.Should().Be(0); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_invoking_after_disposal : PersistenceEngineConcernAsync + { + private Exception? _thrown; + + protected override void Context() + { + Persistence.Dispose(); + } + + protected override async Task BecauseAsync() + { + _thrown = await Catch.ExceptionAsync(() => Persistence.CommitSingleAsync()); + } + + [Fact] + public void should_throw_an_ObjectDisposedException() + { + _thrown.Should().BeOfType(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_committing_a_stream_with_the_same_id_as_a_stream_same_bucket : PersistenceEngineConcernAsync + { + private string? _streamId; + private static Exception? _thrown; + + protected override Task ContextAsync() + { + _streamId = Guid.NewGuid().ToString(); + return Persistence.CommitAsync(_streamId.BuildAttempt(), CancellationToken.None); + } + + protected override async Task BecauseAsync() + { + _thrown = await Catch.ExceptionAsync(() => Persistence.CommitAsync(_streamId!.BuildAttempt(), CancellationToken.None)); + } + + [Fact] + public void should_throw() + { + _thrown.Should().NotBeNull(); + } + + [Fact] + public void should_be_duplicate_commit_exception() + { + _thrown.Should().BeOfType(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_committing_a_stream_with_the_same_id_as_a_stream_in_another_bucket : PersistenceEngineConcernAsync + { + private const string _bucketAId = "a"; + private const string _bucketBId = "b"; + private string? _streamId; + private static CommitAttempt? _attemptForBucketB; + private static Exception? _thrown; + private DateTime _attemptACommitStamp; + + protected override async Task ContextAsync() + { + _streamId = Guid.NewGuid().ToString(); + DateTime now = SystemTime.UtcNow; + await Persistence.CommitAsync(_streamId.BuildAttempt(now, _bucketAId), CancellationToken.None); + + var observer = new CommitStreamObserver(); + await Persistence.GetFromAsync(_bucketAId, _streamId, 0, int.MaxValue, observer, CancellationToken.None); + _attemptACommitStamp = observer.Commits[0].CommitStamp; + + _attemptForBucketB = _streamId.BuildAttempt(now.Subtract(TimeSpan.FromDays(1)), _bucketBId); + } + + protected override async Task BecauseAsync() + { + _thrown = await Catch.ExceptionAsync(() => Persistence.CommitAsync(_attemptForBucketB!, CancellationToken.None)); + } + + [Fact] + public void should_succeed() + { + _thrown.Should().BeNull(); + } + + [Fact] + public async Task should_persist_to_the_correct_bucket() + { + var observer = new CommitStreamObserver(); + await Persistence.GetFromAsync(_bucketBId, _streamId!, 0, int.MaxValue, observer, CancellationToken.None); + var stream = observer.Commits; + + stream.Should().NotBeNull(); + stream.Count.Should().Be(1); + } + + [Fact] + public async Task should_not_affect_the_stream_from_the_other_bucket() + { + var observer = new CommitStreamObserver(); + await Persistence.GetFromAsync(_bucketAId, _streamId!, 0, int.MaxValue, observer, CancellationToken.None); + var stream = observer.Commits; + + stream.Should().NotBeNull(); + stream.Count.Should().Be(1); + stream[0].CommitStamp.Should().Be(_attemptACommitStamp); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_saving_a_snapshot_for_a_stream_with_the_same_id_as_a_stream_in_another_bucket : PersistenceEngineConcernAsync + { + private const string _bucketAId = "a"; + private const string _bucketBId = "b"; + + private string? _streamId; + + private static Snapshot? _snapshot; + + protected override async Task ContextAsync() + { + _streamId = Guid.NewGuid().ToString(); + _snapshot = new Snapshot(_bucketBId, _streamId, 1, "Snapshot"); + await Persistence.CommitAsync(_streamId.BuildAttempt(bucketId: _bucketAId), CancellationToken.None); + await Persistence.CommitAsync(_streamId.BuildAttempt(bucketId: _bucketBId), CancellationToken.None); + } + + protected override Task BecauseAsync() + { + return Persistence.AddSnapshotAsync(_snapshot!, CancellationToken.None); + } + + [Fact] + public async Task should_affect_snapshots_from_another_bucket() + { + (await Persistence.GetSnapshotAsync(_bucketAId, _streamId!, _snapshot!.StreamRevision, CancellationToken.None)).Should().BeNull(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_reading_all_commits_from_a_particular_point_in_time_and_there_are_streams_in_multiple_buckets : PersistenceEngineConcernAsync + { + private const string _bucketAId = "a"; + private const string _bucketBId = "b"; + + private static DateTime _now; + private static ICommit[]? _returnedCommits; + private CommitAttempt? _commitToBucketB; + + protected override async Task ContextAsync() + { + _now = SystemTime.UtcNow.AddYears(1); + + var commitToBucketA = Guid.NewGuid().ToString().BuildAttempt(_now.AddSeconds(1), _bucketAId); + + await Persistence.CommitAsync(commitToBucketA, CancellationToken.None); + commitToBucketA = commitToBucketA.BuildNextAttempt(); + await Persistence.CommitAsync(commitToBucketA, CancellationToken.None); + commitToBucketA = commitToBucketA.BuildNextAttempt(); + await Persistence.CommitAsync(commitToBucketA, CancellationToken.None); + await Persistence.CommitAsync(commitToBucketA.BuildNextAttempt(), CancellationToken.None); + + _commitToBucketB = Guid.NewGuid().ToString().BuildAttempt(_now.AddSeconds(1), _bucketBId); + + await Persistence.CommitAsync(_commitToBucketB, CancellationToken.None); + } + + protected override void Because() + { + _returnedCommits = Persistence.GetFrom(_bucketAId, _now).ToArray(); + } + + [Fact] + public void should_not_return_commits_from_other_buckets() + { + _returnedCommits!.Any(c => c.CommitId.Equals(_commitToBucketB!.CommitId)).Should().BeFalse(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_getting_all_commits_since_checkpoint_and_there_are_streams_in_multiple_buckets : PersistenceEngineConcernAsync + { + private ICommit[]? _commits; + + protected override async Task ContextAsync() + { + const string bucketAId = "a"; + const string bucketBId = "b"; + await Persistence.CommitAsync(Guid.NewGuid().ToString().BuildAttempt(bucketId: bucketAId), CancellationToken.None); + await Persistence.CommitAsync(Guid.NewGuid().ToString().BuildAttempt(bucketId: bucketBId), CancellationToken.None); + await Persistence.CommitAsync(Guid.NewGuid().ToString().BuildAttempt(bucketId: bucketAId), CancellationToken.None); + } + + protected override async Task BecauseAsync() + { + var observer = new CommitStreamObserver(); + await Persistence.GetFromAsync(0, observer, CancellationToken.None).ConfigureAwait(false); + _commits = observer.Commits.ToArray(); + } + + [Fact] + public void should_not_be_empty() + { + _commits.Should().NotBeEmpty(); + } + + [Fact] + public void should_be_in_order_by_checkpoint() + { + Int64 checkpoint = 0; + foreach (var commit in _commits!) + { + Int64 commitCheckpoint = commit.CheckpointToken; + commitCheckpoint.Should().BeGreaterThan(checkpoint); + checkpoint = commit.CheckpointToken; + } + } + } + +#if MSTEST + [TestClass] +#endif + public class when_purging_all_commits_and_there_are_streams_in_multiple_buckets : PersistenceEngineConcernAsync + { + private const string _bucketAId = "a"; + private const string _bucketBId = "b"; + + private string? _streamId; + + protected override async Task ContextAsync() + { + _streamId = Guid.NewGuid().ToString(); + await Persistence.CommitAsync(_streamId.BuildAttempt(bucketId: _bucketAId), CancellationToken.None); + await Persistence.CommitAsync(_streamId.BuildAttempt(bucketId: _bucketBId), CancellationToken.None); + var _snapshotA = new Snapshot(bucketId: _bucketAId, _streamId, 1, "SnapshotA"); + await Persistence.AddSnapshotAsync(_snapshotA, CancellationToken.None); + var _snapshotB = new Snapshot(bucketId: _bucketBId, _streamId, 1, "SnapshotB"); + await Persistence.AddSnapshotAsync(_snapshotB, CancellationToken.None); + } + + protected override Task BecauseAsync() + { + return Persistence.PurgeAsync(CancellationToken.None); + } + + [Fact] + public async Task should_purge_all_commits_stored_in_bucket_a() + { + var observer = new CommitStreamObserver(); + await Persistence.GetFromAsync(_bucketAId, 0, observer, CancellationToken.None).ConfigureAwait(false); + observer.Commits.Count.Should().Be(0); + } + + [Fact] + public async Task should_purge_all_commits_stored_in_bucket_b() + { + var observer = new CommitStreamObserver(); + await Persistence.GetFromAsync(_bucketBId, 0, observer, CancellationToken.None).ConfigureAwait(false); + observer.Commits.Count.Should().Be(0); + } + + [Fact] + public async Task should_purge_all_streams_to_snapshot_in_bucket_a() + { + var observer = new StreamHeadObserver(); + await Persistence.GetStreamsToSnapshotAsync(_bucketAId, 0, observer, CancellationToken.None); + observer.StreamHeads.Count.Should().Be(0); + } + + [Fact] + public async Task should_purge_all_streams_to_snapshot_in_bucket_b() + { + var observer = new StreamHeadObserver(); + await Persistence.GetStreamsToSnapshotAsync(_bucketBId, 0, observer, CancellationToken.None); + observer.StreamHeads.Count.Should().Be(0); + } + } + + [Serializable] + public class TestEventForAsync + { + public String? S { get; set; } + } + +#if MSTEST + [TestClass] +#endif + public class when_calling_CommitChanges : PersistenceEngineConcern + { + private Guid? _commitId; + private ICommit? _persistedCommit; + private ICommit[]? _commits; + + protected override async Task BecauseAsync() + { + var eventStore = new OptimisticEventStore(Persistence, null, null); + using IEventStream stream = await eventStore.OpenStreamAsync(Guid.NewGuid()).ConfigureAwait(false); + stream.Add(new EventMessage { Body = new TestEvent() { S = "Hi " } }); + _commitId = Guid.NewGuid(); + _persistedCommit = await stream.CommitChangesAsync(_commitId.Value).ConfigureAwait(false); + + var observer = new CommitStreamObserver(); + await Persistence.GetFromAsync(0, observer, CancellationToken.None).ConfigureAwait(false); + _commits = observer.Commits.ToArray(); + } + + [Fact] + public void A_Commit_had_been_persisted() + { + _persistedCommit.Should().NotBeNull(); + _persistedCommit!.CommitId.Should().Be(_commitId!.Value); + _persistedCommit.CommitSequence.Should().Be(1); + _persistedCommit.Events.Count.Should().Be(1); + _persistedCommit.Events.Single().Body.Should().BeOfType(); + ((TestEvent)_persistedCommit.Events.Single().Body!).S.Should().Be("Hi "); + } + + [Fact] + public void Should_have_expected_number_of_commits() + { + _commits!.Length.Should().Be(1); + // if should have the right event + _commits[0].CommitId.Should().Be(_commitId!.Value); + _commits[0].Events.Count.Should().Be(1); + _commits[0].Events.Single().Body.Should().BeOfType(); + ((TestEvent)_commits[0].Events.Single().Body!).S.Should().Be("Hi "); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_gettingFromCheckpoint_amount_of_commits_exceeds_PageSize : PersistenceEngineConcernAsync + { + private ICommit[]? _commits; + private int _moreThanPageSize; + + protected override async Task BecauseAsync() + { + _moreThanPageSize = ConfiguredPageSizeForTesting + 1; + var eventStore = new OptimisticEventStore(Persistence, null, null); + // TODO: Not sure how to set the actual page size to the const defined above + for (int i = 0; i < _moreThanPageSize; i++) + { + using IEventStream stream = await eventStore.OpenStreamAsync(Guid.NewGuid()).ConfigureAwait(false); + stream.Add(new EventMessage { Body = new TestEventForAsync() { S = "Hi " + i } }); + await stream.CommitChangesAsync(Guid.NewGuid(), CancellationToken.None).ConfigureAwait(false); + } + + var observer = new CommitStreamObserver(); + await Persistence.GetFromAsync(0, observer, CancellationToken.None).ConfigureAwait(false); + _commits = observer.Commits.ToArray(); + } + + [Fact] + public void Should_have_expected_number_of_commits() + { + _commits!.Length.Should().Be(_moreThanPageSize); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_a_payload_is_large : PersistenceEngineConcernAsync + { + [Fact] + public async Task can_commit() + { + const int bodyLength = 100000; + var attempt = new CommitAttempt( + Bucket.Default, + Guid.NewGuid().ToString(), + 1, + Guid.NewGuid(), + 1, + DateTime.UtcNow, + new Dictionary(), + [new EventMessage { Body = new string('a', bodyLength) }]); + await Persistence.CommitAsync(attempt, CancellationToken.None); + + var observer = new CommitStreamObserver(); + await Persistence.GetFromAsync(0, observer, CancellationToken.None).ConfigureAwait(false); + ICommit commits = observer.Commits.Single(); + + commits.Events.Single().Body.ToString()!.Length.Should().Be(bodyLength); + } + } + + /// + /// We are adapting the tests to use 3 different frameworks: + /// - XUnit: the attached test runner does the job (fixture setup and cleanup) + /// - NUnit (.net core project) + /// - MSTest (.net core project) + /// + public abstract class PersistenceEngineConcernAsync : SpecificationBase +#if XUNIT + , IUseFixture +#endif +#if NUNIT || MSTEST + , IDisposable +#endif + { + protected PersistenceEngineFixtureAsync? Fixture { get; private set; } + + protected IPersistStreams Persistence + { + get { return Fixture!.Persistence!; } + } + + protected int ConfiguredPageSizeForTesting + { + get { return 2; } + } + + /// + /// Can be used by XUNIT to initialize the tests. + /// + public void SetFixture(PersistenceEngineFixtureAsync data) + { + Fixture = data; + Fixture.Initialize(ConfiguredPageSizeForTesting); + } + +#if NUNIT || MSTEST + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + Fixture?.Dispose(); + } + + /// + /// + /// This code was meant to be run right before every test in the fixture to give time + /// to do further initialization before the PersistenceEngineFixture was created. + /// Unfortunately the 3 frameworks + /// have very different ways of doing this: + /// - NUnit: TestFixtureSetUp + /// - MSTest: ClassInitialize (not inherited, will be ignored if defined on a base class) + /// - xUnit: IUseFixture + SetFixture + /// We need a way to also have some configuration before the PersistenceEngineFixture is created. + /// + /// + /// We've decided to use the test constructor to do the job, it's your responsibility to guarantee + /// One time initialization (for anything that need it, if you have multiple tests on a fixture) + /// depending on the framework you are using. + /// + /// + /// quick workaround: + /// - change the parameters of the "Fixture" properties. + /// - call the 'Reinitialize()' method can be called to rerun the initialization after we changed the configuration + /// in the constructor. + /// or + /// - call the SetFixture() to reinitialize everything. + /// + /// + protected PersistenceEngineConcernAsync() : this(new PersistenceEngineFixtureAsync()) + { } + + protected PersistenceEngineConcernAsync(PersistenceEngineFixtureAsync fixture) + { + SetFixture(fixture); + } +#endif + } + + public partial class PersistenceEngineFixtureAsync : IDisposable + { + public IPersistStreams? Persistence { get; private set; } + + private readonly Func _createPersistence; + +#if NET462 + private bool _tracking = false; + private string _trackingInstanceName; + + /// + /// Automatic Performance Counters and tracking was disabled for full + /// framework tests because their initialization + /// can fail when the tests run on build machines (like AppVeyor and similar). + /// You can enable it back calling this function before + /// + /// + /// + public PersistenceEngineFixture TrackPerformanceInstance(string instanceName = "tests") + { + _trackingInstanceName = instanceName; + _tracking = true; + return this; + } +#endif + + public void Initialize(int pageSize) + { +#if NET462 + // performance counters cab be disabled for full framework tests because their initialization + // can fail when the tests run on build machines (like AppVeyor and similar) + if (_tracking) + { + Persistence = new NEventStore.Diagnostics.PerformanceCounterPersistenceEngine(_createPersistence(pageSize), _trackingInstanceName); + } + else + { + Persistence = _createPersistence(pageSize); + } +#else + Persistence = _createPersistence(pageSize); +#endif + Persistence.Initialize(); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (Persistence?.IsDisposed == false) + { + Persistence.Drop(); + Persistence.Dispose(); + } + } + } +} + +#pragma warning restore 169 // ReSharper disable InconsistentNaming +#pragma warning restore IDE1006 // Naming Styles diff --git a/src/NEventStore.Persistence.AcceptanceTests/PersistenceTests.Transactions.cs b/src/NEventStore.Persistence.AcceptanceTests/PersistenceTests.Transactions.cs new file mode 100644 index 000000000..2884ab731 --- /dev/null +++ b/src/NEventStore.Persistence.AcceptanceTests/PersistenceTests.Transactions.cs @@ -0,0 +1,161 @@ +#pragma warning disable 169 // ReSharper disable InconsistentNaming +#pragma warning disable IDE1006 // Naming Styles +#pragma warning disable S101 // Types should be named in PascalCase + +namespace NEventStore.Persistence.AcceptanceTests +{ + using System; + using System.Collections.Generic; + using System.Linq; + using NEventStore.Persistence.AcceptanceTests.BDD; + using FluentAssertions; +#if MSTEST + using Microsoft.VisualStudio.TestTools.UnitTesting; +#endif +#if NUNIT + using NUnit.Framework; + using System.Threading.Tasks; + using System.Transactions; + using System.Threading; + using System.Globalization; +#endif +#if XUNIT + using Xunit; + using Xunit.Should; +#endif + + /* Transactions support must be investigated, it should be valid only for Databases that supports it (InMemoryPersistence will not). */ +#if MSTEST + [TestClass] +#endif + public class TransactionConcern : PersistenceEngineConcern + { + private ICommit[] _commits; + private const int Loop = 2; + private const int StreamsPerTransaction = 20; + + protected override void Because() + { + Parallel.For(0, Loop, i => + { + var eventStore = new OptimisticEventStore(Persistence, null); + using (var scope = new TransactionScope(TransactionScopeOption.Required, + new TransactionOptions { IsolationLevel = IsolationLevel.Serializable })) + { + int j; + for (j = 0; j < StreamsPerTransaction; j++) + { + using (var stream = eventStore.OpenStream(i.ToString() + "-" + j.ToString())) + { + for (int k = 0; k < 10; k++) + { + stream.Add(new EventMessage { Body = "body" + k }); + } + stream.CommitChanges(Guid.NewGuid()); + } + } + scope.Complete(); + } + }); + _commits = Persistence.GetFrom().ToArray(); + } + + [Fact] + public void Should_have_expected_number_of_commits() + { + _commits.Length.Should().Be(Loop * StreamsPerTransaction); + } + + [Fact] + public void ScopeCompleteAndSerializable() + { + Reinitialize(); + const int loop = 10; + using (var scope = new TransactionScope( + TransactionScopeOption.Required, + new TransactionOptions + { + IsolationLevel = IsolationLevel.Serializable + })) + { + Parallel.For(0, loop, i => + { + Console.WriteLine("Creating stream {0} on thread {1}", i, Thread.CurrentThread.ManagedThreadId); + var eventStore = new OptimisticEventStore(Persistence, null); + string streamId = i.ToString(CultureInfo.InvariantCulture); + using (var stream = eventStore.OpenStream(streamId)) + { + stream.Add(new EventMessage { Body = "body1" }); + stream.Add(new EventMessage { Body = "body2" }); + stream.CommitChanges(Guid.NewGuid()); + } + }); + scope.Complete(); + } + ICommit[] commits = Persistence.GetFrom(0).ToArray(); + commits.Length.Should().Be(loop); + } + + [Fact] + public void ScopeNotCompleteAndReadCommitted() + { + Reinitialize(); + const int loop = 10; + using (new TransactionScope( + TransactionScopeOption.Required, + new TransactionOptions + { + IsolationLevel = IsolationLevel.ReadCommitted + })) + { + Parallel.For(0, loop, i => + { + Console.WriteLine(@"Creating stream {0} on thread {1}", i, Thread.CurrentThread.ManagedThreadId); + var eventStore = new OptimisticEventStore(Persistence, null); + string streamId = i.ToString(CultureInfo.InvariantCulture); + using (var stream = eventStore.OpenStream(streamId)) + { + stream.Add(new EventMessage { Body = "body1" }); + stream.Add(new EventMessage { Body = "body2" }); + stream.CommitChanges(Guid.NewGuid()); + } + }); + } + ICommit[] commits = Persistence.GetFrom(0).ToArray(); + commits.Length.Should().Be(0); + } + + [Fact] + public void ScopeNotCompleteAndSerializable() + { + Reinitialize(); + const int loop = 10; + using (new TransactionScope( + TransactionScopeOption.Required, + new TransactionOptions + { + IsolationLevel = IsolationLevel.ReadCommitted + })) + { + Parallel.For(0, loop, i => + { + Console.WriteLine(@"Creating stream {0} on thread {1}", i, Thread.CurrentThread.ManagedThreadId); + var eventStore = new OptimisticEventStore(Persistence, null); + string streamId = i.ToString(CultureInfo.InvariantCulture); + using (var stream = eventStore.OpenStream(streamId)) + { + stream.Add(new EventMessage { Body = "body1" }); + stream.Add(new EventMessage { Body = "body2" }); + stream.CommitChanges(Guid.NewGuid()); + } + }); + } + ICommit[] commits = Persistence.GetFrom(0).ToArray(); + commits.Length.Should().Be(0); + } + } +} + +#pragma warning restore S101 // Types should be named in PascalCase +#pragma warning restore 169 // ReSharper disable InconsistentNaming +#pragma warning restore IDE1006 // Naming Styles diff --git a/src/NEventStore.Persistence.AcceptanceTests/PersistenceTests.cs b/src/NEventStore.Persistence.AcceptanceTests/PersistenceTests.cs new file mode 100644 index 000000000..6b9388dd4 --- /dev/null +++ b/src/NEventStore.Persistence.AcceptanceTests/PersistenceTests.cs @@ -0,0 +1,1417 @@ + +#pragma warning disable 169 // ReSharper disable InconsistentNaming +#pragma warning disable IDE1006 // Naming Styles + +using NEventStore.Persistence.AcceptanceTests.BDD; +using FluentAssertions; +#if MSTEST +using Microsoft.VisualStudio.TestTools.UnitTesting; +#endif +#if NUNIT +using NUnit.Framework; +#endif +#if XUNIT +using Xunit; +using Xunit.Should; +#endif + +namespace NEventStore.Persistence.AcceptanceTests +{ +#if MSTEST + [TestClass] +#endif + public class when_a_commit_header_has_a_name_that_contains_a_period : PersistenceEngineConcern + { + private ICommit? _persisted; + private string? _streamId; + + protected override void Context() + { + _streamId = Guid.NewGuid().ToString(); + var attempt = new CommitAttempt(_streamId, + 2, + Guid.NewGuid(), + 1, + DateTime.UtcNow, + new Dictionary { { "key.1", "value" } }, + [new EventMessage { Body = new ExtensionMethods.SomeDomainEvent { SomeProperty = "Test" } }]); + Persistence.Commit(attempt); + } + + protected override void Because() + { + _persisted = Persistence.GetFrom(_streamId!, 0, int.MaxValue).First(); + } + + [Fact] + public void should_correctly_deserialize_headers() + { + _persisted!.Headers.Keys.Should().Contain("key.1"); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_a_commit_is_successfully_persisted : PersistenceEngineConcern + { + private CommitAttempt? _attempt; + private DateTime _now; + private ICommit? _persisted; + private string? _streamId; + + protected override void Context() + { + _now = SystemTime.UtcNow.AddYears(1); + _streamId = Guid.NewGuid().ToString(); + _attempt = _streamId.BuildAttempt(_now); + + Persistence.Commit(_attempt); + } + + protected override void Because() + { + _persisted = Persistence.GetFrom(_streamId!, 0, int.MaxValue).First(); + } + + [Fact] + public void should_correctly_persist_the_stream_identifier() + { + _persisted!.StreamId.Should().Be(_attempt!.StreamId); + } + + [Fact] + public void should_correctly_persist_the_stream_stream_revision() + { + _persisted!.StreamRevision.Should().Be(_attempt!.StreamRevision); + } + + [Fact] + public void should_correctly_persist_the_commit_identifier() + { + _persisted!.CommitId.Should().Be(_attempt!.CommitId); + } + + [Fact] + public void should_correctly_persist_the_commit_sequence() + { + _persisted!.CommitSequence.Should().Be(_attempt!.CommitSequence); + } + + // persistence engines have varying levels of precision with respect to time. + [Fact] + public void should_correctly_persist_the_commit_stamp() + { + var difference = _persisted!.CommitStamp.Subtract(_now); + difference.Days.Should().Be(0); + difference.Hours.Should().Be(0); + difference.Minutes.Should().Be(0); + difference.Should().BeLessOrEqualTo(TimeSpan.FromSeconds(1)); + } + + [Fact] + public void should_correctly_persist_the_headers() + { + _persisted!.Headers.Count.Should().Be(_attempt!.Headers.Count); + } + + [Fact] + public void should_correctly_persist_the_events() + { + _persisted!.Events.Count.Should().Be(_attempt!.Events.Count); + } + + [Fact] + public void should_cause_the_stream_to_be_found_in_the_list_of_streams_to_snapshot() + { + Persistence.GetStreamsToSnapshot(1).FirstOrDefault(x => x.StreamId == _streamId).Should().NotBeNull(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_reading_from_a_given_revision : PersistenceEngineConcern + { + private const int LoadFromCommitContainingRevision = 3; + private const int UpToCommitWithContainingRevision = 5; + private ICommit[]? _committed; + private ICommit? _oldest, _oldest2, _oldest3; + private string? _streamId; + + protected override void Context() + { + _oldest = Persistence.CommitSingle(); // 2 events, revision 1-2 + _oldest2 = Persistence.CommitNext(_oldest!); // 2 events, revision 3-4 + _oldest3 = Persistence.CommitNext(_oldest2!); // 2 events, revision 5-6 + Persistence.CommitNext(_oldest3!); // 2 events, revision 7-8 + + _streamId = _oldest!.StreamId; + } + + protected override void Because() + { + _committed = Persistence.GetFrom(_streamId!, LoadFromCommitContainingRevision, UpToCommitWithContainingRevision).ToArray(); + } + + [Fact] + public void should_start_from_the_commit_which_contains_the_min_stream_revision_specified() + { + _committed![0].CommitId.Should().Be(_oldest2!.CommitId); // contains revision 3 + } + + [Fact] + public void should_read_up_to_the_commit_which_contains_the_max_stream_revision_specified() + { + _committed!.Last().CommitId.Should().Be(_oldest3!.CommitId); // contains revision 5 + } + } + +#if MSTEST + [TestClass] +#endif + public class when_reading_from_a_given_revision_to_commit_revision : PersistenceEngineConcern + { + private const int LoadFromCommitContainingRevision = 3; + private const int UpToCommitWithContainingRevision = 6; + private ICommit[]? _committed; + private ICommit? _oldest, _oldest2, _oldest3; + private string? _streamId; + + protected override void Context() + { + _oldest = Persistence.CommitSingle(); // 2 events, revision 1-2 + _oldest2 = Persistence.CommitNext(_oldest!); // 2 events, revision 3-4 + _oldest3 = Persistence.CommitNext(_oldest2!); // 2 events, revision 5-6 + Persistence.CommitNext(_oldest3!); // 2 events, revision 7-8 + + _streamId = _oldest!.StreamId; + } + + protected override void Because() + { + _committed = Persistence.GetFrom(_streamId!, LoadFromCommitContainingRevision, UpToCommitWithContainingRevision).ToArray(); + } + + [Fact] + public void should_start_from_the_commit_which_contains_the_min_stream_revision_specified() + { + _committed![0].CommitId.Should().Be(_oldest2!.CommitId); // contains revision 3 + } + + [Fact] + public void should_read_up_to_the_commit_which_contains_the_max_stream_revision_specified() + { + _committed!.Last().CommitId.Should().Be(_oldest3!.CommitId); // contains revision 6 + } + } + +#if MSTEST + [TestClass] +#endif + public class when_committing_a_stream_with_the_same_revision : PersistenceEngineConcern + { + private CommitAttempt? _attemptWithSameRevision; + private Exception? _thrown; + + protected override void Context() + { + var commit = Persistence.CommitSingle(); + _attemptWithSameRevision = commit!.StreamId.BuildAttempt(); + } + + protected override void Because() + { + _thrown = Catch.Exception(() => Persistence.Commit(_attemptWithSameRevision!)); + } + + [Fact] + public void should_throw_a_ConcurrencyException() + { + _thrown.Should().BeOfType(); + } + } + + // This test ensure the uniqueness of BucketId+StreamId+CommitSequence + // to avoid concurrency issues +#if MSTEST + [TestClass] +#endif + public class when_committing_a_stream_with_the_same_sequence : PersistenceEngineConcern + { + private CommitAttempt? _attempt1, _attempt2; + private Exception? _thrown; + + protected override void Context() + { + string streamId = Guid.NewGuid().ToString(); + _attempt1 = streamId.BuildAttempt(); + _attempt2 = new CommitAttempt( + _attempt1.BucketId, // <--- Same bucket + _attempt1.StreamId, // <--- Same stream it + _attempt1.StreamRevision + 10, + Guid.NewGuid(), + _attempt1.CommitSequence, // <--- Same commit seq + DateTime.UtcNow, + _attempt1.Headers, + [ + new EventMessage(){ Body = new ExtensionMethods.SomeDomainEvent {SomeProperty = "Test 3"}} + ] + ); + + Persistence.Commit(_attempt1); + } + + protected override void Because() + { + _thrown = Catch.Exception(() => Persistence.Commit(_attempt2!)); + } + + [Fact] + public void should_throw_a_ConcurrencyException() + { + _thrown.Should().BeOfType(); + } + } + + //TODO:This test looks exactly like the one above. What are we trying to prove? +#if MSTEST + [TestClass] +#endif + public class when_attempting_to_overwrite_a_committed_sequence : PersistenceEngineConcern + { + private CommitAttempt? _failedAttempt; + private Exception? _thrown; + + protected override void Context() + { + string streamId = Guid.NewGuid().ToString(); + CommitAttempt successfulAttempt = streamId.BuildAttempt(); + Persistence.Commit(successfulAttempt); + _failedAttempt = streamId.BuildAttempt(); + } + + protected override void Because() + { + _thrown = Catch.Exception(() => Persistence.Commit(_failedAttempt!)); + } + + [Fact] + public void should_throw_a_ConcurrencyException() + { + _thrown.Should().BeOfType(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_attempting_to_persist_a_commit_twice : PersistenceEngineConcern + { + private CommitAttempt? _attemptTwice; + private Exception? _thrown; + + protected override void Context() + { + var commit = Persistence.CommitSingle(); + _attemptTwice = new CommitAttempt( + commit!.BucketId, + commit.StreamId, + commit.StreamRevision, + commit.CommitId, + commit.CommitSequence, + commit.CommitStamp, + commit.Headers.ToDictionary(k => k.Key, v => v.Value), + commit.Events.ToArray()); + } + + protected override void Because() + { + _thrown = Catch.Exception(() => Persistence.Commit(_attemptTwice!)); + } + + [Fact] + public void should_throw_a_DuplicateCommitException() + { + _thrown.Should().BeOfType(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_attempting_to_persist_a_commitId_twice_on_same_stream : PersistenceEngineConcern + { + private CommitAttempt? _attemptTwice; + private Exception? _thrown; + + protected override void Context() + { + var commit = Persistence.CommitSingle(); + _attemptTwice = new CommitAttempt( + commit!.BucketId, + commit.StreamId, + commit.StreamRevision + 1, + commit.CommitId, + commit.CommitSequence + 1, + commit.CommitStamp, + commit.Headers.ToDictionary(k => k.Key, v => v.Value), + commit.Events.ToArray() + ); + } + + protected override void Because() + { + _thrown = Catch.Exception(() => Persistence.Commit(_attemptTwice!)); + } + + [Fact] + public void should_throw_a_DuplicateCommitException() + { + _thrown.Should().BeOfType(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_committing_more_events_than_the_configured_page_size : PersistenceEngineConcern + { + private CommitAttempt[]? _committed; + private ICommit[]? _loaded; + private string? _streamId; + + protected override void Context() + { + _streamId = Guid.NewGuid().ToString(); + _committed = Persistence.CommitMany(ConfiguredPageSizeForTesting + 2, _streamId).ToArray(); + } + + protected override void Because() + { + _loaded = Persistence.GetFrom(_streamId!, 0, int.MaxValue).ToArray(); + } + + [Fact] + public void should_load_the_same_number_of_commits_which_have_been_persisted() + { + _loaded!.Length.Should().Be(_committed!.Length); + } + + [Fact] + public void should_load_the_same_commits_which_have_been_persisted() + { + _committed! + .All(commit => _loaded!.SingleOrDefault(loaded => loaded.CommitId == commit.CommitId) != null) + .Should().BeTrue(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_saving_a_snapshot : PersistenceEngineConcern + { + private bool _added; + private Snapshot? _snapshot; + private string? _streamId; + + protected override void Context() + { + _streamId = Guid.NewGuid().ToString(); + _snapshot = new Snapshot(_streamId, 1, "Snapshot"); + Persistence.CommitSingle(_streamId); + } + + protected override void Because() + { + _added = Persistence.AddSnapshot(_snapshot!); + } + + [Fact] + public void should_indicate_the_snapshot_was_added() + { + _added.Should().BeTrue(); + } + + [Fact] + public void should_be_able_to_retrieve_the_snapshot() + { + Persistence.GetSnapshot(_streamId!, _snapshot!.StreamRevision).Should().NotBeNull(); + } + } + + /// + /// having multiple snapshots for the same tuple: bucketId, streamId, streamRevision + /// should not be allowed, the resulting behavior should be ignoring or updating the + /// snapshot, that was the original design (it's up to the driver decide what to do) + /// this behavior can be changed in a future implementation. + /// +#if MSTEST + [TestClass] +#endif + public class when_adding_multiple_snapshots_for_same_bucketId_streamId_streamRevision : PersistenceEngineConcern + { + private bool _added; + private Snapshot? _snapshot; + private Snapshot? _updatedSnapshot; + private string? _streamId; + + private Exception? _thrown; + + protected override void Context() + { + _streamId = Guid.NewGuid().ToString(); + _snapshot = new Snapshot(_streamId, 1, "Snapshot"); + Persistence.CommitSingle(_streamId); + + Persistence.AddSnapshot(_snapshot); + } + + protected override void Because() + { + _updatedSnapshot = new Snapshot(_streamId!, 1, "Updated Snapshot"); + _thrown = Catch.Exception(() => _added = Persistence.AddSnapshot(_updatedSnapshot)); + } + + [Fact] + public void should_not_raise_exception() + { + _thrown.Should().BeNull(); + } + + [Fact] + public void should_be_able_to_retrieve_the_correct_snapshot_original_or_updated_depends_on_driver_implementation() + { + var snapshot = Persistence.GetSnapshot(_streamId!, _snapshot!.StreamRevision); + snapshot.Should().NotBeNull(); + if (_added) + { + snapshot!.Payload.Should().Be(_updatedSnapshot!.Payload, "The snapshot was added, I expected to get the most updated version"); + } + else + { + snapshot!.Payload.Should().Be(_snapshot.Payload, "The snapshot was not added, I expected to get the original version"); + } + } + } + +#if MSTEST + [TestClass] +#endif + public class when_retrieving_a_snapshot : PersistenceEngineConcern + { + private Snapshot? _correct; + private ISnapshot? _snapshot; + private string? _streamId; + private Snapshot? _tooFarForward; + + protected override void Context() + { + _streamId = Guid.NewGuid().ToString(); + var commit1 = Persistence.CommitSingle(_streamId); // rev 1-2 + var commit2 = Persistence.CommitNext(commit1!); // rev 3-4 + Persistence.CommitNext(commit2!); // rev 5-6 + + Persistence.AddSnapshot(new Snapshot(_streamId, 1, string.Empty)); //Too far back + _correct = new Snapshot(_streamId, 3, "Snapshot"); + Persistence.AddSnapshot(_correct); + _tooFarForward = new Snapshot(_streamId, 5, string.Empty); + Persistence.AddSnapshot(_tooFarForward); + } + + protected override void Because() + { + _snapshot = Persistence.GetSnapshot(_streamId!, _tooFarForward!.StreamRevision - 1); + } + + [Fact] + public void should_load_the_most_recent_prior_snapshot() + { + _snapshot!.StreamRevision.Should().Be(_correct!.StreamRevision); + } + + [Fact] + public void should_have_the_correct_snapshot_payload() + { + _snapshot!.Payload.Should().Be(_correct!.Payload); + } + + [Fact] + public void should_have_the_correct_stream_id() + { + _snapshot!.StreamId.Should().Be(_correct!.StreamId); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_a_snapshot_has_been_added_to_the_most_recent_commit_of_a_stream : PersistenceEngineConcern + { + private const string SnapshotData = "snapshot"; + private ICommit? _newest; + private ICommit? _oldest, _oldest2; + private string? _streamId; + + protected override void Context() + { + _streamId = Guid.NewGuid().ToString(); + _oldest = Persistence.CommitSingle(_streamId); + _oldest2 = Persistence.CommitNext(_oldest!); + _newest = Persistence.CommitNext(_oldest2!); + } + + protected override void Because() + { + Persistence.AddSnapshot(new Snapshot(_streamId!, _newest!.StreamRevision, SnapshotData)); + } + + [Fact] + public void should_no_longer_find_the_stream_in_the_set_of_streams_to_be_snapshot() + { + Persistence.GetStreamsToSnapshot(1).Any(x => x.StreamId == _streamId).Should().BeFalse(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_adding_a_commit_after_a_snapshot : PersistenceEngineConcern + { + private const int WithinThreshold = 2; + private const int OverThreshold = 3; + private const string SnapshotData = "snapshot"; + private ICommit? _oldest, _oldest2; + private string? _streamId; + + protected override void Context() + { + _streamId = Guid.NewGuid().ToString(); + _oldest = Persistence.CommitSingle(_streamId); + _oldest2 = Persistence.CommitNext(_oldest!); + Persistence.AddSnapshot(new Snapshot(_streamId, _oldest2!.StreamRevision, SnapshotData)); + } + + protected override void Because() + { + Persistence.Commit(_oldest2!.BuildNextAttempt()); + } + + // Because Raven and Mongo update the stream head asynchronously, occasionally will fail this test + [Fact] + public void should_find_the_stream_in_the_set_of_streams_to_be_snapshot_when_within_the_threshold() + { + Persistence.GetStreamsToSnapshot(WithinThreshold).FirstOrDefault(x => x.StreamId == _streamId).Should().NotBeNull(); + } + + [Fact] + public void should_not_find_the_stream_in_the_set_of_streams_to_be_snapshot_when_over_the_threshold() + { + Persistence.GetStreamsToSnapshot(OverThreshold).Any(x => x.StreamId == _streamId).Should().BeFalse(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_reading_all_commits_from_a_particular_point_in_time : PersistenceEngineConcern + { + private ICommit[]? _committed; + private CommitAttempt? _first; + private DateTime _now; + private ICommit? _second; + private string? _streamId; + private ICommit? _third; + + protected override void Context() + { + _streamId = Guid.NewGuid().ToString(); + + _now = SystemTime.UtcNow.AddYears(1); + _first = _streamId.BuildAttempt(_now.AddSeconds(1)); + Persistence.Commit(_first); + + _second = Persistence.CommitNext(_first); + _third = Persistence.CommitNext(_second!); + Persistence.CommitNext(_third!); + } + + protected override void Because() + { + _committed = Persistence.GetFrom(Bucket.Default, _now).ToArray(); + } + + [Fact] + public void should_return_all_commits_on_or_after_the_point_in_time_specified() + { + _committed!.Length.Should().Be(4); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_paging_over_all_commits_from_a_particular_point_in_time : PersistenceEngineConcern + { + private CommitAttempt[]? _committed; + private ICommit[]? _loaded; + private DateTime _start; + + protected override void Context() + { + _start = SystemTime.UtcNow; + // Due to loss in precision in various storage engines, we're rounding down to the + // nearest second to ensure include all commits from the 'start'. + _start = _start.AddSeconds(-1); + _committed = Persistence.CommitMany(ConfiguredPageSizeForTesting + 2).ToArray(); + } + + protected override void Because() + { + _loaded = Persistence.GetFrom(Bucket.Default, _start).ToArray(); + } + + [Fact] + public void should_load_the_same_number_of_commits_which_have_been_persisted() + { + _loaded!.Length.Should().Be(_committed!.Length); + } + + [Fact] + public void should_load_the_same_commits_which_have_been_persisted() + { + _committed! + .All(commit => _loaded!.SingleOrDefault(loaded => loaded.CommitId == commit.CommitId) != null) + .Should().BeTrue(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_paging_over_all_commits_from_a_particular_checkpoint : PersistenceEngineConcern + { + private List? _committed; + private List? _loaded; + private const int checkPoint = 2; + + protected override void Context() + { + _committed = Persistence.CommitMany(ConfiguredPageSizeForTesting + 1).Select(c => c.CommitId).ToList(); + } + + protected override void Because() + { + _loaded = Persistence.GetFrom(checkPoint).Select(c => c.CommitId).ToList(); + } + + [Fact] + public void should_load_the_same_number_of_commits_which_have_been_persisted_starting_from_the_checkpoint() + { + _loaded!.Count.Should().Be(_committed!.Count - checkPoint); + } + + [Fact] + public void should_load_only_the_commits_starting_from_the_checkpoint() + { + _committed!.Skip(checkPoint).All(x => _loaded!.Contains(x)).Should().BeTrue(); // all commits should be found in loaded collection + } + } + +#if MSTEST + [TestClass] +#endif + public class when_paging_over_all_commits_of_a_bucket_from_a_particular_checkpoint : PersistenceEngineConcern + { + private List? _committedOnBucket1; + private List? _committedOnBucket2; + private List? _loaded; + private const int checkPoint = 2; + + protected override void Context() + { + _committedOnBucket1 = Persistence.CommitMany(ConfiguredPageSizeForTesting + 1, null, "b1").Select(c => c.CommitId).ToList(); + _committedOnBucket2 = Persistence.CommitMany(ConfiguredPageSizeForTesting + 1, null, "b2").Select(c => c.CommitId).ToList(); + _committedOnBucket1.AddRange(Persistence.CommitMany(4, null, "b1").Select(c => c.CommitId)); + } + + protected override void Because() + { + _loaded = Persistence.GetFrom("b1", checkPoint).Select(c => c.CommitId).ToList(); + } + + [Fact] + public void should_load_the_same_number_of_commits_which_have_been_persisted_starting_from_the_checkpoint() + { + _loaded!.Count.Should().Be(_committedOnBucket1!.Count - checkPoint); + } + + [Fact] + public void should_load_only_the_commits_on_bucket1_starting_from_the_checkpoint() + { + _committedOnBucket1!.Skip(checkPoint).All(x => _loaded!.Contains(x)).Should().BeTrue(); // all commits should be found in loaded collection + } + + [Fact] + public void should_not_load_the_commits_from_bucket2() + { + _committedOnBucket2!.All(x => !_loaded!.Contains(x)).Should().BeTrue(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_paging_over_all_commits_from_a_particular_checkpoint_to_a_checkpoint : PersistenceEngineConcern + { + private readonly List _committed = []; + private List? _loaded; + private const int startCheckpoint = 2; + private int endCheckpoint; + + protected override void Context() + { + var committedOnBucket1 = Persistence.CommitMany(ConfiguredPageSizeForTesting + 1, null, Bucket.Default).Select(c => c.CommitId).ToList(); + var committedOnBucket2 = Persistence.CommitMany(ConfiguredPageSizeForTesting + 1, null, "Bucket1").Select(c => c.CommitId).ToList(); + _committed.AddRange(committedOnBucket1); + _committed.AddRange(committedOnBucket2); + endCheckpoint = (2 * (ConfiguredPageSizeForTesting + 1)) - 1; + } + + protected override void Because() + { + _loaded = Persistence.GetFromTo(startCheckpoint, endCheckpoint).Select(c => c.CommitId).ToList(); + } + + [Fact] + public void should_load_the_same_number_of_commits_which_have_been_persisted_starting_from_the_checkpoint_to_the_checkpoint() + { + _loaded!.Count.Should().Be(endCheckpoint - startCheckpoint); + } + + [Fact] + public void should_load_only_the_commits_starting_from_the_checkpoint_to_the_checkpoint() + { + _committed + .Skip(startCheckpoint) + .Take(_committed.Count - startCheckpoint - 1) + //.Take(endCheckpoint - startCheckpoint) + .All(x => _loaded!.Contains(x)).Should().BeTrue(); // all commits should be found in loaded collection + } + } + +#if MSTEST + [TestClass] +#endif + public class when_paging_over_all_commits_of_a_bucket_from_a_particular_checkpoint_to_a_checkpoint : PersistenceEngineConcern + { + private List? _committedOnBucket1; + private List? _committedOnBucket2; + private List? _loaded; + private const int startCheckpoint = 2; + private int endCheckpoint; + + protected override void Context() + { + _committedOnBucket1 = Persistence.CommitMany(ConfiguredPageSizeForTesting + 1, null, "b1").Select(c => c.CommitId).ToList(); + _committedOnBucket2 = Persistence.CommitMany(ConfiguredPageSizeForTesting + 1, null, "b2").Select(c => c.CommitId).ToList(); + _committedOnBucket1.AddRange(Persistence.CommitMany(4, null, "b1").Select(c => c.CommitId)); + endCheckpoint = ((2 * (ConfiguredPageSizeForTesting + 1)) + 4) - 1; + } + + protected override void Because() + { + _loaded = Persistence.GetFromTo("b1", startCheckpoint, endCheckpoint).Select(c => c.CommitId).ToList(); + } + + [Fact] + public void should_load_the_same_number_of_commits_which_have_been_persisted_starting_from_the_checkpoint_to_the_checkpoint() + { + _loaded!.Count.Should().Be(_committedOnBucket1!.Count - startCheckpoint - 1); + } + + [Fact] + public void should_load_only_the_commits_on_bucket1_starting_from_the_checkpoint_to_the_checkpoint() + { + _committedOnBucket1! + .Skip(startCheckpoint) + .Take(_committedOnBucket1!.Count - startCheckpoint - 1) + .All(x => _loaded!.Contains(x)).Should().BeTrue(); // all commits should be found in loaded collection + } + + [Fact] + public void should_not_load_the_commits_from_bucket2() + { + _committedOnBucket2!.All(x => !_loaded!.Contains(x)).Should().BeTrue(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_reading_all_commits_from_the_year_1_AD : PersistenceEngineConcern + { + private Exception? _thrown; + + protected override void Because() + { + // ReSharper disable once ReturnValueOfPureMethodIsNotUsed + _thrown = Catch.Exception(() => Persistence.GetFrom(Bucket.Default, 0).FirstOrDefault()); + } + + [Fact] + public void should_NOT_throw_an_exception() + { + _thrown.Should().BeNull(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_purging_all_commits : PersistenceEngineConcern + { + protected override void Context() + { + Persistence.CommitSingle(); + } + + protected override void Because() + { + Persistence.Purge(); + } + + [Fact] + public void should_not_find_any_commits_stored() + { + Persistence.GetFrom(Bucket.Default, 0).Count().Should().Be(0); + } + + [Fact] + public void should_not_find_any_streams_to_snapshot() + { + Persistence.GetStreamsToSnapshot(0).Count().Should().Be(0); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_invoking_after_disposal : PersistenceEngineConcern + { + private Exception? _thrown; + + protected override void Context() + { + Persistence.Dispose(); + } + + protected override void Because() + { + _thrown = Catch.Exception(() => Persistence.CommitSingle()); + } + + [Fact] + public void should_throw_an_ObjectDisposedException() + { + _thrown.Should().BeOfType(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_committing_a_stream_with_the_same_id_as_a_stream_same_bucket : PersistenceEngineConcern + { + private string? _streamId; + private static Exception? _thrown; + + protected override void Context() + { + _streamId = Guid.NewGuid().ToString(); + Persistence.Commit(_streamId.BuildAttempt()); + } + + protected override void Because() + { + _thrown = Catch.Exception(() => Persistence.Commit(_streamId!.BuildAttempt())); + } + + [Fact] + public void should_throw() + { + _thrown.Should().NotBeNull(); + } + + [Fact] + public void should_be_duplicate_commit_exception() + { + _thrown.Should().BeOfType(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_committing_a_stream_with_the_same_id_as_a_stream_in_another_bucket : PersistenceEngineConcern + { + private const string _bucketAId = "a"; + private const string _bucketBId = "b"; + private string? _streamId; + private static CommitAttempt? _attemptForBucketB; + private static Exception? _thrown; + private DateTime _attemptACommitStamp; + + protected override void Context() + { + _streamId = Guid.NewGuid().ToString(); + DateTime now = SystemTime.UtcNow; + Persistence.Commit(_streamId.BuildAttempt(now, _bucketAId)); + _attemptACommitStamp = Persistence.GetFrom(_bucketAId, _streamId, 0, int.MaxValue).First().CommitStamp; + _attemptForBucketB = _streamId.BuildAttempt(now.Subtract(TimeSpan.FromDays(1)), _bucketBId); + } + + protected override void Because() + { + _thrown = Catch.Exception(() => Persistence.Commit(_attemptForBucketB!)); + } + + [Fact] + public void should_succeed() + { + _thrown.Should().BeNull(); + } + + [Fact] + public void should_persist_to_the_correct_bucket() + { + ICommit[] stream = Persistence.GetFrom(_bucketBId, _streamId!, 0, int.MaxValue).ToArray(); + stream.Should().NotBeNull(); + stream.Length.Should().Be(1); + } + + [Fact] + public void should_not_affect_the_stream_from_the_other_bucket() + { + ICommit[] stream = Persistence.GetFrom(_bucketAId, _streamId!, 0, int.MaxValue).ToArray(); + stream.Should().NotBeNull(); + stream.Length.Should().Be(1); + stream[0].CommitStamp.Should().Be(_attemptACommitStamp); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_saving_a_snapshot_for_a_stream_with_the_same_id_as_a_stream_in_another_bucket : PersistenceEngineConcern + { + private const string _bucketAId = "a"; + private const string _bucketBId = "b"; + + private string? _streamId; + + private static Snapshot? _snapshot; + + protected override void Context() + { + _streamId = Guid.NewGuid().ToString(); + _snapshot = new Snapshot(_bucketBId, _streamId, 1, "Snapshot"); + Persistence.Commit(_streamId.BuildAttempt(bucketId: _bucketAId)); + Persistence.Commit(_streamId.BuildAttempt(bucketId: _bucketBId)); + } + + protected override void Because() + { + Persistence.AddSnapshot(_snapshot!); + } + + [Fact] + public void should_affect_snapshots_from_another_bucket() + { + Persistence.GetSnapshot(_bucketAId, _streamId!, _snapshot!.StreamRevision).Should().BeNull(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_reading_all_commits_from_a_particular_point_in_time_and_there_are_streams_in_multiple_buckets : PersistenceEngineConcern + { + private const string _bucketAId = "a"; + private const string _bucketBId = "b"; + + private static DateTime _now; + private static ICommit[]? _returnedCommits; + private CommitAttempt? _commitToBucketB; + + protected override void Context() + { + _now = SystemTime.UtcNow.AddYears(1); + + var commitToBucketA = Guid.NewGuid().ToString().BuildAttempt(_now.AddSeconds(1), _bucketAId); + + Persistence.Commit(commitToBucketA); + commitToBucketA = commitToBucketA.BuildNextAttempt(); + Persistence.Commit(commitToBucketA); + commitToBucketA = commitToBucketA.BuildNextAttempt(); + Persistence.Commit(commitToBucketA); + Persistence.Commit(commitToBucketA.BuildNextAttempt()); + + _commitToBucketB = Guid.NewGuid().ToString().BuildAttempt(_now.AddSeconds(1), _bucketBId); + + Persistence.Commit(_commitToBucketB); + } + + protected override void Because() + { + _returnedCommits = Persistence.GetFrom(_bucketAId, _now).ToArray(); + } + + [Fact] + public void should_not_return_commits_from_other_buckets() + { + _returnedCommits!.Any(c => c.CommitId.Equals(_commitToBucketB!.CommitId)).Should().BeFalse(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_getting_all_commits_since_checkpoint_and_there_are_streams_in_multiple_buckets : PersistenceEngineConcern + { + private ICommit[]? _commits; + + protected override void Context() + { + const string bucketAId = "a"; + const string bucketBId = "b"; + Persistence.Commit(Guid.NewGuid().ToString().BuildAttempt(bucketId: bucketAId)); + Persistence.Commit(Guid.NewGuid().ToString().BuildAttempt(bucketId: bucketBId)); + Persistence.Commit(Guid.NewGuid().ToString().BuildAttempt(bucketId: bucketAId)); + } + + protected override void Because() + { + _commits = Persistence.GetFrom(0).ToArray(); + } + + [Fact] + public void should_not_be_empty() + { + _commits.Should().NotBeEmpty(); + } + + [Fact] + public void should_be_in_order_by_checkpoint() + { + Int64 checkpoint = 0; + foreach (var commit in _commits!) + { + Int64 commitCheckpoint = commit.CheckpointToken; + commitCheckpoint.Should().BeGreaterThan(checkpoint); + checkpoint = commit.CheckpointToken; + } + } + } + +#if MSTEST + [TestClass] +#endif + public class when_purging_all_commits_and_there_are_streams_in_multiple_buckets : PersistenceEngineConcern + { + private const string _bucketAId = "a"; + private const string _bucketBId = "b"; + + private string? _streamId; + + protected override void Context() + { + _streamId = Guid.NewGuid().ToString(); + Persistence.Commit(_streamId.BuildAttempt(bucketId: _bucketAId)); + Persistence.Commit(_streamId.BuildAttempt(bucketId: _bucketBId)); + var _snapshotA = new Snapshot(bucketId: _bucketAId, _streamId, 1, "SnapshotA"); + Persistence.AddSnapshot(_snapshotA); + var _snapshotB = new Snapshot(bucketId: _bucketBId, _streamId, 1, "SnapshotB"); + Persistence.AddSnapshot(_snapshotB); + } + + protected override void Because() + { + Persistence.Purge(); + } + + [Fact] + public void should_purge_all_commits_stored_in_bucket_a() + { + Persistence.GetFrom(_bucketAId, 0).Count().Should().Be(0); + } + + [Fact] + public void should_purge_all_commits_stored_in_bucket_b() + { + Persistence.GetFrom(_bucketBId, 0).Count().Should().Be(0); + } + + [Fact] + public void should_purge_all_streams_to_snapshot_in_bucket_a() + { + Persistence.GetStreamsToSnapshot(_bucketAId, 0).Count().Should().Be(0); + } + + [Fact] + public void should_purge_all_streams_to_snapshot_in_bucket_b() + { + Persistence.GetStreamsToSnapshot(_bucketBId, 0).Count().Should().Be(0); + } + } + + [Serializable] + public class TestEvent + { + public String? S { get; set; } + } + +#if MSTEST + [TestClass] +#endif + public class when_calling_CommitChanges : PersistenceEngineConcern + { + private Guid? _commitId; + private ICommit? _persistedCommit; + private ICommit[]? _commits; + + protected override void Because() + { + var eventStore = new OptimisticEventStore(Persistence, null, null); + using IEventStream stream = eventStore.OpenStream(Guid.NewGuid()); + stream.Add(new EventMessage { Body = new TestEvent() { S = "Hi " } }); + _commitId = Guid.NewGuid(); + _persistedCommit = stream.CommitChanges(_commitId.Value); + + _commits = Persistence.GetFrom(0).ToArray(); + } + + [Fact] + public void A_Commit_had_been_persisted() + { + _persistedCommit.Should().NotBeNull(); + _persistedCommit!.CommitId.Should().Be(_commitId!.Value); + _persistedCommit.CommitSequence.Should().Be(1); + _persistedCommit.Events.Count.Should().Be(1); + _persistedCommit.Events.Single().Body.Should().BeOfType(); + ((TestEvent)_persistedCommit.Events.Single().Body!).S.Should().Be("Hi "); + } + + [Fact] + public void Should_have_expected_number_of_commits() + { + _commits!.Length.Should().Be(1); + // if should have the right event + _commits[0].CommitId.Should().Be(_commitId!.Value); + _commits[0].Events.Count.Should().Be(1); + _commits[0].Events.Single().Body.Should().BeOfType(); + ((TestEvent)_commits[0].Events.Single().Body!).S.Should().Be("Hi "); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_gettingFromCheckpoint_amount_of_commits_exceeds_PageSize : PersistenceEngineConcern + { + private ICommit[]? _commits; + private int _moreThanPageSize; + + protected override void Because() + { + _moreThanPageSize = ConfiguredPageSizeForTesting + 1; + var eventStore = new OptimisticEventStore(Persistence, null, null); + // TODO: Not sure how to set the actual page size to the const defined above + for (int i = 0; i < _moreThanPageSize; i++) + { + using IEventStream stream = eventStore.OpenStream(Guid.NewGuid()); + stream.Add(new EventMessage { Body = new TestEvent() { S = "Hi " + i } }); + stream.CommitChanges(Guid.NewGuid()); + } + _commits = Persistence.GetFrom(0).ToArray(); + } + + [Fact] + public void Should_have_expected_number_of_commits() + { + _commits!.Length.Should().Be(_moreThanPageSize); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_a_payload_is_large : PersistenceEngineConcern + { + [Fact] + public void can_commit() + { + const int bodyLength = 100000; + var attempt = new CommitAttempt( + Bucket.Default, + Guid.NewGuid().ToString(), + 1, + Guid.NewGuid(), + 1, + DateTime.UtcNow, + new Dictionary(), + [new EventMessage { Body = new string('a', bodyLength) }]); + Persistence.Commit(attempt); + + ICommit commits = Persistence.GetFrom(0).Single(); + commits.Events.Single().Body.ToString()!.Length.Should().Be(bodyLength); + } + } + + /// + /// We are adapting the tests to use 3 different frameworks: + /// - XUnit: the attached test runner does the job (fixture setup and cleanup) + /// - NUnit (.net core project) + /// - MSTest (.net core project) + /// + public abstract class PersistenceEngineConcern : SpecificationBase +#if XUNIT + , IUseFixture +#endif +#if NUNIT || MSTEST + , IDisposable +#endif + { + protected PersistenceEngineFixture? Fixture { get; private set; } + + protected IPersistStreams Persistence + { + get { return Fixture!.Persistence!; } + } + + protected int ConfiguredPageSizeForTesting + { + get { return 2; } + } + + /// + /// Can be used by XUNIT to initialize the tests. + /// + public void SetFixture(PersistenceEngineFixture data) + { + Fixture = data; + Fixture.Initialize(ConfiguredPageSizeForTesting); + } + +#if NUNIT || MSTEST + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + Fixture?.Dispose(); + } + + /// + /// + /// This code was meant to be run right before every test in the fixture to give time + /// to do further initialization before the PersistenceEngineFixture was created. + /// Unfortunately the 3 frameworks + /// have very different ways of doing this: + /// - NUnit: TestFixtureSetUp + /// - MSTest: ClassInitialize (not inherited, will be ignored if defined on a base class) + /// - xUnit: IUseFixture + SetFixture + /// We need a way to also have some configuration before the PersistenceEngineFixture is created. + /// + /// + /// We've decided to use the test constructor to do the job, it's your responsibility to guarantee + /// One time initialization (for anything that need it, if you have multiple tests on a fixture) + /// depending on the framework you are using. + /// + /// + /// quick workaround: + /// - change the parameters of the "Fixture" properties. + /// - call the 'Reinitialize()' method can be called to rerun the initialization after we changed the configuration + /// in the constructor. + /// or + /// - call the SetFixture() to reinitialize everything. + /// + /// + protected PersistenceEngineConcern() : this(new PersistenceEngineFixture()) + { } + + protected PersistenceEngineConcern(PersistenceEngineFixture fixture) + { + SetFixture(fixture); + } +#endif + } + + public partial class PersistenceEngineFixture : IDisposable + { + public IPersistStreams? Persistence { get; private set; } + + private readonly Func _createPersistence; + +#if NET462 + private bool _tracking = false; + private string _trackingInstanceName; + + /// + /// Automatic Performance Counters and tracking was disabled for full + /// framework tests because their initialization + /// can fail when the tests run on build machines (like AppVeyor and similar). + /// You can enable it back calling this function before + /// + /// + /// + public PersistenceEngineFixture TrackPerformanceInstance(string instanceName = "tests") + { + _trackingInstanceName = instanceName; + _tracking = true; + return this; + } +#endif + + public void Initialize(int pageSize) + { +#if NET462 + // performance counters cab be disabled for full framework tests because their initialization + // can fail when the tests run on build machines (like AppVeyor and similar) + if (_tracking) + { + Persistence = new NEventStore.Diagnostics.PerformanceCounterPersistenceEngine(_createPersistence(pageSize), _trackingInstanceName); + } + else + { + Persistence = _createPersistence(pageSize); + } +#else + Persistence = _createPersistence(pageSize); +#endif + Persistence.Initialize(); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (Persistence?.IsDisposed == false) + { + Persistence.Drop(); + Persistence.Dispose(); + } + } + } +} + +#pragma warning restore 169 // ReSharper disable InconsistentNaming +#pragma warning restore IDE1006 // Naming Styles diff --git a/src/NEventStore.Persistence.AcceptanceTests/Properties/AssemblyInfo.cs b/src/NEventStore.Persistence.AcceptanceTests/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..3fa652324 --- /dev/null +++ b/src/NEventStore.Persistence.AcceptanceTests/Properties/AssemblyInfo.cs @@ -0,0 +1,39 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. + +[assembly: AssemblyTitle("NEventStore.Persistence.AcceptanceTests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("NEventStore.Persistence.AcceptanceTests")] +[assembly: AssemblyCopyright("Copyright © 2013")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. + +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM + +[assembly: Guid("f85a343a-1aae-4a01-a91d-efd76322d9d8")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("8.0.0.0")] +//[assembly: AssemblyVersion("8.0.0.0")] +//[assembly: AssemblyFileVersion("8.0.0.0")] +//[assembly: AssemblyInformationalVersion("8.0.0-beta.5+Branch.release-8.0.0.Sha.4029904d0dae687763bdf2f11ad4c7754927a8c6")] diff --git a/src/NEventStore.Persistence.AcceptanceTests/SerializationTests.cs b/src/NEventStore.Persistence.AcceptanceTests/SerializationTests.cs new file mode 100644 index 000000000..2372aaf63 --- /dev/null +++ b/src/NEventStore.Persistence.AcceptanceTests/SerializationTests.cs @@ -0,0 +1,217 @@ +using NEventStore.Persistence.AcceptanceTests; +using NEventStore.Persistence.AcceptanceTests.BDD; +using FluentAssertions; +#if MSTEST +using Microsoft.VisualStudio.TestTools.UnitTesting; +#endif +#if NUNIT +#endif +#if XUNIT +using Xunit; +using Xunit.Should; +#endif + +#pragma warning disable 169 // ReSharper disable InconsistentNaming +#pragma warning disable IDE1006 // Naming Styles +#pragma warning disable S101 // Types should be named in PascalCase + +namespace NEventStore.Serialization.AcceptanceTests +{ +#if MSTEST + [TestClass] +#endif + public class when_serializing_a_simple_message : SerializationConcern + { + private readonly SimpleMessage _message = new SimpleMessage().Populate(); + private SimpleMessage? _deserialized; + private byte[]? _serialized; + + protected override void Context() + { + _serialized = Serializer.Serialize(_message); + } + + protected override void Because() + { + _deserialized = Serializer.Deserialize(_serialized!); + } + + [Fact] + public void should_deserialize_a_message_which_contains_the_same_Id_as_the_serialized_message() + { + _deserialized!.Id.Should().Be(_message.Id); + } + + [Fact] + public void should_deserialize_a_message_which_contains_the_same_Value_as_the_serialized_message() + { + _deserialized!.Value.Should().Be(_message.Value); + } + + [Fact] + public void should_deserialize_a_message_which_contains_the_same_Created_value_as_the_serialized_message() + { + _deserialized!.Created.Should().Be(_message.Created); + } + + [Fact] + public void should_deserialize_a_message_which_contains_the_same_Count_as_the_serialized_message() + { + _deserialized!.Count.Should().Be(_message.Count); + } + + [Fact] + public void should_deserialize_a_message_which_contains_the_number_of_elements_as_the_serialized_message() + { + _deserialized!.Contents.Count.Should().Be(_message.Contents.Count); + } + + [Fact] + public void should_deserialize_a_message_which_contains_the_same_Contents_as_the_serialized_message() + { + _deserialized!.Contents.SequenceEqual(_message.Contents).Should().BeTrue(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_serializing_a_list_of_event_messages : SerializationConcern + { + private readonly List Messages = new List + { + new EventMessage {Body = "some value"}, + new EventMessage {Body = 42}, + new EventMessage {Body = new SimpleMessage()} + }; + + private List? _deserialized; + private byte[]? _serialized; + + protected override void Context() + { + _serialized = Serializer.Serialize(Messages); + } + + protected override void Because() + { + _deserialized = Serializer.Deserialize>(_serialized!); + } + + [Fact] + public void should_deserialize_the_same_number_of_event_messages_as_it_serialized() + { + Messages.Count.Should().Be(_deserialized!.Count); + } + + [Fact] + public void should_deserialize_the_the_complex_types_within_the_event_messages() + { + _deserialized!.Last().Body.Should().BeOfType(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_serializing_a_list_of_commit_headers : SerializationConcern + { + private readonly Dictionary _headers = new Dictionary + { + {"HeaderKey", "SomeValue"}, + {"AnotherKey", 42}, + {"AndAnotherKey", Guid.NewGuid()}, + {"LastKey", new SimpleMessage()} + }; + + private Dictionary? _deserialized; + private byte[]? _serialized; + + protected override void Context() + { + _serialized = Serializer.Serialize(_headers); + } + + protected override void Because() + { + _deserialized = Serializer.Deserialize>(_serialized!); + } + + [Fact] + public void should_deserialize_the_same_number_of_event_messages_as_it_serialized() + { + _headers.Count.Should().Be(_deserialized!.Count); + } + + [Fact] + public void should_deserialize_the_the_complex_types_within_the_event_messages() + { + _deserialized!.Last().Value.Should().BeOfType(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_serializing_an_untyped_payload_on_a_snapshot : SerializationConcern + { + private Snapshot? _deserialized; + private IDictionary>? _payload; + private byte[]? _serialized; + private Snapshot? _snapshot; + + protected override void Context() + { + _payload = new Dictionary>(); + _snapshot = new Snapshot(Guid.NewGuid().ToString(), 42, _payload); + _serialized = Serializer.Serialize(_snapshot); + } + + protected override void Because() + { + _deserialized = Serializer.Deserialize(_serialized!); + } + + [Fact] + public void should_correctly_deserialize_the_untyped_payload_contents() + { + _deserialized!.Payload.Should().BeEquivalentTo(_snapshot!.Payload); + } + + [Fact] + public void should_correctly_deserialize_the_untyped_payload_type() + { + _deserialized!.Payload.Should().BeOfType(_snapshot!.Payload.GetType()); + } + } + + public abstract class SerializationConcern : SpecificationBase + { + private readonly SerializerFixture _data; + + public ISerialize Serializer + { + get { return _data.Serializer; } + } + + protected SerializationConcern() + { + _data = new SerializerFixture(); + } + } + + public partial class SerializerFixture + { + private readonly Func _createSerializer; + private ISerialize? _serializer; + + public ISerialize Serializer + { + get { return _serializer ??= _createSerializer(); } + } + } +} + +#pragma warning restore S101 // Types should be named in PascalCase +#pragma warning restore 169 // ReSharper disable InconsistentNaming +#pragma warning restore IDE1006 // Naming Styles \ No newline at end of file diff --git a/src/NEventStore.Persistence.AcceptanceTests/SimpleMessage.cs b/src/NEventStore.Persistence.AcceptanceTests/SimpleMessage.cs new file mode 100644 index 000000000..84f519312 --- /dev/null +++ b/src/NEventStore.Persistence.AcceptanceTests/SimpleMessage.cs @@ -0,0 +1,18 @@ +namespace NEventStore.Persistence.AcceptanceTests +{ + [Serializable] + public class SimpleMessage + { + public SimpleMessage() + { + Contents = []; + } + + public Guid Id { get; set; } + public DateTime Created { get; set; } + public string? Value { get; set; } + public int Count { get; set; } + + public List Contents { get; private set; } + } +} \ No newline at end of file diff --git a/src/NEventStore.PollingClient/AsyncPollingClient.cs b/src/NEventStore.PollingClient/AsyncPollingClient.cs new file mode 100644 index 000000000..c0ad0d8be --- /dev/null +++ b/src/NEventStore.PollingClient/AsyncPollingClient.cs @@ -0,0 +1,333 @@ +using NEventStore.Logging; +using NEventStore.Persistence; +using Microsoft.Extensions.Logging; + +namespace NEventStore.PollingClient +{ + /// + /// A Polling Client that uses the Asynchronous API of the store. + /// + public class AsyncPollingClient : IDisposable + { + /// + /// Decorator used to enable Commit Re-Sequencing. + /// During normal operations Commits might arrive out of order, or there can be holes in the sequence for whatever reason. + /// In a rebuild scenario the stream is stable and commits will be read in the correct order, holes can be safely skipped. + /// + private class CommitSequencer : IAsyncObserver + { + private readonly AsyncPollingClientObserver _observer; + private readonly ILogger _logger = LogFactory.BuildLogger(typeof(CommitSequencer)); + /// + /// How many retries we did on a hole. + /// + public int NumOfRetries { get; private set; } + /// + /// Max number of retries before skipping the hole. + /// + public int MaxNumOfRetriesBeforeSkip { get; set; } = 5; + + public CommitSequencer(AsyncPollingClientObserver observer, long checkpoint) + { + _observer = observer; + _observer.ProcessedCheckpoint = checkpoint; + } + + public Task OnCompletedAsync(CancellationToken cancellationToken) + { + return _observer.OnCompletedAsync(cancellationToken); + } + + public Task OnErrorAsync(Exception ex, CancellationToken cancellationToken) + { + return _observer.OnErrorAsync(ex, cancellationToken); + } + + public Task OnNextAsync(ICommit value, CancellationToken cancellationToken) + { + if (value.CheckpointToken != _observer.ProcessedCheckpoint + 1) + { + if (NumOfRetries < MaxNumOfRetriesBeforeSkip) + { + NumOfRetries++; + if (_logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogDebug("Hole detected on {Checkpoint} - {NumOfRetries}", value.CheckpointToken, NumOfRetries); + } + // stop the reading, the caller will wait then resume reading from the same checkpoint + return Task.FromResult(false); + } + // we reached the max number of retries, skip the hole + if (_logger.IsEnabled(LogLevel.Warning)) + { + _logger.LogWarning("Skipping hole on {Checkpoint}", value.CheckpointToken); + } + } + + NumOfRetries = 0; + return _observer.OnNextAsync(value, cancellationToken); + } + } + + /// + /// Async Observer Decorator that wraps the original observer: it is used to: + /// - Track the last processed commit. + /// - Track if the user request to stop the polling from inside the observer. + /// + private class AsyncPollingClientObserver : IAsyncObserver + { + private readonly IAsyncObserver _observer; + + public bool StopPolling { get; private set; } + /// + /// The last correctly processed commit. + /// + public long ProcessedCheckpoint { get; internal set; } + + public AsyncPollingClientObserver(IAsyncObserver observer) + { + _observer = observer; + } + public Task OnCompletedAsync(CancellationToken cancellationToken) + { + return _observer.OnCompletedAsync(cancellationToken); + } + + public Task OnErrorAsync(Exception ex, CancellationToken cancellationToken) + { + return _observer.OnErrorAsync(ex, cancellationToken); + } + + public async Task OnNextAsync(ICommit value, CancellationToken cancellationToken) + { + StopPolling = false; + var goOn = await _observer.OnNextAsync(value, cancellationToken).ConfigureAwait(false); + StopPolling = !goOn; + ProcessedCheckpoint = value.CheckpointToken; + return goOn; + } + } + + private readonly ILogger _logger; + private readonly IPersistStreams _persistStreams; + private readonly AsyncPollingClientObserver _commitObserver; + private readonly Int32 _waitInterval; + private readonly int _holeDetectionWaitInterval; + private Func? _pollingFunc; + private Int64 _checkpointToken; + private CancellationTokenSource? _cancellationTokeSource; + private bool _stopped = true; + private int _isPolling; + private CommitSequencer? _commitSequencer; + + /// + /// Creates an NEventStore Polling Client + /// + /// the store to check + /// the observer to notify + /// Interval in Milliseconds to wait when the provider + /// return no more commit and the next request + /// Interval in Milliseconds to wait before retry when a hole was detected in the stream. 0 = disable hole detection. + public AsyncPollingClient( + IPersistStreams persistStreams, + IAsyncObserver commitObserver, + Int32 waitInterval = 100, + Int32 holeDetectionWaitInterval = 0) + { + if (commitObserver is null) + { + throw new ArgumentNullException(nameof(commitObserver)); + } + + _persistStreams = persistStreams ?? throw new ArgumentNullException(nameof(persistStreams)); + _commitObserver = new AsyncPollingClientObserver(commitObserver); + _logger = LogFactory.BuildLogger(GetType()); + _waitInterval = waitInterval; + _holeDetectionWaitInterval = holeDetectionWaitInterval; + LastActivityTimestamp = DateTime.UtcNow; + } + + /// + /// Tells the caller the last tick count when the last activity occurred. This is useful for the caller + /// to setup Health check that verify if the polling client is really active and it is really loading new commits. + /// This value is obtained with DateTime.UtcNow + /// + public DateTime LastActivityTimestamp { get; private set; } + + /// + /// If the polling client encounter an exception it immediately retry, but we need to tell to the caller code + /// that the last polling encounter an error. This is needed to detect a polling client stuck as an example + /// with deserialization problems. + /// + public String? LastPollingError { get; private set; } + + /// + /// Start the polling client. + /// + public void Start(Int64 checkpointToken = 0) + { + _cancellationTokeSource = new CancellationTokenSource(); + ConfigurePollingClient(checkpointToken); + StartPollingThread(_cancellationTokeSource.Token); + } + + /// + /// Start the polling client. + /// + public void Start(string bucketId, Int64 checkpointToken = 0) + { + _cancellationTokeSource = new CancellationTokenSource(); + ConfigurePollingClient(checkpointToken, bucketId); + StartPollingThread(_cancellationTokeSource.Token); + } + + /// + /// Configure the polling function to get commits from the store. + /// + internal void ConfigurePollingClient(Int64 checkpointToken = 0, string? bucketId = null) + { + _checkpointToken = checkpointToken; + IAsyncObserver commitObserver; + if (_holeDetectionWaitInterval > 0) + { + _commitSequencer = new CommitSequencer(_commitObserver, _checkpointToken); + commitObserver = _commitSequencer; + } + else + { + commitObserver = _commitObserver; + } + if (bucketId == null) + _pollingFunc = (long checkpointToken, CancellationToken cancellationToken) => _persistStreams.GetFromAsync(checkpointToken, commitObserver, cancellationToken); + else + _pollingFunc = (long checkpointToken, CancellationToken cancellationToken) => _persistStreams.GetFromAsync(bucketId, checkpointToken, commitObserver, cancellationToken); + } + + /// + /// Simply start the timer that will queue wake up tokens. + /// + private void StartPollingThread(CancellationToken cancellationToken) + { + if (_pollingFunc == null) return; + _stopped = false; + Task.Run(async () => + { + try + { + while (!cancellationToken.IsCancellationRequested && !_commitObserver.StopPolling) + { + await PollAsync(cancellationToken).ConfigureAwait(false); + + if (!cancellationToken.IsCancellationRequested) + { + await Task.Delay(_waitInterval, cancellationToken).ConfigureAwait(false); + } + } + } + finally + { + _stopped = true; + } + }, cancellationToken) + .ContinueWith(t => + { + if (t.IsFaulted) + { + var ex = t.Exception.Flatten().InnerException; + _logger.LogError($"Error during Poll, first exception: {ex.Message}.\n{ex}"); + LastPollingError = ex.Message; + } + }, cancellationToken); + } + + /// + /// Stop the polling client. + /// + public async Task StopAsync() + { + _cancellationTokeSource?.Cancel(); + _cancellationTokeSource = null; + + while (!_stopped) + { + await Task.Delay(100).ConfigureAwait(false); + } + } + + /// + /// Poll the store for new commits. + /// + public async Task PollAsync(CancellationToken token) + { + if (Interlocked.CompareExchange(ref _isPolling, 1, 0) == 0) + { + try + { + LastActivityTimestamp = DateTime.UtcNow; + if (_commitSequencer == null) + { + await _pollingFunc!(_checkpointToken, token).ConfigureAwait(false); + _checkpointToken = _commitObserver.ProcessedCheckpoint; + } + else + { + do + { + await _pollingFunc!(_checkpointToken, token).ConfigureAwait(false); + _checkpointToken = _commitObserver.ProcessedCheckpoint; + if (_commitObserver.StopPolling) + { + break; + } + // todo: should also check if the user requested to stop the polling from the observer ? + if (_commitSequencer.NumOfRetries > 0) + { + await Task.Delay(_holeDetectionWaitInterval, token).ConfigureAwait(false); + } + } + while (!token.IsCancellationRequested && _commitSequencer.NumOfRetries > 0); + } + } + finally + { + Interlocked.Exchange(ref _isPolling, 0); + } + } + } + + private Boolean _isDisposed; + + /// + /// Dispose the polling client. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Dispose the polling client. + /// + protected virtual void Dispose(Boolean isDisposing) + { + if (_isDisposed) + { + return; + } + if (isDisposing) + { + StopAsync().Wait(); + } + _isDisposed = true; + } + + /// + /// Finalizer. + /// + ~AsyncPollingClient() + { + Dispose(false); + } + } +} diff --git a/src/NEventStore.PollingClient/CommitSequencer.cs b/src/NEventStore.PollingClient/CommitSequencer.cs new file mode 100644 index 000000000..ea8a8883a --- /dev/null +++ b/src/NEventStore.PollingClient/CommitSequencer.cs @@ -0,0 +1,93 @@ +using Microsoft.Extensions.Logging; +using NEventStore.Helpers; +using NEventStore.Logging; +using System.Globalization; + +namespace NEventStore.PollingClient +{ + /// + /// A commit sequencer that can be used with + /// + public class CommitSequencer + { + private readonly ILogger _logger; + + private readonly Func _commitCallback; + + private Int64 _lastCommitRead; + + private readonly Int32 _outOfSequenceTimeoutInMilliseconds; + + /// + /// Initializes a new instance of the CommitSequencer class. + /// + public CommitSequencer(Func commitCallback, long lastCommitRead, int outOfSequenceTimeoutInMilliseconds) + { + _logger = LogFactory.BuildLogger(GetType()); + _commitCallback = commitCallback; + _lastCommitRead = lastCommitRead; + _outOfSequenceTimeoutInMilliseconds = outOfSequenceTimeoutInMilliseconds; + } + + private DateTime? outOfSequenceTimestamp; + + /// + /// Handles the specified commit. + /// + public PollingClient2.HandlingResult Handle(ICommit commit) + { + var lc = commit.CheckpointToken; + if (lc == _lastCommitRead + 1) + { + //is ok no need to re-sequence. + return InnerHandleResult(commit, lc); + } + else if (_lastCommitRead >= lc) + { + if (_logger.IsEnabled(LogLevel.Warning)) + { + _logger.LogWarning(String.Format(CultureInfo.InvariantCulture, "Wrong sequence in commit, last read: {0} actual read: {1}", _lastCommitRead, lc)); + } + return PollingClient2.HandlingResult.MoveToNext; + } + + if (outOfSequenceTimestamp == null) + { + if (_logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogDebug("Sequencer found out of sequence, last dispatched: {LastDispatchedCheckpoint} now dispatching: {NowDispatchingCheckpoint}", _lastCommitRead, lc); + } + outOfSequenceTimestamp = DateTimeService.Now; + } + else + { + var interval = DateTimeService.Now.Subtract(outOfSequenceTimestamp.Value); + if (interval.TotalMilliseconds > _outOfSequenceTimeoutInMilliseconds) + { + if (_logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogDebug("Sequencer out of sequence timeout after {TotalMilliseconds} ms, last dispatched: {LastDispatchedCheckpoint} now dispatching: {NowDispatchingCheckpoint}", interval.TotalMilliseconds, _lastCommitRead, lc); + } + return InnerHandleResult(commit, lc); + } + if (_logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogDebug("Sequencer still out of sequence from {TotalMilliseconds} ms, last dispatched: {LastDispatchedCheckpoint} now dispatching: {NowDispatchingCheckpoint}", interval.TotalMilliseconds, _lastCommitRead, lc); + } + } + + return PollingClient2.HandlingResult.Retry; + } + + private PollingClient2.HandlingResult InnerHandleResult(ICommit commit, Int64 lc) + { + var innerReturn = _commitCallback(commit); + outOfSequenceTimestamp = null; + if (innerReturn == PollingClient2.HandlingResult.MoveToNext) + { + _lastCommitRead = lc; + } + return innerReturn; + } + } +} diff --git a/src/NEventStore.PollingClient/NEventStore.PollingClient.csproj b/src/NEventStore.PollingClient/NEventStore.PollingClient.csproj new file mode 100644 index 000000000..0adfc1a86 --- /dev/null +++ b/src/NEventStore.PollingClient/NEventStore.PollingClient.csproj @@ -0,0 +1,64 @@ + + + netstandard2.0 + false + NEventStore.PollingClient + NEventStore.PollingClient + false + TRACE;DEBUG + + + NEventStore.PollingClient + NEventStore.PollingClient + NEventStore Dev Team + http://neventstore.org + false + The purpose of the EventStore is to represent a series of events as a stream. NEventStore is a persistence agnostic event sourcing library for .NET. The primary use is most often associated with CQRS. This package is an implementation of a Polling Client that reads data from an EventStore. + events, event sourcing, cqrs, storage, persistence, database + True + true + true + snupkg + true + true + True + True + NEventStore Dev Team + icon.png + Readme.md + https://github.com/NEventStore/NEventStore + git + license.txt + True + True + latest-recommended + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + True + \ + + + True + \ + + + True + \ + + + + + + \ No newline at end of file diff --git a/src/NEventStore.PollingClient/PollingClient2.cs b/src/NEventStore.PollingClient/PollingClient2.cs new file mode 100644 index 000000000..3f6e35b39 --- /dev/null +++ b/src/NEventStore.PollingClient/PollingClient2.cs @@ -0,0 +1,278 @@ +using NEventStore.Logging; +using NEventStore.Persistence; +using System.Collections.Concurrent; +using Microsoft.Extensions.Logging; +using System.Globalization; + +namespace NEventStore.PollingClient +{ + /// + /// A Polling Client that uses the Synchronous API of the store. + /// + public class PollingClient2 : IDisposable + { + /// + /// Result of the handling of a commit. + /// + public enum HandlingResult + { + /// + /// Move to the next commit. + /// + MoveToNext = 0, + /// + /// Retry the current commit. + /// + Retry = 1, + /// + /// Stop the polling client. + /// + Stop = 2, + } + + private readonly ILogger _logger; + + private readonly Func _commitCallback; + + private readonly IPersistStreams _persistStreams; + + private readonly Int32 _waitInterval; + private readonly Thread _pollingThread; + private readonly System.Timers.Timer _pollingWakeUpTimer; + + private Func>? _pollingFunc; + + private Int64 _checkpointToken; + + /// + /// Creates an NEventStore Polling Client + /// + /// the store to check + /// callback to execute at each commit + /// Interval in Milliseconds to wait when the provider + /// return no more commit and the next request + public PollingClient2( + IPersistStreams persistStreams, + Func callback, + Int32 waitInterval = 100) + { + _commitCallback = callback ?? throw new ArgumentNullException(nameof(callback), "Cannot use polling client without callback"); + _persistStreams = persistStreams ?? throw new ArgumentNullException(nameof(persistStreams), "PersistStreams cannot be null"); + + _logger = LogFactory.BuildLogger(GetType()); + _waitInterval = waitInterval; + _pollingWakeUpTimer = new System.Timers.Timer(); + _pollingWakeUpTimer.Elapsed += (sender, e) => WakeUpPollingClient(); + _pollingWakeUpTimer.Interval = _waitInterval; + + //Create polling thread + _pollingThread = new Thread(InnerPollingLoop); + _pollingThread.Start(); + + LastActivityTimestamp = DateTime.UtcNow; + } + + /// + /// Tells the caller the last tick count when the last activity occurred. This is useful for the caller + /// to setup Health check that verify if the polling client is really active and it is really loading new commits. + /// This value is obtained with DateTime.UtcNow + /// + public DateTime LastActivityTimestamp { get; private set; } + + /// + /// If the polling client encounter an exception it immediately retry, but we need to tell to the caller code + /// that the last polling encounter an error. This is needed to detect a polling client stuck as an example + /// with deserialization problems. + /// + public String? LastPollingError { get; private set; } + + /// + /// Start the polling client. + /// + public void StartFrom(Int64 checkpointToken = 0) + { + _checkpointToken = checkpointToken; + ConfigurePollingFunction(); + StartPollingThread(); + } + + /// + /// Start the polling client. + /// + public void StartFromBucket(string bucketId, Int64 checkpointToken = 0) + { + _checkpointToken = checkpointToken; + ConfigurePollingFunction(bucketId); + StartPollingThread(); + } + + /// + /// Simply start the timer that will queue wake up tokens. + /// + private void StartPollingThread() + { + _pollingWakeUpTimer.Start(); + } + + /// + /// Configure the polling function to get commits from the store. + /// + internal void ConfigurePollingFunction(string? bucketId = null) + { + if (bucketId == null) + _pollingFunc = () => _persistStreams.GetFrom(_checkpointToken); + else + _pollingFunc = () => _persistStreams.GetFrom(bucketId, _checkpointToken); + } + + /// + /// Stop the polling client. + /// + public void Stop() + { + _stopRequest = true; + _pollingWakeUpTimer?.Stop(); + + WakeUpPollingClient(); + } + + /// + /// Poll now. + /// + public void PollNow() + { + WakeUpPollingClient(); + } + + /// + /// Add an object to wake up the polling client. + /// + private void WakeUpPollingClient() + { + //Avoid adding more than one wake up object. + if (Interlocked.CompareExchange(ref _isPolling, 1, 0) == 0) + { + //If we have not a wake up object add one. + if (_pollCollection.Count == 0) + _pollCollection.Add(new object()); + + Interlocked.Exchange(ref _isPolling, 0); + } + } + + private int _isPolling; + + private Boolean _stopRequest; + + /// + /// This blocking collection is used to Wake up the polling thread + /// and to ensure that only the polling thread is polling from + /// event stream. + /// + private readonly BlockingCollection _pollCollection = []; + + private void InnerPollingLoop(object obj) + { + foreach (var _ in _pollCollection.GetConsumingEnumerable()) + { + //check stop request + if (_stopRequest) + return; + + if (InnerPoll()) + return; + } + } + + /// + /// Added to avoid flooding of logging during polling. + /// + private DateTime _lastPollingErrorLogTimestamp = DateTime.MinValue; + + /// + /// This is the inner polling function that does the polling and + /// returns true if there were errors that should stop the polling client. + /// + /// Returns true if we need to stop the outer cycle. + private bool InnerPoll() + { + LastActivityTimestamp = DateTime.UtcNow; + if (_pollingFunc == null) return false; + + try + { + var commits = _pollingFunc(); + + //if we have an error in the provider, the error will be thrown during enumeration + foreach (var commit in commits) + { + //We need to reset the error, because we read correctly a commit + LastPollingError = null; + LastActivityTimestamp = DateTime.UtcNow; + if (_stopRequest) + { + return true; + } + var result = _commitCallback(commit); + if (result == HandlingResult.Retry) + { + if (_logger.IsEnabled(LogLevel.Trace)) + { + _logger.LogTrace("Commit callback ask retry for checkpointToken: {CommitCheckpointToken} - last dispatched: {LastDispatchedCheckpointToken}", commit.CheckpointToken, _checkpointToken); + } + break; + } + else if (result == HandlingResult.Stop) + { + Stop(); + return true; + } + _checkpointToken = commit.CheckpointToken; + } + //if we reach here, we had no error contacting the persistence store. + LastPollingError = null; + } + catch (Exception ex) + { + // These exceptions are expected to be transient + LastPollingError = ex.Message; + + // These exceptions are expected to be transient, we log at maximum a log entry each minute. + if (DateTime.UtcNow.Subtract(_lastPollingErrorLogTimestamp).TotalMinutes > 1) + { + _logger.LogError(String.Format(CultureInfo.InvariantCulture, "Error during polling client {0}", ex.ToString())); + _lastPollingErrorLogTimestamp = DateTime.UtcNow; + } + + //A transient reading error is possible, but we need to wait a little bit before retrying. + Thread.Sleep(1000); + } + + return false; + } + + private Boolean _isDisposed; + + /// + /// Dispose the polling client. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Dispose the polling client. + /// + public void Dispose(Boolean isDisposing) + { + if (_isDisposed) return; + if (isDisposing) + { + Stop(); + } + _isDisposed = true; + } + } +} diff --git a/src/NEventStore.PollingClient/PollingClientException.cs b/src/NEventStore.PollingClient/PollingClientException.cs new file mode 100644 index 000000000..42e89e8dd --- /dev/null +++ b/src/NEventStore.PollingClient/PollingClientException.cs @@ -0,0 +1,28 @@ +namespace NEventStore.PollingClient +{ + /// + /// ApplicationException has been deprecated in .NET Core + /// + [Serializable] + public class PollingClientException : Exception + { + /// + /// Initializes a new instance of the class. + /// + public PollingClientException() { } + /// + /// Initializes a new instance of the class. + /// + public PollingClientException(string message) : base(message) { } + /// + /// Initializes a new instance of the class. + /// + public PollingClientException(string message, Exception inner) : base(message, inner) { } + /// + /// Initializes a new instance of the class. + /// + protected PollingClientException( + System.Runtime.Serialization.SerializationInfo info, + System.Runtime.Serialization.StreamingContext context) : base(info, context) { } + } +} diff --git a/src/proj/EventStore/Properties/AssemblyInfo.cs b/src/NEventStore.PollingClient/Properties/ProjectAssemblyInfo.cs similarity index 77% rename from src/proj/EventStore/Properties/AssemblyInfo.cs rename to src/NEventStore.PollingClient/Properties/ProjectAssemblyInfo.cs index 36864776a..0269eb2dc 100644 --- a/src/proj/EventStore/Properties/AssemblyInfo.cs +++ b/src/NEventStore.PollingClient/Properties/ProjectAssemblyInfo.cs @@ -1,6 +1,6 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -[assembly: AssemblyTitle("EventStore")] -[assembly: AssemblyDescription("")] +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("NEventStore")] +[assembly: AssemblyDescription("")] [assembly: Guid("9cb4668f-d7b2-4ad9-8f9b-2af9903d2db2")] \ No newline at end of file diff --git a/src/NEventStore.PollingClient/README.MD b/src/NEventStore.PollingClient/README.MD new file mode 100644 index 000000000..cdb8186b4 --- /dev/null +++ b/src/NEventStore.PollingClient/README.MD @@ -0,0 +1,208 @@ +# Polling Client + +This project contains a 'reference' implementation of a Polling Client for NEventStore. + +Its duty is to constantly ask the NEventStore if there are new commits made by the users, read them and dispatch the committed events to their handlers. + +You are encouraged to write your own PollingClient that suits your needs, you can use the code in this project as a starting point. + +## New Polling client without RX + +To remove dependency from RX library, the old PollingClient was removed from the project. + +The entire code for OldPollingClient is included in this README, If you still need it in your project you can simply include this class in your code as well as the dependencies from RX. + +With the new PollingClient2 you should simply pass a Function that will be called to handle the commit, you can use Rx if you like that approach approach, or even better you can use TPL Dataflow that is probably more suited. + +If you want to use the new polling client with RX you can look at PollingClientRx inside Test project. + +### Old Polling Client Code + + namespace NEventStore.Client + { + using System; + using System.Collections.Generic; + using System.Reactive; + using System.Reactive.Subjects; + using System.Threading; + using System.Threading.Tasks; + using NEventStore.Logging; + using NEventStore.Persistence; + + /// + /// Represents a client that poll the storage for latest commits. + /// + public sealed class PollingClient : ClientBase + { + private readonly int _interval; + + public PollingClient(IPersistStreams persistStreams, int interval = 5000) : base(persistStreams) + { + if (persistStreams == null) + { + throw new ArgumentNullException("persistStreams"); + } + if (interval <= 0) + { + throw new ArgumentException(Messages.MustBeGreaterThanZero.FormatWith("interval")); + } + _interval = interval; + } + + /// + /// Observe commits from the sepecified checkpoint token. If the token is null, + /// all commits from the beginning will be observed. + /// + /// The checkpoint token. + /// + /// An instance. + /// + public override IObserveCommits ObserveFrom(Int64 checkpointToken = 0) + { + return new PollingObserveCommits(PersistStreams, _interval, null, checkpointToken); + } + + public override IObserveCommits ObserveFromBucket(string bucketId, Int64 checkpointToken = 0) + { + return new PollingObserveCommits(PersistStreams, _interval, bucketId, checkpointToken); + } + + private class PollingObserveCommits : IObserveCommits + { + private ILog Logger = LogFactory.BuildLogger(typeof (PollingClient)); + private readonly IPersistStreams _persistStreams; + private Int64 _checkpointToken; + private readonly int _interval; + private readonly string _bucketId; + private readonly Subject _subject = new Subject(); + private readonly CancellationTokenSource _stopRequested = new CancellationTokenSource(); + private TaskCompletionSource _runningTaskCompletionSource; + private int _isPolling = 0; + + public PollingObserveCommits(IPersistStreams persistStreams, int interval, string bucketId, Int64 checkpointToken = 0) + { + _persistStreams = persistStreams; + _checkpointToken = checkpointToken; + _interval = interval; + _bucketId = bucketId; + } + + public IDisposable Subscribe(IObserver observer) + { + return _subject.Subscribe(observer); + } + + public void Dispose() + { + _stopRequested.Cancel(); + _subject.Dispose(); + if (_runningTaskCompletionSource != null) + { + _runningTaskCompletionSource.TrySetResult(new Unit()); + } + } + + public Task Start() + { + if (_runningTaskCompletionSource != null) + { + return _runningTaskCompletionSource.Task; + } + _runningTaskCompletionSource = new TaskCompletionSource(); + PollLoop(); + return _runningTaskCompletionSource.Task; + } + + public void PollNow() + { + DoPoll(); + } + + private void PollLoop() + { + if (_stopRequested.IsCancellationRequested) + { + Dispose(); + return; + } + TaskHelpers.Delay(_interval, _stopRequested.Token) + .WhenCompleted(_ => + { + DoPoll(); + PollLoop(); + },_ => Dispose()); + } + + private void DoPoll() + { + if (Interlocked.CompareExchange(ref _isPolling, 1, 0) == 0) + { + try + { + var commits = _bucketId == null ? + _persistStreams.GetFrom(_checkpointToken) : + _persistStreams.GetFrom(_bucketId, _checkpointToken); + + foreach (var commit in commits) + { + if (_stopRequested.IsCancellationRequested) + { + _subject.OnCompleted(); + return; + } + _subject.OnNext(commit); + _checkpointToken = commit.CheckpointToken; + } + } + catch (Exception ex) + { + // These exceptions are expected to be transient + Logger.Error(ex.ToString()); + } + Interlocked.Exchange(ref _isPolling, 0); + } + } + } + } + + public abstract class ClientBase + { + private readonly IPersistStreams _persistStreams; + + protected ClientBase(IPersistStreams persistStreams) + { + _persistStreams = persistStreams; + } + + protected IPersistStreams PersistStreams + { + get { return _persistStreams; } + } + + + /// + /// Observe commits from the sepecified checkpoint token. If the token is null, + /// all commits from the beginning will be observed. + /// + /// The checkpoint token. + /// An instance. + public abstract IObserveCommits ObserveFrom(Int64 checkpointToken = 0); + + /// + /// Observe commits from a bucket after the sepecified checkpoint token. If the token is null, + /// all commits from the beginning will be observed. + /// + /// The bucket id + /// The checkpoint token. + /// An instance. + public abstract IObserveCommits ObserveFromBucket(string bucketId, Int64 checkpointToken = 0); + } + + public interface IObserveCommits : IObservable, IDisposable + { + Task Start(); + + void PollNow(); + } + } + diff --git a/src/NEventStore.PollingClientExample/MainProgram.cs b/src/NEventStore.PollingClientExample/MainProgram.cs new file mode 100644 index 000000000..825768a7c --- /dev/null +++ b/src/NEventStore.PollingClientExample/MainProgram.cs @@ -0,0 +1,95 @@ +using NEventStore.PollingClient; +using Microsoft.Extensions.Logging; + +namespace NEventStore.PollingClientExample +{ + internal static class MainProgram + { + private static void Main() + { + using var store = WireupEventStore(); + // append some commits to the EventStore + AppendToStream(store, "Stream1"); + AppendToStream(store, "Stream2"); + AppendToStream(store, "Stream1"); + + Console.WriteLine("--------------------------"); + Console.WriteLine("Starting PollingClient2..."); + Console.WriteLine(); + + // now test the polling client + Int64 checkpointToken = LoadCheckpoint(); + var client = new PollingClient2(store.Advanced, commit => + { + // Project the commit etc + Console.WriteLine("BucketId={0};StreamId={1};CommitSequence={2}", commit.BucketId, commit.StreamId, commit.CommitSequence); + // Track the most recent checkpoint + checkpointToken = commit.CheckpointToken; + return PollingClient2.HandlingResult.MoveToNext; + }, + waitInterval: 3000); + + client.StartFrom(checkpointToken); + Console.WriteLine("Wait for the Stream to end, then press any key to continue..."); + Console.ReadKey(); + client.Stop(); + + Console.WriteLine(); + Console.WriteLine("------------------------------"); + Console.WriteLine("Starting AsyncPollingClient..."); + Console.WriteLine(); + + checkpointToken = LoadCheckpoint(); + var observer = new LambdaAsyncObserver((commit, _) => + { + // Project the commit etc + Console.WriteLine("BucketId={0};StreamId={1};CommitSequence={2}", commit.BucketId, commit.StreamId, commit.CommitSequence); + // Track the most recent checkpoint + checkpointToken = commit.CheckpointToken; + return Task.FromResult(true); + }); + var asyncClient = new AsyncPollingClient(store.Advanced, observer, waitInterval: 3000, holeDetectionWaitInterval: 100); + asyncClient.Start(checkpointToken); + + Console.WriteLine("Press any key to continue..."); + Console.ReadKey(); + asyncClient.StopAsync() + .GetAwaiter().GetResult(); + } + + private static Int64 LoadCheckpoint() + { + // Load the checkpoint value from disk / local db/ etc + return 0; + } + + private static IStoreEvents WireupEventStore() + { + var loggerFactory = LoggerFactory.Create(logging => + { + logging + .AddConsole() + .AddDebug() + .SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace); + }); + + return Wireup.Init() + .WithLoggerFactory(loggerFactory) + .UsingInMemoryPersistence() + .InitializeStorageEngine() +#if NET462 + .TrackPerformanceInstance("example") +#endif + .Build(); + } + + private static void AppendToStream(IStoreEvents store, string streamId) + { + using var stream = store.OpenStream(streamId); + var @event = new SomeDomainEvent { Value = "event" }; + + stream.Add(new EventMessage { Body = @event }); + stream.CommitChanges(Guid.NewGuid()); + } + } +} \ No newline at end of file diff --git a/src/NEventStore.PollingClientExample/NEventStore.PollingClientExample.csproj b/src/NEventStore.PollingClientExample/NEventStore.PollingClientExample.csproj new file mode 100644 index 000000000..7571208c5 --- /dev/null +++ b/src/NEventStore.PollingClientExample/NEventStore.PollingClientExample.csproj @@ -0,0 +1,32 @@ + + + + net8.0;net472 + false + + exe + + Exe + + + + TRACE;DEBUG + + + + + + + + + + + + + + + + + + + diff --git a/src/NEventStore.PollingClientExample/Properties/ProjectAssemblyInfo.cs b/src/NEventStore.PollingClientExample/Properties/ProjectAssemblyInfo.cs new file mode 100644 index 000000000..33cbc2e74 --- /dev/null +++ b/src/NEventStore.PollingClientExample/Properties/ProjectAssemblyInfo.cs @@ -0,0 +1,6 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("NEventStore.Example")] +[assembly: AssemblyDescription("")] +[assembly: Guid("6fd621e0-9047-4449-b136-249383936d5c")] \ No newline at end of file diff --git a/src/NEventStore.PollingClientExample/SomeDomainEvent.cs b/src/NEventStore.PollingClientExample/SomeDomainEvent.cs new file mode 100644 index 000000000..96a47fffb --- /dev/null +++ b/src/NEventStore.PollingClientExample/SomeDomainEvent.cs @@ -0,0 +1,7 @@ +namespace NEventStore.PollingClientExample +{ + internal class SomeDomainEvent + { + public string? Value { get; set; } + } +} diff --git a/src/NEventStore.Serialization.Binary.Tests/NEventStore.Serialization.Binary.Core.Tests.csproj b/src/NEventStore.Serialization.Binary.Tests/NEventStore.Serialization.Binary.Core.Tests.csproj new file mode 100644 index 000000000..711e8fcc8 --- /dev/null +++ b/src/NEventStore.Serialization.Binary.Tests/NEventStore.Serialization.Binary.Core.Tests.csproj @@ -0,0 +1,46 @@ + + + + net8.0;net472 + false + + exe + + NEventStore.Serialization.Binary.Tests + NEventStore.Serialization.Binary.Tests + + + + TRACE;DEBUG;NUNIT + + + NUNIT + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/NEventStore.Serialization.Binary.Tests/NUnitTestAdapterTestsDiscovery.cs b/src/NEventStore.Serialization.Binary.Tests/NUnitTestAdapterTestsDiscovery.cs new file mode 100644 index 000000000..25dac50c3 --- /dev/null +++ b/src/NEventStore.Serialization.Binary.Tests/NUnitTestAdapterTestsDiscovery.cs @@ -0,0 +1,17 @@ +using NUnit.Framework; + +namespace NEventStore.Serialization.Json.Tests +{ + /// + /// this is needed to allow NUnit test adapter to discover the tests, + /// if we do not have any class explicitly marked with the TestFixture attribute + /// all the assembly will be ignored (even if some class inherit from something which is + /// marked with the attribute in a reference assembly) + /// +#if NUNIT + [TestFixture] + public class NUnitTestAdapterTestsDiscovery + { + } +#endif +} diff --git a/src/NEventStore.Serialization.Binary.Tests/Properties/AssemblyInfo.cs b/src/NEventStore.Serialization.Binary.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..186e9e034 --- /dev/null +++ b/src/NEventStore.Serialization.Binary.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("NEventStore.Serialization.Binary.Tests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("NEventStore")] +[assembly: AssemblyCopyright("Copyright © 2013")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("5bc73f44-a60e-4b2e-984d-81f56806cc79")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +//[assembly: AssemblyVersion("8.0.0.0")] +//[assembly: AssemblyFileVersion("8.0.0.0")] +//[assembly: AssemblyInformationalVersion("8.0.0-beta.5+Branch.release-8.0.0.Sha.4029904d0dae687763bdf2f11ad4c7754927a8c6")] diff --git a/src/NEventStore.Serialization.Binary.Tests/SerializerFixture.cs b/src/NEventStore.Serialization.Binary.Tests/SerializerFixture.cs new file mode 100644 index 000000000..185b1c314 --- /dev/null +++ b/src/NEventStore.Serialization.Binary.Tests/SerializerFixture.cs @@ -0,0 +1,20 @@ +using NEventStore.Serialization.Binary; + +// ReSharper disable CheckNamespace +namespace NEventStore.Serialization.AcceptanceTests +// ReSharper restore CheckNamespace +{ + public partial class SerializerFixture + { + public SerializerFixture() + { +#if NET8_0_OR_GREATER + AppContext.SetSwitch("System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization", true); +#endif +#pragma warning disable CS0618 // Type or member is obsolete + _createSerializer = () => + new BinarySerializer(); +#pragma warning restore CS0618 // Type or member is obsolete + } + } +} \ No newline at end of file diff --git a/src/NEventStore.Serialization.Binary.Tests/packages.config b/src/NEventStore.Serialization.Binary.Tests/packages.config new file mode 100644 index 000000000..3676eab81 --- /dev/null +++ b/src/NEventStore.Serialization.Binary.Tests/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/NEventStore.Serialization.Binary/BinarySerializationWireupExtension.cs b/src/NEventStore.Serialization.Binary/BinarySerializationWireupExtension.cs new file mode 100644 index 000000000..7bb7bdfd6 --- /dev/null +++ b/src/NEventStore.Serialization.Binary/BinarySerializationWireupExtension.cs @@ -0,0 +1,25 @@ +using System.Diagnostics; + +namespace NEventStore.Serialization.Binary +{ + /// + /// Binary serialization wire-up extensions. + /// + public static class BinarySerializationWireupExtension + { + /// + /// Use the MessagePack serializer. + /// + /// Wire-up to extend + /// Serialization Wire-up + [DebuggerStepThrough] + [Obsolete("BinaryFormatter serialization is obsolete and should not be used. See https://aka.ms/binaryformatter for more information.")] + public static SerializationWireup UsingBinarySerialization(this PersistenceWireup wireup) + { +#if NET8_0_OR_GREATER + AppContext.SetSwitch("System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization", true); +#endif + return wireup.UsingCustomSerialization(new BinarySerializer()); + } + } +} diff --git a/src/NEventStore.Serialization.Binary/BinarySerializer.cs b/src/NEventStore.Serialization.Binary/BinarySerializer.cs new file mode 100644 index 000000000..775b26339 --- /dev/null +++ b/src/NEventStore.Serialization.Binary/BinarySerializer.cs @@ -0,0 +1,36 @@ +using System.Runtime.Serialization.Formatters.Binary; +using Microsoft.Extensions.Logging; +using NEventStore.Logging; + +namespace NEventStore.Serialization.Binary +{ + /// + /// Delegates to to perform the actual serialization. + /// + [Obsolete("BinaryFormatter serialization is obsolete and should not be used. See https://aka.ms/binaryformatter for more information.")] + public class BinarySerializer : ISerialize + { + private static readonly ILogger Logger = LogFactory.BuildLogger(typeof(BinarySerializer)); + private readonly BinaryFormatter _formatter = new(); + + /// + public virtual void Serialize(Stream output, T graph) where T : notnull + { + if (Logger.IsEnabled(LogLevel.Trace)) + { + Logger.LogTrace(Messages.SerializingGraph, typeof(T)); + } + _formatter.Serialize(output, graph); + } + + /// + public virtual T Deserialize(Stream input) + { + if (Logger.IsEnabled(LogLevel.Trace)) + { + Logger.LogTrace(Messages.DeserializingStream, typeof(T)); + } + return (T)_formatter.Deserialize(input); + } + } +} \ No newline at end of file diff --git a/src/proj/EventStore.Serialization.ServiceStack/Messages.Designer.cs b/src/NEventStore.Serialization.Binary/Messages.Designer.cs similarity index 90% rename from src/proj/EventStore.Serialization.ServiceStack/Messages.Designer.cs rename to src/NEventStore.Serialization.Binary/Messages.Designer.cs index 81e80df2c..80ff790b3 100644 --- a/src/proj/EventStore.Serialization.ServiceStack/Messages.Designer.cs +++ b/src/NEventStore.Serialization.Binary/Messages.Designer.cs @@ -1,81 +1,81 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.235 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace EventStore.Serialization { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Messages { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Messages() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("EventStore.Serialization.Messages", typeof(Messages).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to Deserializing stream to object of type '{0}'.. - /// - internal static string DeserializingStream { - get { - return ResourceManager.GetString("DeserializingStream", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Serializing object graph of type '{0}'.. - /// - internal static string SerializingGraph { - get { - return ResourceManager.GetString("SerializingGraph", resourceCulture); - } - } - } -} +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace NEventStore.Serialization.Binary { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Messages { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Messages() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("NEventStore.Serialization.Binary.Messages", typeof(Messages).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Deserializing stream to object of type '{0}'.. + /// + internal static string DeserializingStream { + get { + return ResourceManager.GetString("DeserializingStream", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Serializing object graph of type '{0}'.. + /// + internal static string SerializingGraph { + get { + return ResourceManager.GetString("SerializingGraph", resourceCulture); + } + } + } +} diff --git a/src/proj/EventStore.Serialization.ServiceStack/Messages.resx b/src/NEventStore.Serialization.Binary/Messages.resx similarity index 97% rename from src/proj/EventStore.Serialization.ServiceStack/Messages.resx rename to src/NEventStore.Serialization.Binary/Messages.resx index 62a032247..971eea1e1 100644 --- a/src/proj/EventStore.Serialization.ServiceStack/Messages.resx +++ b/src/NEventStore.Serialization.Binary/Messages.resx @@ -1,126 +1,126 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - Deserializing stream to object of type '{0}'. - - - Serializing object graph of type '{0}'. - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Deserializing stream to object of type '{0}'. + + + Serializing object graph of type '{0}'. + \ No newline at end of file diff --git a/src/NEventStore.Serialization.Binary/NEventStore.Serialization.Binary.Core.csproj b/src/NEventStore.Serialization.Binary/NEventStore.Serialization.Binary.Core.csproj new file mode 100644 index 000000000..09e594492 --- /dev/null +++ b/src/NEventStore.Serialization.Binary/NEventStore.Serialization.Binary.Core.csproj @@ -0,0 +1,80 @@ + + + netstandard2.0;net8.0 + false + NEventStore.Serialization.Binary + NEventStore.Serialization.Binary + + + NEventStore.Serialization.Binary + NEventStore Binary Serializers + NEventStore Dev Team + http://neventstore.org + false + This package contains Bson serializers for NEventStore library. These serializers were removed from the core package to limit dependencies from external libraries. + events, event sourcing, cqrs, storage, persistence, database + + True + true + true + snupkg + true + true + True + True + NEventStore Dev Team + icon.png + Readme.md + https://github.com/NEventStore/NEventStore + git + license.txt + True + True + latest-recommended + + + TRACE;DEBUG + + + + True + \ + + + True + \ + + + True + \ + + + + + + + + Messages.resx + True + True + + + + + NEventStore.Serialization.Binary + Messages.Designer.cs + ResXFileCodeGenerator + + + + + 9.0.0 + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + \ No newline at end of file diff --git a/src/NEventStore.Serialization.Binary/Properties/AssemblyInfo.cs b/src/NEventStore.Serialization.Binary/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..0e5d8c1c8 --- /dev/null +++ b/src/NEventStore.Serialization.Binary/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("NEventStore.Serialization.Binary")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("NEventStore.Serialization.Binary")] +[assembly: AssemblyCopyright("Copyright © 2011")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("41CFA1C0-8B82-4F50-A758-056B556679F6")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +[assembly: AssemblyVersion("0.0.0.0")] +[assembly: AssemblyFileVersion("0.0.0.0")] +[assembly: AssemblyInformationalVersion("0.0.0.0")] \ No newline at end of file diff --git a/src/NEventStore.Serialization.Bson.Tests/NEventStore.Serialization.Bson.Core.Tests.csproj b/src/NEventStore.Serialization.Bson.Tests/NEventStore.Serialization.Bson.Core.Tests.csproj new file mode 100644 index 000000000..db8d704ac --- /dev/null +++ b/src/NEventStore.Serialization.Bson.Tests/NEventStore.Serialization.Bson.Core.Tests.csproj @@ -0,0 +1,46 @@ + + + + net8.0;net472 + false + + exe + + NEventStore.Serialization.Bson.Tests + NEventStore.Serialization.Bson.Tests + + + + TRACE;DEBUG;NUNIT + + + NUNIT + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/NEventStore.Serialization.Bson.Tests/NUnitTestAdapterTestsDiscovery.cs b/src/NEventStore.Serialization.Bson.Tests/NUnitTestAdapterTestsDiscovery.cs new file mode 100644 index 000000000..e0ea056df --- /dev/null +++ b/src/NEventStore.Serialization.Bson.Tests/NUnitTestAdapterTestsDiscovery.cs @@ -0,0 +1,17 @@ +using NUnit.Framework; + +namespace NEventStore.Serialization.Bson.Tests +{ + /// + /// this is needed to allow NUnit test adapter to discover the tests, + /// if we do not have any class explicitly marked with the TestFixture attribute + /// all the assembly will be ignored (even if some class inherit from something which is + /// marked with the attribute in a reference assembly) + /// +#if NUNIT + [TestFixture] + public class NUnitTestAdapterTestsDiscovery + { + } +#endif +} diff --git a/src/NEventStore.Serialization.Bson.Tests/Properties/AssemblyInfo.cs b/src/NEventStore.Serialization.Bson.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..914757de1 --- /dev/null +++ b/src/NEventStore.Serialization.Bson.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("NEventStore.Serialization.Bson.Tests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("NEventStore")] +[assembly: AssemblyCopyright("Copyright © 2013")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("94518417-F5DE-44D7-B20B-0DD8BB86DC6C")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +//[assembly: AssemblyVersion("8.0.0.0")] +//[assembly: AssemblyFileVersion("8.0.0.0")] +//[assembly: AssemblyInformationalVersion("8.0.0-beta.5+Branch.release-8.0.0.Sha.4029904d0dae687763bdf2f11ad4c7754927a8c6")] diff --git a/src/NEventStore.Serialization.Bson.Tests/SerializerFixture.cs b/src/NEventStore.Serialization.Bson.Tests/SerializerFixture.cs new file mode 100644 index 000000000..3d9a2028a --- /dev/null +++ b/src/NEventStore.Serialization.Bson.Tests/SerializerFixture.cs @@ -0,0 +1,15 @@ +// ReSharper disable CheckNamespace +namespace NEventStore.Serialization.AcceptanceTests +// ReSharper restore CheckNamespace +{ + using NEventStore.Serialization.Bson; + + public partial class SerializerFixture + { + public SerializerFixture() + { + _createSerializer = () => + new BsonSerializer(); + } + } +} \ No newline at end of file diff --git a/src/NEventStore.Serialization.Bson.Tests/packages.config b/src/NEventStore.Serialization.Bson.Tests/packages.config new file mode 100644 index 000000000..3676eab81 --- /dev/null +++ b/src/NEventStore.Serialization.Bson.Tests/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/NEventStore.Serialization.Bson/BsonSerializationWireupExtension.cs b/src/NEventStore.Serialization.Bson/BsonSerializationWireupExtension.cs new file mode 100644 index 000000000..8b0bd99fb --- /dev/null +++ b/src/NEventStore.Serialization.Bson/BsonSerializationWireupExtension.cs @@ -0,0 +1,16 @@ +namespace NEventStore.Serialization.Bson +{ + /// + /// Bson serialization wire-up extensions. + /// + public static class BsonSerializationWireupExtension + { + /// + /// Specify we want to use Bson serialization. + /// + public static SerializationWireup UsingBsonSerialization(this PersistenceWireup wireup) + { + return wireup.UsingCustomSerialization(new BsonSerializer()); + } + } +} diff --git a/src/NEventStore.Serialization.Bson/BsonSerializer.cs b/src/NEventStore.Serialization.Bson/BsonSerializer.cs new file mode 100644 index 000000000..5c0870111 --- /dev/null +++ b/src/NEventStore.Serialization.Bson/BsonSerializer.cs @@ -0,0 +1,125 @@ +using System.Collections; +using NEventStore.Logging; +using Newtonsoft.Json.Bson; +using Newtonsoft.Json; +using Microsoft.Extensions.Logging; + +namespace NEventStore.Serialization.Bson +{ + /// + /// Represents a BSON serializer. + /// + public class BsonSerializer : ISerialize + { + private static readonly ILogger Logger = LogFactory.BuildLogger(typeof(BsonSerializer)); + + private readonly IEnumerable _knownTypes = [typeof(List), typeof(Dictionary)]; + + private readonly JsonSerializer _typedSerializer = new() + { + TypeNameHandling = TypeNameHandling.All, + DefaultValueHandling = DefaultValueHandling.Ignore, + NullValueHandling = NullValueHandling.Ignore + }; + + private readonly JsonSerializer _untypedSerializer = new() + { + TypeNameHandling = TypeNameHandling.Auto, + DefaultValueHandling = DefaultValueHandling.Ignore, + NullValueHandling = NullValueHandling.Ignore + }; + + /// + /// Initializes a new instance of the BsonSerializer class. + /// + public BsonSerializer(params Type[]? knownTypes) + { + if (knownTypes?.Length == 0) + { + knownTypes = null; + } + + _knownTypes = knownTypes ?? _knownTypes; + + if (Logger.IsEnabled(LogLevel.Debug)) + { + foreach (var type in _knownTypes) + { + Logger.LogDebug(Messages.RegisteringKnownType, type); + } + } + } + + /// + public virtual void Serialize(Stream output, T graph) where T: notnull + { + if (Logger.IsEnabled(LogLevel.Trace)) + { + Logger.LogTrace(Messages.SerializingGraph, typeof(T)); + } + using var writer = new BsonDataWriter(output) { DateTimeKindHandling = DateTimeKind.Utc }; + Serialize(writer, graph); + } + + /// + public virtual T? Deserialize(Stream input) + { + if (Logger.IsEnabled(LogLevel.Trace)) + { + Logger.LogTrace(Messages.DeserializingStream, typeof(T)); + } + using var reader = new BsonDataReader(input, IsArray(typeof(T)), DateTimeKind.Utc); + return Deserialize(reader); + } + + /// + /// Serialize an object to a JsonWriter. + /// + protected virtual void Serialize(JsonWriter writer, object graph) + { + GetSerializer(graph.GetType()).Serialize(writer, graph); + } + + /// + /// Deserialize an object from a JsonReader. + /// + protected virtual T? Deserialize(JsonReader reader) + { + Type type = typeof(T); + return (T?)GetSerializer(type).Deserialize(reader, type); + } + + /// + /// Get the serializer for the given type. + /// + protected virtual Newtonsoft.Json.JsonSerializer GetSerializer(Type typeToSerialize) + { + if (_knownTypes.Contains(typeToSerialize)) + { + if (Logger.IsEnabled(LogLevel.Trace)) + { + Logger.LogTrace(Messages.UsingUntypedSerializer, typeToSerialize); + } + return _untypedSerializer; + } + + if (Logger.IsEnabled(LogLevel.Trace)) + { + Logger.LogTrace(Messages.UsingTypedSerializer, typeToSerialize); + } + return _typedSerializer; + } + + private static bool IsArray(Type type) + { + bool array = typeof(IEnumerable).IsAssignableFrom(type) && !typeof(IDictionary).IsAssignableFrom(type); + + if (Logger.IsEnabled(LogLevel.Trace)) + { + Logger.LogTrace(Messages.TypeIsArray, type, array); + } + + return array; + } + } +} \ No newline at end of file diff --git a/src/proj/EventStore.Serialization.Json/Messages.Designer.cs b/src/NEventStore.Serialization.Bson/Messages.Designer.cs similarity index 92% rename from src/proj/EventStore.Serialization.Json/Messages.Designer.cs rename to src/NEventStore.Serialization.Bson/Messages.Designer.cs index 960255bcb..ec95a95da 100644 --- a/src/proj/EventStore.Serialization.Json/Messages.Designer.cs +++ b/src/NEventStore.Serialization.Bson/Messages.Designer.cs @@ -1,117 +1,117 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.235 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace EventStore.Serialization { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Messages { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Messages() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("EventStore.Serialization.Messages", typeof(Messages).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to Deserializing stream to object of type '{0}'.. - /// - internal static string DeserializingStream { - get { - return ResourceManager.GetString("DeserializingStream", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Registering type '{0}' as a known type.. - /// - internal static string RegisteringKnownType { - get { - return ResourceManager.GetString("RegisteringKnownType", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Serializing object graph of type '{0}'.. - /// - internal static string SerializingGraph { - get { - return ResourceManager.GetString("SerializingGraph", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Objects of type '{0}' are considered to be an array: '{1}'.. - /// - internal static string TypeIsArray { - get { - return ResourceManager.GetString("TypeIsArray", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The object to be serialized is of type '{0}'. Using a typed serializer for the unknown type.. - /// - internal static string UsingTypedSerializer { - get { - return ResourceManager.GetString("UsingTypedSerializer", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The object to be serialized is of type '{0}'. Using an untyped serializer for the known type.. - /// - internal static string UsingUntypedSerializer { - get { - return ResourceManager.GetString("UsingUntypedSerializer", resourceCulture); - } - } - } -} +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace NEventStore.Serialization.Bson { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Messages { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Messages() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("NEventStore.Serialization.Bson.Messages", typeof(Messages).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Deserializing stream to object of type '{0}'.. + /// + internal static string DeserializingStream { + get { + return ResourceManager.GetString("DeserializingStream", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Registering type '{0}' as a known type.. + /// + internal static string RegisteringKnownType { + get { + return ResourceManager.GetString("RegisteringKnownType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Serializing object graph of type '{0}'.. + /// + internal static string SerializingGraph { + get { + return ResourceManager.GetString("SerializingGraph", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Objects of type '{0}' are considered to be an array: '{1}'.. + /// + internal static string TypeIsArray { + get { + return ResourceManager.GetString("TypeIsArray", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The object to be serialized is of type '{0}'. Using a typed serializer for the unknown type.. + /// + internal static string UsingTypedSerializer { + get { + return ResourceManager.GetString("UsingTypedSerializer", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The object to be serialized is of type '{0}'. Using an untyped serializer for the known type.. + /// + internal static string UsingUntypedSerializer { + get { + return ResourceManager.GetString("UsingUntypedSerializer", resourceCulture); + } + } + } +} diff --git a/src/proj/EventStore.Serialization.Json/Messages.resx b/src/NEventStore.Serialization.Bson/Messages.resx similarity index 97% rename from src/proj/EventStore.Serialization.Json/Messages.resx rename to src/NEventStore.Serialization.Bson/Messages.resx index 7e19c9941..390f70808 100644 --- a/src/proj/EventStore.Serialization.Json/Messages.resx +++ b/src/NEventStore.Serialization.Bson/Messages.resx @@ -1,138 +1,138 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - Deserializing stream to object of type '{0}'. - - - Serializing object graph of type '{0}'. - - - The object to be serialized is of type '{0}'. Using a typed serializer for the unknown type. - - - Registering type '{0}' as a known type. - - - The object to be serialized is of type '{0}'. Using an untyped serializer for the known type. - - - Objects of type '{0}' are considered to be an array: '{1}'. - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Deserializing stream to object of type '{0}'. + + + Serializing object graph of type '{0}'. + + + Registering type '{0}' as a known type. + + + Objects of type '{0}' are considered to be an array: '{1}'. + + + The object to be serialized is of type '{0}'. Using a typed serializer for the unknown type. + + + The object to be serialized is of type '{0}'. Using an untyped serializer for the known type. + \ No newline at end of file diff --git a/src/NEventStore.Serialization.Bson/NEventStore.Serialization.Bson.Core.csproj b/src/NEventStore.Serialization.Bson/NEventStore.Serialization.Bson.Core.csproj new file mode 100644 index 000000000..655bfd458 --- /dev/null +++ b/src/NEventStore.Serialization.Bson/NEventStore.Serialization.Bson.Core.csproj @@ -0,0 +1,77 @@ + + + netstandard2.0 + false + NEventStore.Serialization.Bson + NEventStore.Serialization.Bson + + + NEventStore.Serialization.Bson + NEventStore Bson Serializers + NEventStore Dev Team + http://neventstore.org + false + This package contains Bson serializers for NEventStore library. These serializers were removed from the core package to limit dependencies from external libraries. + events, event sourcing, cqrs, storage, persistence, database + + True + true + true + snupkg + true + true + True + True + NEventStore Dev Team + icon.png + Readme.md + https://github.com/NEventStore/NEventStore + git + license.txt + True + True + latest-recommended + + + TRACE;DEBUG + + + + True + \ + + + True + \ + + + True + \ + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + True + True + Messages.resx + + + + + ResXFileCodeGenerator + Messages.Designer.cs + NEventStore.Serialization.Bson + + + \ No newline at end of file diff --git a/src/NEventStore.Serialization.Bson/Properties/AssemblyInfo.cs b/src/NEventStore.Serialization.Bson/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..8d6722a14 --- /dev/null +++ b/src/NEventStore.Serialization.Bson/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("NEventStore.Serialization.Bson")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("NEventStore.Serialization.Bson")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("7bbb9172-7b2c-4854-9b59-952b54122632")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +[assembly: AssemblyVersion("0.0.0.0")] +[assembly: AssemblyFileVersion("0.0.0.0")] +[assembly: AssemblyInformationalVersion("0.0.0.0")] diff --git a/src/NEventStore.Serialization.Gzip.Tests/NEventStore.Serialization.GZip.Core.Tests.csproj b/src/NEventStore.Serialization.Gzip.Tests/NEventStore.Serialization.GZip.Core.Tests.csproj new file mode 100644 index 000000000..cd5532795 --- /dev/null +++ b/src/NEventStore.Serialization.Gzip.Tests/NEventStore.Serialization.GZip.Core.Tests.csproj @@ -0,0 +1,47 @@ + + + + net8.0;net472 + false + + exe + + NEventStore.Serialization.GZip.Tests + NEventStore.Serialization.GZip.Tests + + + + TRACE;DEBUG;NUNIT + + + NUNIT + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/NEventStore.Serialization.Gzip.Tests/NUnitTestAdapterTestsDiscovery.cs b/src/NEventStore.Serialization.Gzip.Tests/NUnitTestAdapterTestsDiscovery.cs new file mode 100644 index 000000000..b8ee80d9f --- /dev/null +++ b/src/NEventStore.Serialization.Gzip.Tests/NUnitTestAdapterTestsDiscovery.cs @@ -0,0 +1,17 @@ +using NUnit.Framework; + +namespace NEventStore.Serialization.GZip.Tests +{ + /// + /// this is needed to allow NUnit test adapter to discover the tests, + /// if we do not have any class explicitly marked with the TestFixture attribute + /// all the assembly will be ignored (even if some class inherit from something which is + /// marked with the attribute in a reference assembly) + /// +#if NUNIT + [TestFixture] + public class NUnitTestAdapterTestsDiscovery + { + } +#endif +} diff --git a/src/NEventStore.Serialization.Gzip.Tests/Properties/AssemblyInfo.cs b/src/NEventStore.Serialization.Gzip.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..cddbe103b --- /dev/null +++ b/src/NEventStore.Serialization.Gzip.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,34 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("NEventStore.Serialization.Gzip.Tests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyCopyright("Copyright © 2013")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("5BB89C20-A481-4A15-9478-C0F69E419239")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +//[assembly: AssemblyVersion("8.0.0.0")] +//[assembly: AssemblyFileVersion("8.0.0.0")] +//[assembly: AssemblyInformationalVersion("8.0.0-beta.5+Branch.release-8.0.0.Sha.4029904d0dae687763bdf2f11ad4c7754927a8c6")] diff --git a/src/NEventStore.Serialization.Gzip.Tests/SerializerFixture.cs b/src/NEventStore.Serialization.Gzip.Tests/SerializerFixture.cs new file mode 100644 index 000000000..a6a348ecd --- /dev/null +++ b/src/NEventStore.Serialization.Gzip.Tests/SerializerFixture.cs @@ -0,0 +1,20 @@ +// ReSharper disable CheckNamespace +using NEventStore.Serialization.Binary; + +namespace NEventStore.Serialization.AcceptanceTests +// ReSharper restore CheckNamespace +{ + public partial class SerializerFixture + { + public SerializerFixture() + { +#if NET8_0_OR_GREATER + AppContext.SetSwitch("System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization", true); +#endif +#pragma warning disable CS0618 // Type or member is obsolete + _createSerializer = () => + new GzipSerializer(new BinarySerializer()); +#pragma warning restore CS0618 // Type or member is obsolete + } + } +} \ No newline at end of file diff --git a/src/NEventStore.Serialization.Gzip.Tests/packages.config b/src/NEventStore.Serialization.Gzip.Tests/packages.config new file mode 100644 index 000000000..3676eab81 --- /dev/null +++ b/src/NEventStore.Serialization.Gzip.Tests/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/NEventStore.Serialization.Json.Tests/NEventStore.Serialization.Json.Core.Tests.csproj b/src/NEventStore.Serialization.Json.Tests/NEventStore.Serialization.Json.Core.Tests.csproj new file mode 100644 index 000000000..a751aefbf --- /dev/null +++ b/src/NEventStore.Serialization.Json.Tests/NEventStore.Serialization.Json.Core.Tests.csproj @@ -0,0 +1,46 @@ + + + + net8.0;net472 + false + + exe + + NEventStore.Serialization.Json.Tests + NEventStore.Serialization.Json.Tests + + + + TRACE;DEBUG;NUNIT + + + NUNIT + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/NEventStore.Serialization.Json.Tests/NUnitTestAdapterTestsDiscovery.cs b/src/NEventStore.Serialization.Json.Tests/NUnitTestAdapterTestsDiscovery.cs new file mode 100644 index 000000000..e6f66c26f --- /dev/null +++ b/src/NEventStore.Serialization.Json.Tests/NUnitTestAdapterTestsDiscovery.cs @@ -0,0 +1,17 @@ +using NUnit.Framework; + +namespace NEventStore.Serialization.Json.Tests +{ + /// + /// this is needed to allow NUnit test adapter to discover the tests, + /// if we do not have any class explicitly marked with the TestFixture attribute + /// all the assembly will be ignored (even if some class inherit from something which is + /// marked with the attribute in a reference assembly) + /// +#if NUNIT + [TestFixture] + public class NUnitTestAdapterTestsDiscovery + { + } +#endif +} diff --git a/src/NEventStore.Serialization.Json.Tests/Properties/AssemblyInfo.cs b/src/NEventStore.Serialization.Json.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..f8bf36ef2 --- /dev/null +++ b/src/NEventStore.Serialization.Json.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("NEventStore.Serialization.Json.Tests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("NEventStore")] +[assembly: AssemblyCopyright("Copyright © 2013")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("9775F040-BA9C-48C1-BB16-1A1D6B86A3F1")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +//[assembly: AssemblyVersion("8.0.0.0")] +//[assembly: AssemblyFileVersion("8.0.0.0")] +//[assembly: AssemblyInformationalVersion("8.0.0-beta.5+Branch.release-8.0.0.Sha.4029904d0dae687763bdf2f11ad4c7754927a8c6")] diff --git a/src/NEventStore.Serialization.Json.Tests/SerializerFixture.cs b/src/NEventStore.Serialization.Json.Tests/SerializerFixture.cs new file mode 100644 index 000000000..040a7c2c6 --- /dev/null +++ b/src/NEventStore.Serialization.Json.Tests/SerializerFixture.cs @@ -0,0 +1,15 @@ +using NEventStore.Serialization.Json; + +// ReSharper disable CheckNamespace +namespace NEventStore.Serialization.AcceptanceTests +// ReSharper restore CheckNamespace +{ + public partial class SerializerFixture + { + public SerializerFixture() + { + _createSerializer = () => + new JsonSerializer(null); + } + } +} \ No newline at end of file diff --git a/src/NEventStore.Serialization.Json.Tests/packages.config b/src/NEventStore.Serialization.Json.Tests/packages.config new file mode 100644 index 000000000..dff6f66d1 --- /dev/null +++ b/src/NEventStore.Serialization.Json.Tests/packages.config @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/NEventStore.Serialization.Json/JsonSerializationWireupExtension.cs b/src/NEventStore.Serialization.Json/JsonSerializationWireupExtension.cs new file mode 100644 index 000000000..503ccaa41 --- /dev/null +++ b/src/NEventStore.Serialization.Json/JsonSerializationWireupExtension.cs @@ -0,0 +1,29 @@ +using Newtonsoft.Json; + +namespace NEventStore.Serialization.Json +{ + /// + /// Newtonsoft Json serialization wire-up extensions. + /// + public static class JsonSerializationWireupExtension + { + /// + /// Specify we want to use Json serialization using Newtonsoft.Json + /// + /// The persistence wire-up. + /// + /// Allows to customize some Serializer options, however some of them will + /// be under control of this specific implementation and will be overwritten no matter + /// what the user specifies: + /// - TypeNameHandling = TypeNameHandling.All + /// - DefaultValueHandling = DefaultValueHandling.Ignore + /// - NullValueHandling = NullValueHandling.Ignore + /// + public static SerializationWireup UsingJsonSerialization( + this PersistenceWireup wireup, + JsonSerializerSettings? jsonSerializerSettings = null) + { + return wireup.UsingCustomSerialization(new JsonSerializer(jsonSerializerSettings)); + } + } +} diff --git a/src/NEventStore.Serialization.Json/JsonSerializer.cs b/src/NEventStore.Serialization.Json/JsonSerializer.cs new file mode 100644 index 000000000..fa5cfdc90 --- /dev/null +++ b/src/NEventStore.Serialization.Json/JsonSerializer.cs @@ -0,0 +1,125 @@ +using System.Text; +using Newtonsoft.Json; +using NEventStore.Logging; +using Microsoft.Extensions.Logging; + +namespace NEventStore.Serialization.Json +{ + /// + /// The Newtonsoft JSON serializer. + /// + public class JsonSerializer : ISerialize + { + private static readonly ILogger Logger = LogFactory.BuildLogger(typeof(JsonSerializer)); + private readonly IEnumerable _knownTypes = [typeof(List), typeof(Dictionary)]; + + private readonly Newtonsoft.Json.JsonSerializer _typedSerializer; + + private readonly Newtonsoft.Json.JsonSerializer _untypedSerializer; + + /// + /// NEventStore Json Serialization using Newtonsoft.Json + /// + /// Allows to configure some Json serialization options, + /// some of them will be overwritten given the values passed to parameter + /// + /// Every Type specified here will be serialized with: + /// - TypeNameHandling = TypeNameHandling.Auto + /// - DefaultValueHandling = DefaultValueHandling.Ignore + /// - NullValueHandling = NullValueHandling.Ignore + /// Every other type will be serialized with: + /// - TypeNameHandling = TypeNameHandling.All + /// - DefaultValueHandling = DefaultValueHandling.Ignore + /// - NullValueHandling = NullValueHandling.Ignore + /// + public JsonSerializer(JsonSerializerSettings? jsonSerializerSettings, params Type[]? knownTypes) + { + if (knownTypes?.Length == 0) + { + knownTypes = null; + } + + _knownTypes = knownTypes ?? _knownTypes; + + _typedSerializer = Newtonsoft.Json.JsonSerializer.Create(jsonSerializerSettings); + _typedSerializer.TypeNameHandling = TypeNameHandling.All; + _typedSerializer.DefaultValueHandling = DefaultValueHandling.Ignore; + _typedSerializer.NullValueHandling = NullValueHandling.Ignore; + + _untypedSerializer = Newtonsoft.Json.JsonSerializer.Create(jsonSerializerSettings); + _untypedSerializer.TypeNameHandling = TypeNameHandling.Auto; + _untypedSerializer.DefaultValueHandling = DefaultValueHandling.Ignore; + _untypedSerializer.NullValueHandling = NullValueHandling.Ignore; + + if (Logger.IsEnabled(LogLevel.Debug)) + { + foreach (var type in _knownTypes) + { + Logger.LogDebug(Messages.RegisteringKnownType, type); + } + } + } + + /// + public virtual void Serialize(Stream output, T graph) where T: notnull + { + if (Logger.IsEnabled(LogLevel.Trace)) + { + Logger.LogTrace(Messages.SerializingGraph, typeof(T)); + } + using var streamWriter = new StreamWriter(output, Encoding.UTF8); + using var jsonTextWriter = new JsonTextWriter(streamWriter); + Serialize(jsonTextWriter, graph); + } + + /// + public virtual T? Deserialize(Stream input) + { + if (Logger.IsEnabled(LogLevel.Trace)) + { + Logger.LogTrace(Messages.DeserializingStream, typeof(T)); + } + using var streamReader = new StreamReader(input, Encoding.UTF8); + using var jsonTextReader = new JsonTextReader(streamReader); + return Deserialize(jsonTextReader); + } + + /// + /// Serialize an object to a JsonWriter. + /// + protected virtual void Serialize(JsonWriter writer, object graph) + { + GetSerializer(graph.GetType()).Serialize(writer, graph); + } + + /// + /// Deserialize an object from a JsonReader. + /// + protected virtual T? Deserialize(JsonReader reader) + { + Type type = typeof(T); + return (T?)GetSerializer(type).Deserialize(reader, type); + } + + /// + /// Get the serializer for the given type. + /// + protected virtual Newtonsoft.Json.JsonSerializer GetSerializer(Type typeToSerialize) + { + if (_knownTypes.Contains(typeToSerialize)) + { + if (Logger.IsEnabled(LogLevel.Trace)) + { + Logger.LogTrace(Messages.UsingUntypedSerializer, typeToSerialize); + } + return _untypedSerializer; + } + + if (Logger.IsEnabled(LogLevel.Trace)) + { + Logger.LogTrace(Messages.UsingTypedSerializer, typeToSerialize); + } + return _typedSerializer; + } + } +} \ No newline at end of file diff --git a/src/NEventStore.Serialization.Json/Messages.Designer.cs b/src/NEventStore.Serialization.Json/Messages.Designer.cs new file mode 100644 index 000000000..533c6643a --- /dev/null +++ b/src/NEventStore.Serialization.Json/Messages.Designer.cs @@ -0,0 +1,108 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace NEventStore.Serialization.Json { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Messages { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Messages() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("NEventStore.Serialization.Json.Messages", typeof(Messages).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Deserializing stream to object of type '{0}'.. + /// + internal static string DeserializingStream { + get { + return ResourceManager.GetString("DeserializingStream", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Registering type '{0}' as a known type.. + /// + internal static string RegisteringKnownType { + get { + return ResourceManager.GetString("RegisteringKnownType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Serializing object graph of type '{0}'.. + /// + internal static string SerializingGraph { + get { + return ResourceManager.GetString("SerializingGraph", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The object to be serialized is of type '{0}'. Using a typed serializer for the unknown type.. + /// + internal static string UsingTypedSerializer { + get { + return ResourceManager.GetString("UsingTypedSerializer", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The object to be serialized is of type '{0}'. Using an untyped serializer for the known type.. + /// + internal static string UsingUntypedSerializer { + get { + return ResourceManager.GetString("UsingUntypedSerializer", resourceCulture); + } + } + } +} diff --git a/src/NEventStore.Serialization.Json/Messages.resx b/src/NEventStore.Serialization.Json/Messages.resx new file mode 100644 index 000000000..34a0c4cfe --- /dev/null +++ b/src/NEventStore.Serialization.Json/Messages.resx @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Deserializing stream to object of type '{0}'. + + + Serializing object graph of type '{0}'. + + + Registering type '{0}' as a known type. + + + The object to be serialized is of type '{0}'. Using a typed serializer for the unknown type. + + + The object to be serialized is of type '{0}'. Using an untyped serializer for the known type. + + \ No newline at end of file diff --git a/src/NEventStore.Serialization.Json/NEventStore.Serialization.Json.Core.csproj b/src/NEventStore.Serialization.Json/NEventStore.Serialization.Json.Core.csproj new file mode 100644 index 000000000..62eb557e3 --- /dev/null +++ b/src/NEventStore.Serialization.Json/NEventStore.Serialization.Json.Core.csproj @@ -0,0 +1,76 @@ + + + netstandard2.0 + false + NEventStore.Serialization.Json + NEventStore.Serialization.Json + + + NEventStore.Serialization.Json + NEventStore Json Serializers + NEventStore Dev Team + http://neventstore.org + false + This package contains Json serializers for NEventStore library. These serializers were removed from the core package to limit dependencies from external libraries. + events, event sourcing, cqrs, storage, persistence, database + + True + true + true + snupkg + true + true + True + True + NEventStore Dev Team + icon.png + Readme.md + https://github.com/NEventStore/NEventStore + git + license.txt + True + True + latest-recommended + + + TRACE;DEBUG + + + + True + \ + + + True + \ + + + True + \ + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + True + True + Messages.resx + + + + + ResXFileCodeGenerator + Messages.Designer.cs + NEventStore.Serialization.Json + + + \ No newline at end of file diff --git a/src/NEventStore.Serialization.Json/Properties/AssemblyInfo.cs b/src/NEventStore.Serialization.Json/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..1e6a65202 --- /dev/null +++ b/src/NEventStore.Serialization.Json/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("NEventStore.Serialization.Json")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("NEventStore.Serialization.Json")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("7bbb9172-7b2c-4854-9b59-952b54122632")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +[assembly: AssemblyVersion("0.0.0.0")] +[assembly: AssemblyFileVersion("0.0.0.0")] +[assembly: AssemblyInformationalVersion("0.0.0.0")] diff --git a/src/NEventStore.Serialization.MsgPack.Tests/NEventStore.Serialization.MsgPack.Core.Tests.csproj b/src/NEventStore.Serialization.MsgPack.Tests/NEventStore.Serialization.MsgPack.Core.Tests.csproj new file mode 100644 index 000000000..5f948e032 --- /dev/null +++ b/src/NEventStore.Serialization.MsgPack.Tests/NEventStore.Serialization.MsgPack.Core.Tests.csproj @@ -0,0 +1,46 @@ + + + + net8.0;net472 + false + + exe + + NEventStore.Serialization.MsgPack.Tests + NEventStore.Serialization.MsgPack.Tests + + + + TRACE;DEBUG;NUNIT + + + NUNIT + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/NEventStore.Serialization.MsgPack.Tests/NUnitTestAdapterTestsDiscovery.cs b/src/NEventStore.Serialization.MsgPack.Tests/NUnitTestAdapterTestsDiscovery.cs new file mode 100644 index 000000000..e78b055bf --- /dev/null +++ b/src/NEventStore.Serialization.MsgPack.Tests/NUnitTestAdapterTestsDiscovery.cs @@ -0,0 +1,17 @@ +using NUnit.Framework; + +namespace NEventStore.Serialization.MsgPack.Tests +{ + /// + /// this is needed to allow NUnit test adapter to discover the tests, + /// if we do not have any class explicitly marked with the TestFixture attribute + /// all the assembly will be ignored (even if some class inherit from something which is + /// marked with the attribute in a reference assembly) + /// +#if NUNIT + [TestFixture] + public class NUnitTestAdapterTestsDiscovery + { + } +#endif +} diff --git a/src/NEventStore.Serialization.MsgPack.Tests/Properties/AssemblyInfo.cs b/src/NEventStore.Serialization.MsgPack.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..41a702b36 --- /dev/null +++ b/src/NEventStore.Serialization.MsgPack.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("NEventStore.Serialization.MsgPack.Tests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("NEventStore")] +[assembly: AssemblyCopyright("Copyright © 2013")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("646D1249-63B9-46AD-8F9D-BFC58D3AC6F1")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +//[assembly: AssemblyVersion("8.0.0.0")] +//[assembly: AssemblyFileVersion("8.0.0.0")] +//[assembly: AssemblyInformationalVersion("8.0.0-beta.5+Branch.release-8.0.0.Sha.4029904d0dae687763bdf2f11ad4c7754927a8c6")] diff --git a/src/NEventStore.Serialization.MsgPack.Tests/SerializerFixture.cs b/src/NEventStore.Serialization.MsgPack.Tests/SerializerFixture.cs new file mode 100644 index 000000000..76b30107f --- /dev/null +++ b/src/NEventStore.Serialization.MsgPack.Tests/SerializerFixture.cs @@ -0,0 +1,15 @@ +// ReSharper disable CheckNamespace +namespace NEventStore.Serialization.AcceptanceTests +// ReSharper restore CheckNamespace +{ + using NEventStore.Serialization.MsgPack; + + public partial class SerializerFixture + { + public SerializerFixture() + { + _createSerializer = () => + new MsgPackSerializer(); + } + } +} \ No newline at end of file diff --git a/src/NEventStore.Serialization.MsgPack.Tests/packages.config b/src/NEventStore.Serialization.MsgPack.Tests/packages.config new file mode 100644 index 000000000..ff6f4c98d --- /dev/null +++ b/src/NEventStore.Serialization.MsgPack.Tests/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/doc/EventStore.Example/Resources.Designer.cs b/src/NEventStore.Serialization.MsgPack/Messages.Designer.cs similarity index 68% rename from doc/EventStore.Example/Resources.Designer.cs rename to src/NEventStore.Serialization.MsgPack/Messages.Designer.cs index f64d3d036..44d7751fb 100644 --- a/doc/EventStore.Example/Resources.Designer.cs +++ b/src/NEventStore.Serialization.MsgPack/Messages.Designer.cs @@ -1,14 +1,14 @@ //------------------------------------------------------------------------------ // // This code was generated by a tool. -// Runtime Version:4.0.30319.235 +// Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ -namespace EventStore.Example { +namespace NEventStore.Serialization.MsgPack { using System; @@ -19,17 +19,17 @@ namespace EventStore.Example { // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources { + internal class Messages { private static global::System.Resources.ResourceManager resourceMan; private static global::System.Globalization.CultureInfo resourceCulture; [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Resources() { + internal Messages() { } /// @@ -39,7 +39,7 @@ internal Resources() { internal static global::System.Resources.ResourceManager ResourceManager { get { if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("EventStore.Example.Resources", typeof(Resources).Assembly); + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("NEventStore.Serialization.MsgPack.Messages", typeof(Messages).Assembly); resourceMan = temp; } return resourceMan; @@ -61,29 +61,20 @@ internal Resources() { } /// - /// Looks up a localized string similar to Messages from commit have been dispatched: . + /// Looks up a localized string similar to Deserializing stream to object of type '{0}'.. /// - internal static string MessagesDispatched { + internal static string DeserializingStream { get { - return ResourceManager.GetString("MessagesDispatched", resourceCulture); + return ResourceManager.GetString("DeserializingStream", resourceCulture); } } /// - /// Looks up a localized string similar to Press any key to continue.... + /// Looks up a localized string similar to Serializing object graph of type '{0}'.. /// - internal static string PressAnyKey { + internal static string SerializingGraph { get { - return ResourceManager.GetString("PressAnyKey", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to If for some reason we are unable to dispatch, we'd just handle it here.. - /// - internal static string UnableToDispatch { - get { - return ResourceManager.GetString("UnableToDispatch", resourceCulture); + return ResourceManager.GetString("SerializingGraph", resourceCulture); } } } diff --git a/doc/EventStore.Example/Resources.resx b/src/NEventStore.Serialization.MsgPack/Messages.resx similarity index 91% rename from doc/EventStore.Example/Resources.resx rename to src/NEventStore.Serialization.MsgPack/Messages.resx index a061896af..971eea1e1 100644 --- a/doc/EventStore.Example/Resources.resx +++ b/src/NEventStore.Serialization.MsgPack/Messages.resx @@ -117,13 +117,10 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - Messages from commit have been dispatched: + + Deserializing stream to object of type '{0}'. - - Press any key to continue... - - - If for some reason we are unable to dispatch, we'd just handle it here. + + Serializing object graph of type '{0}'. \ No newline at end of file diff --git a/src/NEventStore.Serialization.MsgPack/MsgPackSerializationWireupExtension.cs b/src/NEventStore.Serialization.MsgPack/MsgPackSerializationWireupExtension.cs new file mode 100644 index 000000000..e26d20f1a --- /dev/null +++ b/src/NEventStore.Serialization.MsgPack/MsgPackSerializationWireupExtension.cs @@ -0,0 +1,20 @@ +using MessagePack; +using System.Diagnostics; + +namespace NEventStore.Serialization.MsgPack +{ + /// + /// MsgPack serialization wire-up extensions. + /// + public static class MsgPackSerializationWireupExtension + { + /// + /// Use the MessagePack serializer. + /// + /// Wire-up to extend + /// MsgPack serialization options + /// Serialization Wire-up + [DebuggerStepThrough] + public static SerializationWireup UsingMsgPackSerialization(this PersistenceWireup wireup, MessagePackSerializerOptions? option = null) => wireup.UsingCustomSerialization(new MsgPackSerializer(option)); + } +} diff --git a/src/NEventStore.Serialization.MsgPack/MsgPackSerializer.cs b/src/NEventStore.Serialization.MsgPack/MsgPackSerializer.cs new file mode 100644 index 000000000..fa61054db --- /dev/null +++ b/src/NEventStore.Serialization.MsgPack/MsgPackSerializer.cs @@ -0,0 +1,58 @@ +using MessagePack; +using MessagePack.Resolvers; +using Microsoft.Extensions.Logging; +using NEventStore.Logging; + +namespace NEventStore.Serialization.MsgPack +{ + /// + /// MsgPack serializer + /// + public class MsgPackSerializer : ISerialize + { + private static readonly ILogger Logger = LogFactory.BuildLogger(typeof(MsgPackSerializer)); + /// + /// Serializer options + /// + private readonly MessagePackSerializerOptions _options; + + /// + /// Initializes a new instance. + /// + /// MsgPack Options. + public MsgPackSerializer(MessagePackSerializerOptions? options = null) + { + _options = options ?? new MessagePackSerializerOptions(TypelessContractlessStandardResolver.Instance); + } + + /// + /// Serializes an object. + /// + /// Object type + /// Output stream + /// Object to deserialize. + public virtual void Serialize(Stream output, T graph) where T : notnull + { + if (Logger.IsEnabled(LogLevel.Trace)) + { + Logger.LogTrace(Messages.SerializingGraph, typeof(T)); + } + MessagePackSerializer.Serialize(output, graph, _options); + } + + /// + /// Deserializes an object from stream. + /// + /// Object type. + /// Stream input + /// Deserialized object + public virtual T Deserialize(Stream input) + { + if (Logger.IsEnabled(LogLevel.Trace)) + { + Logger.LogTrace(Messages.DeserializingStream, typeof(T)); + } + return MessagePackSerializer.Deserialize(input, _options); + } + } +} \ No newline at end of file diff --git a/src/NEventStore.Serialization.MsgPack/NEventStore.Serialization.MsgPack.Core.csproj b/src/NEventStore.Serialization.MsgPack/NEventStore.Serialization.MsgPack.Core.csproj new file mode 100644 index 000000000..00d1a0999 --- /dev/null +++ b/src/NEventStore.Serialization.MsgPack/NEventStore.Serialization.MsgPack.Core.csproj @@ -0,0 +1,76 @@ + + + netstandard2.0 + false + NEventStore.Serialization.MsgPack + NEventStore.Serialization.MsgPack + + + NEventStore.Serialization.MsgPack + NEventStore MsgPack Serializers + NEventStore Dev Team + http://neventstore.org + false + This package contains Bson serializers for NEventStore library. These serializers were removed from the core package to limit dependencies from external libraries. + events, event sourcing, cqrs, storage, persistence, database + + True + true + true + snupkg + true + true + True + True + NEventStore Dev Team + icon.png + Readme.md + https://github.com/NEventStore/NEventStore + git + license.txt + True + True + latest-recommended + + + TRACE;DEBUG + + + + True + \ + + + True + \ + + + True + \ + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + True + True + Messages.resx + + + + + ResXFileCodeGenerator + Messages.Designer.cs + NEventStore.Serialization.MsgPack + + + \ No newline at end of file diff --git a/src/NEventStore.Serialization.MsgPack/Properties/AssemblyInfo.cs b/src/NEventStore.Serialization.MsgPack/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..c6ab41871 --- /dev/null +++ b/src/NEventStore.Serialization.MsgPack/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("NEventStore.Serialization.MsgPack")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("NEventStore.Serialization.MsgPack")] +[assembly: AssemblyCopyright("Copyright © 2024")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("b0b4110c-9285-4c18-ba2c-e5d4b1731b5b")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +[assembly: AssemblyVersion("0.0.0.0")] +[assembly: AssemblyFileVersion("0.0.0.0")] +[assembly: AssemblyInformationalVersion("0.0.0.0")] \ No newline at end of file diff --git a/src/NEventStore.Serialization.Rijndael.Tests/NEventStore.Serialization.Rijndael.Core.Tests.csproj b/src/NEventStore.Serialization.Rijndael.Tests/NEventStore.Serialization.Rijndael.Core.Tests.csproj new file mode 100644 index 000000000..2474df0c5 --- /dev/null +++ b/src/NEventStore.Serialization.Rijndael.Tests/NEventStore.Serialization.Rijndael.Core.Tests.csproj @@ -0,0 +1,46 @@ + + + + net8.0;net472 + false + + exe + + NEventStore.Serialization.Rijndael.Tests + NEventStore.Serialization.Rijndael.Tests + + + + TRACE;DEBUG;NUNIT + + + NUNIT + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/NEventStore.Serialization.Rijndael.Tests/NUnitTestAdapterTestsDiscovery.cs b/src/NEventStore.Serialization.Rijndael.Tests/NUnitTestAdapterTestsDiscovery.cs new file mode 100644 index 000000000..25dac50c3 --- /dev/null +++ b/src/NEventStore.Serialization.Rijndael.Tests/NUnitTestAdapterTestsDiscovery.cs @@ -0,0 +1,17 @@ +using NUnit.Framework; + +namespace NEventStore.Serialization.Json.Tests +{ + /// + /// this is needed to allow NUnit test adapter to discover the tests, + /// if we do not have any class explicitly marked with the TestFixture attribute + /// all the assembly will be ignored (even if some class inherit from something which is + /// marked with the attribute in a reference assembly) + /// +#if NUNIT + [TestFixture] + public class NUnitTestAdapterTestsDiscovery + { + } +#endif +} diff --git a/src/NEventStore.Serialization.Rijndael.Tests/Properties/AssemblyInfo.cs b/src/NEventStore.Serialization.Rijndael.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..d59819ba7 --- /dev/null +++ b/src/NEventStore.Serialization.Rijndael.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("NEventStore.Serialization.Rijndael.Tests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("NEventStore")] +[assembly: AssemblyCopyright("Copyright © 2013")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("A4E30DF3-EE23-4C74-B03B-FB438C66B688")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +//[assembly: AssemblyVersion("8.0.0.0")] +//[assembly: AssemblyFileVersion("8.0.0.0")] +//[assembly: AssemblyInformationalVersion("8.0.0-beta.5+Branch.release-8.0.0.Sha.4029904d0dae687763bdf2f11ad4c7754927a8c6")] diff --git a/src/NEventStore.Serialization.Rijndael.Tests/SerializerFixture.cs b/src/NEventStore.Serialization.Rijndael.Tests/SerializerFixture.cs new file mode 100644 index 000000000..fd77acd63 --- /dev/null +++ b/src/NEventStore.Serialization.Rijndael.Tests/SerializerFixture.cs @@ -0,0 +1,22 @@ +// ReSharper disable CheckNamespace +using NEventStore.Serialization.Binary; + +namespace NEventStore.Serialization.AcceptanceTests +// ReSharper restore CheckNamespace +{ + public partial class SerializerFixture + { + private static readonly byte[] EncryptionKey = new byte[] + {0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0x0}; + + public SerializerFixture() + { +#if NET8_0_OR_GREATER + AppContext.SetSwitch("System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization", true); +#endif +#pragma warning disable CS0618 // Type or member is obsolete + _createSerializer = () => new RijndaelSerializer(new BinarySerializer(), EncryptionKey); +#pragma warning restore CS0618 // Type or member is obsolete + } + } +} \ No newline at end of file diff --git a/src/NEventStore.Serialization.Rijndael.Tests/packages.config b/src/NEventStore.Serialization.Rijndael.Tests/packages.config new file mode 100644 index 000000000..3676eab81 --- /dev/null +++ b/src/NEventStore.Serialization.Rijndael.Tests/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/NEventStore.Tests/Client/AsyncPollingClientTests.cs b/src/NEventStore.Tests/Client/AsyncPollingClientTests.cs new file mode 100644 index 000000000..6af985a44 --- /dev/null +++ b/src/NEventStore.Tests/Client/AsyncPollingClientTests.cs @@ -0,0 +1,203 @@ +#pragma warning disable IDE1006 // Naming Styles + +using FakeItEasy; +using NEventStore.Persistence; +using NEventStore.Persistence.AcceptanceTests; +using NEventStore.Persistence.AcceptanceTests.BDD; +using FluentAssertions; +#if MSTEST +using Microsoft.VisualStudio.TestTools.UnitTesting; +#endif +#if NUNIT +#endif +#if XUNIT +using Xunit; +using Xunit.Should; +#endif + +namespace NEventStore.PollingClient.Async +{ +#if MSTEST + [TestClass] +#endif + public class Creating_AsyncPollingClient_Tests + { + [Fact] + public void When_persist_streams_is_null_then_should_throw() + { +#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. + Catch.Exception(() => new AsyncPollingClient(null, new CommitStreamObserver()).Should().BeOfType()); +#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. + } + + [Fact] + public void When_interval_less_than_zero_then_should_throw() + { +#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. + Catch.Exception(() => new AsyncPollingClient(A.Fake(), null)).Should().BeOfType(); +#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. + } + } + +#if MSTEST + [TestClass] +#endif + public class base_handling_committed_events : using_AsyncPollingClient + { + private readonly List commits = []; + + protected override void Context() + { + Observer = new LambdaAsyncObserver((c, _) => { commits.Add(c); return Task.FromResult(true); }); + base.Context(); + StoreEvents.Advanced.CommitSingle(); + } + + protected override void Because() + { + Sut.Start(0); + } + + [Fact] + public void commits_are_correctly_dispatched() + { + WaitForCondition(() => commits.Count >= 1); + commits.Count.Should().Be(1); + } + } + +#if MSTEST + [TestClass] +#endif + public class base_handling_committed_events_and_new_events : using_AsyncPollingClient + { + private readonly List commits = []; + + protected override void Context() + { + Observer = new LambdaAsyncObserver((c, _) => { commits.Add(c); return Task.FromResult(true); }); + base.Context(); + StoreEvents.Advanced.CommitSingle(); + } + + protected override void Because() + { + Sut.Start(0); + for (int i = 0; i < 15; i++) + { + StoreEvents.Advanced.CommitSingle(); + } + } + + [Fact] + public void commits_are_correctly_dispatched() + { + WaitForCondition(() => commits.Count >= 16); + commits.Count.Should().Be(16); + } + } + +#if MSTEST + [TestClass] +#endif + public class verify_stopping_commit_polling_client : using_AsyncPollingClient + { + private readonly List commits = []; + + protected override void Context() + { + Observer = new LambdaAsyncObserver((c, _) => { commits.Add(c); return Task.FromResult(false); }); + base.Context(); + StoreEvents.Advanced.CommitSingle(); + StoreEvents.Advanced.CommitSingle(); + StoreEvents.Advanced.CommitSingle(); + } + + protected override void Because() + { + Sut.Start(0); + } + + [Fact] + public void commits_are_correctly_dispatched() + { + WaitForCondition(() => commits.Count >= 2, timeoutInSeconds: 1); + commits.Count.Should().Be(1); + } + } + +#if MSTEST + [TestClass] +#endif + public class verify_manual_polling : using_AsyncPollingClient + { + private readonly List commits = []; + + protected override void Context() + { + Observer = new LambdaAsyncObserver((c, _) => { commits.Add(c); return Task.FromResult(true); }); + base.Context(); + StoreEvents.Advanced.CommitSingle(); + StoreEvents.Advanced.CommitSingle(); + } + + protected override Task BecauseAsync() + { + Sut.ConfigurePollingClient(); + return Sut.PollAsync(CancellationToken.None); + } + + [Fact] + public void commits_are_retried_then_move_next() + { + WaitForCondition(() => commits.Count >= 2, timeoutInSeconds: 3); + commits.Count.Should().Be(2); + commits + .Select(c => c.CheckpointToken) + .SequenceEqual([1L, 2L]) + .Should().BeTrue(); + } + } + + public abstract class using_AsyncPollingClient : SpecificationBase + { + protected const int PollingInterval = 100; + protected AsyncPollingClient? sut; + private IStoreEvents? _storeEvents; + + protected AsyncPollingClient Sut + { + get { return sut!; } + } + + protected IStoreEvents StoreEvents + { + get { return _storeEvents!; } + } + + protected IAsyncObserver Observer { get; set; } = new CommitStreamObserver(); + + protected override void Context() + { + _storeEvents = Wireup.Init().UsingInMemoryPersistence().Build(); + sut = new AsyncPollingClient(_storeEvents.Advanced, Observer, PollingInterval); + } + + protected override void Cleanup() + { + _storeEvents?.Dispose(); + Sut.Dispose(); + } + + protected void WaitForCondition(Func predicate, Int32 timeoutInSeconds = 4) + { + DateTime startTest = DateTime.Now; + while (!predicate() && DateTime.Now.Subtract(startTest).TotalSeconds < timeoutInSeconds) + { + Thread.Sleep(100); + } + } + } +} + +#pragma warning restore IDE1006 // Naming Styles diff --git a/src/NEventStore.Tests/Client/CommitSequencerTests.cs b/src/NEventStore.Tests/Client/CommitSequencerTests.cs new file mode 100644 index 000000000..78d7dd358 --- /dev/null +++ b/src/NEventStore.Tests/Client/CommitSequencerTests.cs @@ -0,0 +1,175 @@ +#pragma warning disable IDE1006 // Naming Styles + +using System.Collections.Concurrent; +using NEventStore.Helpers; +using NEventStore.Persistence.AcceptanceTests.BDD; +using FluentAssertions; +#if MSTEST +using Microsoft.VisualStudio.TestTools.UnitTesting; +#endif +#if NUNIT +using NUnit.Framework; +#endif +#if XUNIT +using Xunit; +using Xunit.Should; +#endif + +namespace NEventStore.PollingClient +{ +#if MSTEST + [TestClass] +#endif +#if NUNIT + [TestFixture] +#endif + public class CommitSequencerTests + { + private int _outOfSequenceTimeoutInMilliseconds; + + private CommitSequencer InitCommitSequencer(Func? callBack = null) + { + callBack ??= _ => PollingClient2.HandlingResult.MoveToNext; + _outOfSequenceTimeoutInMilliseconds = 2000; + return new CommitSequencer(c => callBack(c), 0, _outOfSequenceTimeoutInMilliseconds); + } + + [Fact] + public void verify_check_sequential_missing_commit() + { + var sut = InitCommitSequencer(); + + var result = sut.Handle(new TestICommit() { CheckpointToken = 1L }); + result.Should().Be(PollingClient2.HandlingResult.MoveToNext); + result = sut.Handle(new TestICommit() { CheckpointToken = 3L }); + result.Should().Be(PollingClient2.HandlingResult.Retry); + } + + [Fact] + public void verify_timeout_on_missing_commit_not_elapsed() + { + var sut = InitCommitSequencer(); + + DateTime start = DateTime.Now; + var result = sut.Handle(new TestICommit() { CheckpointToken = 1L }); + result.Should().Be(PollingClient2.HandlingResult.MoveToNext); + using (DateTimeService.Override(start)) + { + result = sut.Handle(new TestICommit() { CheckpointToken = 3 }); + result.Should().Be(PollingClient2.HandlingResult.Retry); + } + using (DateTimeService.Override(start.AddMilliseconds(_outOfSequenceTimeoutInMilliseconds - 100))) + { + result = sut.Handle(new TestICommit() { CheckpointToken = 3 }); + result.Should().Be(PollingClient2.HandlingResult.Retry); + } + } + + [Fact] + public void verify_idempotence_on_read_same_commit() + { + Int32 callBackCount = 0; + var sut = InitCommitSequencer(_ => + { + callBackCount++; + return PollingClient2.HandlingResult.MoveToNext; + }); + + var result = sut.Handle(new TestICommit() { CheckpointToken = 1 }); + result.Should().Be(PollingClient2.HandlingResult.MoveToNext); + callBackCount.Should().Be(1); + result = sut.Handle(new TestICommit() { CheckpointToken = 1 }); + result.Should().Be(PollingClient2.HandlingResult.MoveToNext); + callBackCount.Should().Be(1); + } + + [Fact] + public void verify_timeout_on_missing_commit_then_next_commit() + { + var sut = InitCommitSequencer(); + + DateTime start = DateTime.Now; + var result = sut.Handle(new TestICommit() { CheckpointToken = 1 }); + result.Should().Be(PollingClient2.HandlingResult.MoveToNext); + using (DateTimeService.Override(start)) + { + result = sut.Handle(new TestICommit() { CheckpointToken = 3 }); + result.Should().Be(PollingClient2.HandlingResult.Retry); + } + using (DateTimeService.Override(start.AddMilliseconds(_outOfSequenceTimeoutInMilliseconds - 100))) + { + result = sut.Handle(new TestICommit() { CheckpointToken = 2 }); + result.Should().Be(PollingClient2.HandlingResult.MoveToNext); + result = sut.Handle(new TestICommit() { CheckpointToken = 3 }); + result.Should().Be(PollingClient2.HandlingResult.MoveToNext); + } + } + + [Fact] + public void verify_timeout_on_missing_commit_elapsed() + { + var sut = InitCommitSequencer(); + + DateTime start = DateTime.Now; + var result = sut.Handle(new TestICommit() { CheckpointToken = 1 }); + result.Should().Be(PollingClient2.HandlingResult.MoveToNext); + using (DateTimeService.Override(start)) + { + result = sut.Handle(new TestICommit() { CheckpointToken = 3 }); + result.Should().Be(PollingClient2.HandlingResult.Retry); + } + using (DateTimeService.Override(start.AddMilliseconds(_outOfSequenceTimeoutInMilliseconds + 100))) + { + result = sut.Handle(new TestICommit() { CheckpointToken = 3 }); + result.Should().Be(PollingClient2.HandlingResult.MoveToNext); + } + } + + public class TestICommit : ICommit + { + public string BucketId + { + get { return ""; } + } + + public string StreamId + { + get { return ""; } + } + + public int StreamRevision + { + get { return 0; } + } + + public Guid CommitId + { + get { return Guid.Empty; } + } + + public int CommitSequence + { + get { return 0; } + } + + public DateTime CommitStamp + { + get { return DateTimeService.Now; } + } + + public IDictionary Headers + { + get { return new ConcurrentDictionary(); } + } + + public ICollection Events + { + get { return new List(); } + } + + public Int64 CheckpointToken { get; set; } + } + } +} + +#pragma warning restore IDE1006 // Naming Styles diff --git a/src/NEventStore.Tests/Client/PollingClient2Tests.cs b/src/NEventStore.Tests/Client/PollingClient2Tests.cs new file mode 100644 index 000000000..1dcb93bee --- /dev/null +++ b/src/NEventStore.Tests/Client/PollingClient2Tests.cs @@ -0,0 +1,294 @@ +#pragma warning disable IDE1006 // Naming Styles + +using FakeItEasy; +using NEventStore.Persistence; +using NEventStore.Persistence.AcceptanceTests; +using NEventStore.Persistence.AcceptanceTests.BDD; +using FluentAssertions; +#if MSTEST +using Microsoft.VisualStudio.TestTools.UnitTesting; +#endif +#if NUNIT +#endif +#if XUNIT +using Xunit; +using Xunit.Should; +#endif + +namespace NEventStore.PollingClient +{ +#if MSTEST + [TestClass] +#endif + public class CreatingPollingClient2Tests + { + [Fact] + public void When_persist_streams_is_null_then_should_throw() + { +#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. + Catch.Exception(() => new PollingClient2(null, _ => PollingClient2.HandlingResult.MoveToNext)).Should().BeOfType(); +#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. + } + + [Fact] + public void When_interval_less_than_zero_then_should_throw() + { +#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. + Catch.Exception(() => new PollingClient2(A.Fake(), null)).Should().BeOfType(); +#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. + } + } + +#if MSTEST + [TestClass] +#endif + public class base_handling_committed_events : using_polling_client2 + { + private readonly List commits = []; + + protected override void Context() + { + base.Context(); + HandleFunction = c => + { + commits.Add(c); + return PollingClient2.HandlingResult.MoveToNext; + }; + StoreEvents.Advanced.CommitSingle(); + } + + protected override void Because() + { + Sut.StartFrom(0); + } + + [Fact] + public void commits_are_correctly_dispatched() + { + WaitForCondition(() => commits.Count >= 1); + commits.Count.Should().Be(1); + } + } + +#if MSTEST + [TestClass] +#endif + public class base_handling_committed_events_and_new_events : using_polling_client2 + { + private readonly List commits = []; + + protected override void Context() + { + base.Context(); + HandleFunction = c => + { + commits.Add(c); + return PollingClient2.HandlingResult.MoveToNext; + }; + StoreEvents.Advanced.CommitSingle(); + } + + protected override void Because() + { + Sut.StartFrom(0); + for (int i = 0; i < 15; i++) + { + StoreEvents.Advanced.CommitSingle(); + } + } + + [Fact] + public void commits_are_correctly_dispatched() + { + WaitForCondition(() => commits.Count >= 16); + commits.Count.Should().Be(16); + } + } + +#if MSTEST + [TestClass] +#endif + public class verify_stopping_commit_polling_client : using_polling_client2 + { + private readonly List commits = []; + + protected override void Context() + { + base.Context(); + HandleFunction = c => + { + commits.Add(c); + return PollingClient2.HandlingResult.Stop; + }; + StoreEvents.Advanced.CommitSingle(); + StoreEvents.Advanced.CommitSingle(); + StoreEvents.Advanced.CommitSingle(); + } + + protected override void Because() + { + Sut.StartFrom(0); + } + + [Fact] + public void commits_are_correctly_dispatched() + { + WaitForCondition(() => commits.Count >= 2, timeoutInSeconds: 1); + commits.Count.Should().Be(1); + } + } + +#if MSTEST + [TestClass] +#endif + public class verify_retry_commit_polling_client : using_polling_client2 + { + private readonly List commits = []; + + protected override void Context() + { + base.Context(); + HandleFunction = c => + { + commits.Add(c); + if (commits.Count < 3) + return PollingClient2.HandlingResult.Retry; + + return PollingClient2.HandlingResult.MoveToNext; + }; + StoreEvents.Advanced.CommitSingle(); + } + + protected override void Because() + { + Sut.StartFrom(0); + } + + [Fact] + public void commits_are_retried() + { + WaitForCondition(() => commits.Count >= 3, timeoutInSeconds: 1); + commits.Count.Should().Be(3); + commits.All(c => c.CheckpointToken == 1).Should().BeTrue(); + } + } + +#if MSTEST + [TestClass] +#endif + public class verify_retry_then_move_next : using_polling_client2 + { + private readonly List commits = []; + + protected override void Context() + { + base.Context(); + HandleFunction = c => + { + commits.Add(c); + if (commits.Count < 3 && c.CheckpointToken == 1) + return PollingClient2.HandlingResult.Retry; + + return PollingClient2.HandlingResult.MoveToNext; + }; + StoreEvents.Advanced.CommitSingle(); + StoreEvents.Advanced.CommitSingle(); + } + + protected override void Because() + { + Sut.StartFrom(0); + } + + [Fact] + public void commits_are_retried_then_move_next() + { + WaitForCondition(() => commits.Count >= 4, timeoutInSeconds: 1); + commits.Count.Should().Be(4); + commits + .Select(c => c.CheckpointToken) + .SequenceEqual([1L, 1L, 1, 2]) + .Should().BeTrue(); + } + } + +#if MSTEST + [TestClass] +#endif + public class verify_manual_polling : using_polling_client2 + { + private readonly List commits = []; + + protected override void Context() + { + base.Context(); + HandleFunction = c => + { + commits.Add(c); + return PollingClient2.HandlingResult.MoveToNext; + }; + StoreEvents.Advanced.CommitSingle(); + StoreEvents.Advanced.CommitSingle(); + } + + protected override void Because() + { + Sut.ConfigurePollingFunction(); + Sut.PollNow(); + } + + [Fact] + public void commits_are_retried_then_move_next() + { + WaitForCondition(() => commits.Count >= 2, timeoutInSeconds: 3); + commits.Count.Should().Be(2); + commits + .Select(c => c.CheckpointToken) + .SequenceEqual([1L, 2L]) + .Should().BeTrue(); + } + } + + public abstract class using_polling_client2 : SpecificationBase + { + protected const int PollingInterval = 100; + protected PollingClient2? sut; + private IStoreEvents? _storeEvents; + + protected PollingClient2 Sut + { + get { return sut!; } + } + + protected IStoreEvents StoreEvents + { + get { return _storeEvents!; } + } + + protected Func? HandleFunction; + + protected override void Context() + { + HandleFunction = _ => PollingClient2.HandlingResult.MoveToNext; + _storeEvents = Wireup.Init().UsingInMemoryPersistence().Build(); + sut = new PollingClient2(_storeEvents.Advanced, c => HandleFunction(c), PollingInterval); + } + + protected override void Cleanup() + { + _storeEvents?.Dispose(); + Sut.Dispose(); + } + + protected void WaitForCondition(Func predicate, Int32 timeoutInSeconds = 4) + { + DateTime startTest = DateTime.Now; + while (!predicate() && DateTime.Now.Subtract(startTest).TotalSeconds < timeoutInSeconds) + { + Thread.Sleep(100); + } + } + } +} + +#pragma warning restore IDE1006 // Naming Styles diff --git a/src/NEventStore.Tests/Client/PollingClientRx.cs b/src/NEventStore.Tests/Client/PollingClientRx.cs new file mode 100644 index 000000000..e20df0de4 --- /dev/null +++ b/src/NEventStore.Tests/Client/PollingClientRx.cs @@ -0,0 +1,65 @@ +namespace NEventStore.PollingClient +{ + using System; + using System.Reactive.Subjects; + using NEventStore.Persistence; + + /// + /// Represents a client that poll the storage for latest commits. + /// + public sealed class PollingClientRx + { + private readonly PollingClient2 _pollingClient2; + + private readonly Subject _subject; + + public PollingClientRx( + IPersistStreams persistStreams, + Int32 waitInterval = 5000) + { + if (persistStreams is null) + { + throw new ArgumentNullException(nameof(persistStreams)); + } + if (waitInterval <= 0) + { + throw new ArgumentException("Must be greater than 0", nameof(waitInterval)); + } + _subject = new Subject(); + _pollingClient2 = new PollingClient2(persistStreams, c => + { + _subject.OnNext(c); + return PollingClient2.HandlingResult.MoveToNext; + }, + waitInterval: waitInterval); + } + + public IDisposable Subscribe(IObserver observer) + { + return _subject.Subscribe(observer); + } + + private Int64 _checkpointToObserveFrom; + + public IObservable ObserveFrom(Int64 checkpointToken = 0) + { + _checkpointToObserveFrom = checkpointToken; + return _subject; + } + + internal void Start() + { + _pollingClient2.StartFrom(_checkpointToObserveFrom); + } + + internal void Dispose() + { + _pollingClient2.Dispose(); + } + + internal void StartFromBucket(string bucketId) + { + _pollingClient2.StartFromBucket(bucketId, 0); + } + } +} \ No newline at end of file diff --git a/src/NEventStore.Tests/Client/PollingClientRxTests.cs b/src/NEventStore.Tests/Client/PollingClientRxTests.cs new file mode 100644 index 000000000..4c202858e --- /dev/null +++ b/src/NEventStore.Tests/Client/PollingClientRxTests.cs @@ -0,0 +1,279 @@ +#pragma warning disable IDE1006 // Naming Styles +using System.Reactive.Linq; +using System.Reactive.Threading.Tasks; +using FakeItEasy; +using FluentAssertions; +using NEventStore.Persistence; +using NEventStore.Persistence.AcceptanceTests; +using NEventStore.Persistence.AcceptanceTests.BDD; +#if MSTEST +using Microsoft.VisualStudio.TestTools.UnitTesting; +#endif +#if NUNIT +#endif +#if XUNIT +using Xunit; +using Xunit.Should; +#endif + +namespace NEventStore.PollingClient +{ +#if MSTEST + [TestClass] +#endif + public class CreatingPollingClientTests + { + [Fact] + public void When_persist_streams_is_null_then_should_throw() + { +#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. + Catch.Exception(() => new PollingClientRx(null)).Should().BeOfType(); +#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. + } + + [Fact] + public void When_interval_less_than_zero_then_should_throw() + { + Catch.Exception(() => new PollingClientRx(A.Fake(), -1)).Should().BeOfType(); + } + + [Fact] + public void When_interval_is_zero_then_should_throw() + { + Catch.Exception(() => new PollingClientRx(A.Fake(), 0)).Should().BeOfType(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_commit_is_committed_before_subscribing : using_polling_client + { + private IObservable? _observeCommits; + private Task? _commitObserved; + + protected override void Context() + { + base.Context(); + StoreEvents.Advanced.CommitSingle(); + _observeCommits = PollingClient.ObserveFrom(); + _commitObserved = _observeCommits.FirstAsync().ToTask(); + } + + protected override void Because() + { + PollingClient.Start(); + } + + protected override void Cleanup() + { + PollingClient.Dispose(); + } + + [Fact] + public void should_observe_commit() + { + _commitObserved!.Wait(PollingInterval * 2).Should().BeTrue(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_commit_is_committed_before_and_after_subscribing : using_polling_client + { + private IObservable? _observeCommits; + private Task? _twoCommitsObserved; + + protected override void Context() + { + base.Context(); + StoreEvents.Advanced.CommitSingle(); + _observeCommits = PollingClient.ObserveFrom(); + _twoCommitsObserved = _observeCommits.Take(2).ToTask(); + } + + protected override void Because() + { + PollingClient.Start(); + StoreEvents.Advanced.CommitSingle(); + } + + protected override void Cleanup() + { + PollingClient.Dispose(); + } + + [Fact] + public void should_observe_two_commits() + { + _twoCommitsObserved!.Wait(PollingInterval * 2).Should().BeTrue(); + } + } + + //#if MSTEST + // [TestClass] + //#endif + //public class with_two_observers_and_multiple_commits : using_polling_client + //{ + // private IObserveCommits _observeCommits1; + // private IObserveCommits _observeCommits2; + // private Task _observeCommits1Complete; + // private Task _observeCommits2Complete; + + // protected override void Context() + // { + // base.Context(); + // StoreEvents.Advanced.CommitSingle(); + // _observeCommits1 = PollingClient.ObserveFrom(); + // _observeCommits1Complete = _observeCommits1.Take(5).ToTask(); + + // _observeCommits2 = PollingClient.ObserveFrom(); + // _observeCommits2Complete = _observeCommits1.Take(10).ToTask(); + // } + + // protected override void Because() + // { + // _observeCommits1.Start(); + // _observeCommits2.Start(); + // Task.Factory.StartNew(() => + // { + // for (int i = 0; i < 15; i++) + // { + // StoreEvents.Advanced.CommitSingle(); + // } + // }); + // } + + // protected override void Cleanup() + // { + // _observeCommits1.Dispose(); + // _observeCommits2.Dispose(); + // } + + // [Fact] + // public void should_observe_commits_on_first_observer() + // { + // _observeCommits1Complete.Wait(PollingInterval * 10).ShouldBe(true); + // } + + // [Fact] + // public void should_observe_commits_on_second_observer() + // { + // _observeCommits2Complete.Wait(PollingInterval * 10).ShouldBe(true); + // } + //} + +#if MSTEST + [TestClass] +#endif + public class with_two_subscriptions_on_a_single_observer_and_multiple_commits : using_polling_client + { + private IObservable? _observeCommits1; + private Task? _observeCommits1Complete; + private Task? _observeCommits2Complete; + + protected override void Context() + { + base.Context(); + StoreEvents.Advanced.CommitSingle(); + _observeCommits1 = PollingClient.ObserveFrom(); + _observeCommits1Complete = _observeCommits1.Take(5).ToTask(); + _observeCommits2Complete = _observeCommits1.Take(10).ToTask(); + } + + protected override void Because() + { + PollingClient.Start(); + Task.Factory.StartNew(() => + { + for (int i = 0; i < 15; i++) + { + StoreEvents.Advanced.CommitSingle(); + } + }); + } + + protected override void Cleanup() + { + PollingClient.Dispose(); + } + + [Fact] + public void should_observe_commits_on_first_observer() + { + _observeCommits1Complete!.Wait(PollingInterval * 10).Should().BeTrue(); + } + + [Fact] + public void should_observe_commits_on_second_observer() + { + _observeCommits2Complete!.Wait(PollingInterval * 10).Should().BeTrue(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_polling_from_bucket1 : using_polling_client + { + private IObservable? _observeCommits; + private Task? _commitObserved; + + protected override void Context() + { + base.Context(); + StoreEvents.Advanced.CommitMany(4, null, "bucket_2"); + StoreEvents.Advanced.CommitMany(4, null, "bucket_1"); + _observeCommits = PollingClient.ObserveFrom(); + _commitObserved = _observeCommits.FirstAsync().ToTask(); + } + + protected override void Because() + { + PollingClient.StartFromBucket("bucket_1"); + } + + protected override void Cleanup() + { + PollingClient.Dispose(); + } + + [Fact] + public void should_observe_commit_from_bucket1() + { + _commitObserved!.Wait(PollingInterval * 2).Should().BeTrue(); + _commitObserved.Result.BucketId.Should().Be("bucket_1"); + } + } + + public abstract class using_polling_client : SpecificationBase + { + protected const int PollingInterval = 100; + private PollingClientRx? _pollingClient; + private IStoreEvents? _storeEvents; + + protected PollingClientRx PollingClient + { + get { return _pollingClient!; } + } + + protected IStoreEvents StoreEvents + { + get { return _storeEvents!; } + } + + protected override void Context() + { + _storeEvents = Wireup.Init().UsingInMemoryPersistence().Build(); + _pollingClient = new PollingClientRx(_storeEvents.Advanced, PollingInterval); + } + + protected override void Cleanup() + { + _storeEvents!.Dispose(); + } + } +} + +#pragma warning restore IDE1006 // Naming Styles \ No newline at end of file diff --git a/src/NEventStore.Tests/ConversionTests/EventUpconverterPipelineHookTests.cs b/src/NEventStore.Tests/ConversionTests/EventUpconverterPipelineHookTests.cs new file mode 100644 index 000000000..e405dfabc --- /dev/null +++ b/src/NEventStore.Tests/ConversionTests/EventUpconverterPipelineHookTests.cs @@ -0,0 +1,235 @@ +#pragma warning disable IDE1006 // Naming Styles + +using System.Reflection; +using NEventStore.Conversion; +using NEventStore.Persistence; +using NEventStore.Persistence.AcceptanceTests.BDD; +using FluentAssertions; +#if MSTEST +using Microsoft.VisualStudio.TestTools.UnitTesting; +#endif +#if NUNIT +#endif +#if XUNIT +using Xunit; +using Xunit.Should; +#endif + +namespace NEventStore.ConversionTests +{ +#if MSTEST + [TestClass] +#endif + public class when_opening_a_commit_that_does_not_have_convertible_events : using_event_converter + { + private ICommit? _commit; + + private ICommit? _converted; + + protected override void Context() + { + _commit = CreateCommit(new EventMessage { Body = new NonConvertingEvent() }); + } + + protected override void Because() + { + _converted = EventUpconverter.SelectCommit(_commit!); + } + + [Fact] + public void should_not_be_converted() + { + _converted.Should().BeSameAs(_commit); + } + + [Fact] + public void should_have_the_same_instance_of_the_event() + { + _converted!.Events.Single().Should().Be(_commit!.Events.Single()); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_opening_a_commit_that_has_convertible_events : using_event_converter + { + private ICommit? _commit; + + private readonly Guid _id = Guid.NewGuid(); + private ICommit? _converted; + + protected override void Context() + { + _commit = CreateCommit(new EventMessage { Body = new ConvertingEvent(_id) }); + } + + protected override void Because() + { + _converted = EventUpconverter.SelectCommit(_commit!); + } + + [Fact] + public void should_be_of_the_converted_type() + { + _converted!.Events.Single().Body.GetType().Should().Be(typeof(ConvertingEvent3)); + } + + [Fact] + public void should_have_the_same_id_of_the_committed_event() + { + ((ConvertingEvent3)_converted!.Events.Single().Body).Id.Should().Be(_id); + } + } + + // ReSharper disable InconsistentNaming +#if MSTEST + [TestClass] +#endif + public class when_an_event_converter_implements_the_IConvertEvents_interface_explicitly : using_event_converter + // ReSharper restore InconsistentNaming + { + private ICommit? _commit; + private readonly Guid _id = Guid.NewGuid(); + private ICommit? _converted; + private EventMessage? _eventMessage; + + protected override void Context() + { + _eventMessage = new EventMessage { Body = new ConvertingEvent2(_id, "FooEvent") }; + + _commit = CreateCommit(_eventMessage); + } + + protected override void Because() + { + _converted = EventUpconverter.SelectCommit(_commit!); + } + + [Fact] + public void should_be_of_the_converted_type() + { + _converted!.Events.Single().Body.GetType().Should().Be(typeof(ConvertingEvent3)); + } + + [Fact] + public void should_have_the_same_id_of_the_committed_event() + { + ((ConvertingEvent3)_converted!.Events.Single().Body).Id.Should().Be(_id); + } + } + + public abstract class using_event_converter : SpecificationBase + { + private IEnumerable? _assemblies; + private Dictionary>? _converters; + private EventUpconverterPipelineHook? _eventUpconverter; + + protected EventUpconverterPipelineHook EventUpconverter + { + get { return _eventUpconverter ??= CreateUpConverterHook(); } + } + + private EventUpconverterPipelineHook CreateUpConverterHook() + { + _assemblies = GetAllAssemblies(); + _converters = GetConverters(_assemblies); + return new EventUpconverterPipelineHook(_converters); + } + + private static Dictionary> GetConverters(IEnumerable toScan) + { + IEnumerable>> c = from a in toScan + from t in a.GetTypes() + let i = t.GetInterface(typeof(IUpconvertEvents<,>).FullName!) + where i != null + let sourceType = i.GetGenericArguments().First() + let convertMethod = i.GetMethods(BindingFlags.Public | BindingFlags.Instance).First() + let instance = Activator.CreateInstance(t) + select new KeyValuePair>(sourceType, e => convertMethod.Invoke(instance, [e])); + try + { + return c.ToDictionary(x => x.Key, x => x.Value); + } + catch (ArgumentException ex) + { + throw new MultipleConvertersFoundException(ex.Message, ex); + } + } + + private static IEnumerable GetAllAssemblies() + { + return + Assembly.GetCallingAssembly().GetReferencedAssemblies().Select(Assembly.Load).Concat([Assembly.GetCallingAssembly()]); + } + + protected static ICommit CreateCommit(EventMessage eventMessage) + { + return new Commit(Bucket.Default, + Guid.NewGuid().ToString(), + 1, + Guid.NewGuid(), + 1, + DateTime.MinValue, + 1, + null, + [eventMessage]); + } + } + + public class ConvertingEventConverter : IUpconvertEvents + { + public ConvertingEvent2 Convert(ConvertingEvent sourceEvent) + { + return new ConvertingEvent2(sourceEvent.Id, "Temp"); + } + } + + public class ExplicitConvertingEventConverter : IUpconvertEvents + { + ConvertingEvent3 IUpconvertEvents.Convert(ConvertingEvent2 sourceEvent) + { + return new ConvertingEvent3(sourceEvent.Id, "Temp", true); + } + } + + public class NonConvertingEvent; + + public class ConvertingEvent + { + public ConvertingEvent(Guid id) + { + Id = id; + } + + public Guid Id { get; set; } + } + + public class ConvertingEvent2 + { + public ConvertingEvent2(Guid id, string name) + { + Id = id; + Name = name; + } + + public Guid Id { get; set; } + public string Name { get; set; } + } + + public class ConvertingEvent3 + { + public ConvertingEvent3(Guid id, string name, bool imExplicit) + { + Id = id; + Name = name; + ImExplicit = imExplicit; + } + + public Guid Id { get; set; } + public string Name { get; set; } + public bool ImExplicit { get; set; } + } +} + +#pragma warning restore IDE1006 // Naming Styles \ No newline at end of file diff --git a/src/NEventStore.Tests/DefaultSerializationWireupTests.cs b/src/NEventStore.Tests/DefaultSerializationWireupTests.cs new file mode 100644 index 000000000..8b9a3f7ac --- /dev/null +++ b/src/NEventStore.Tests/DefaultSerializationWireupTests.cs @@ -0,0 +1,67 @@ +#pragma warning disable IDE1006 // Naming Styles + +using NEventStore.Persistence.AcceptanceTests; +using NEventStore.Persistence.AcceptanceTests.BDD; +using FluentAssertions; +#if MSTEST +using Microsoft.VisualStudio.TestTools.UnitTesting; +#endif +#if NUNIT +using NEventStore.Persistence.InMemory; +using NEventStore.Tests; +using NEventStore.Persistence; +#endif +#if XUNIT +using Xunit; +using Xunit.Should; +#endif + +namespace NEventStore +{ +#if MSTEST + [TestClass] +#endif + public class when_building_an_event_store_without_an_explicit_serializer : SpecificationBase + { + private TestableWireup? _wireup; + private Exception? _exception; + private IStoreEvents? _eventStore; + + protected override void Context() + { + _wireup = Wireup.Init() + // .UsingInMemoryPersistence() // the InMemoryPersistence should be the default serializer + .UseTestableWireup(); + } + + protected override void Because() + { + _exception = Catch.Exception(() => _eventStore = _wireup!.Build()); + } + + protected override void Cleanup() + { + _eventStore?.Dispose(); + } + + [Fact] + public void should_not_throw_an_argument_null_exception() + { + // _exception.Should().NotBeOfType(); + _exception.Should().BeNull(); + } + + [Fact] + public void should_have_InMemoryPersistenceEngine_as_default_serializer() + { + var defaultPersistence = _wireup!.Container.Resolve(); + defaultPersistence.Should().BeOfType(typeof(InMemoryPersistenceEngine)); + + // cannot check eventstore.Advanced type because the persistence is wrapped + // by a PipelineHooksPersistenceAwareDecorator + // _eventStore.Advanced.Should().BeOfType(typeof(InMemoryPersistenceEngine)); + } + } +} + +#pragma warning restore IDE1006 // Naming Styles diff --git a/src/NEventStore.Tests/EnumerableCounter.cs b/src/NEventStore.Tests/EnumerableCounter.cs new file mode 100644 index 000000000..4e4c77287 --- /dev/null +++ b/src/NEventStore.Tests/EnumerableCounter.cs @@ -0,0 +1,28 @@ +using System.Collections; + +namespace NEventStore +{ + internal class EnumerableCounter : IEnumerable + { + private readonly IEnumerable _enumerable; + + public EnumerableCounter(IEnumerable enumerable) + { + _enumerable = enumerable; + GetEnumeratorCallCount = 0; + } + + public int GetEnumeratorCallCount { get; private set; } + + public IEnumerator GetEnumerator() + { + GetEnumeratorCallCount++; + return _enumerable.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} \ No newline at end of file diff --git a/src/NEventStore.Tests/NEventStore.Core.Tests.csproj b/src/NEventStore.Tests/NEventStore.Core.Tests.csproj new file mode 100644 index 000000000..dab010f72 --- /dev/null +++ b/src/NEventStore.Tests/NEventStore.Core.Tests.csproj @@ -0,0 +1,43 @@ + + + + net8.0;net472 + false + false + NEventStore.Tests + NEventStore.Tests + + + + TRACE;DEBUG;NUNIT + NUNIT + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/NEventStore.Tests/NUnitTestAdapterTestsDiscovery.cs b/src/NEventStore.Tests/NUnitTestAdapterTestsDiscovery.cs new file mode 100644 index 000000000..cba13cb9f --- /dev/null +++ b/src/NEventStore.Tests/NUnitTestAdapterTestsDiscovery.cs @@ -0,0 +1,17 @@ +using NUnit.Framework; + +namespace NEventStore.Tests +{ + /// + /// this is needed to allow NUnit test adapter to discover the tests, + /// if we do not have any class explicitly marked with the TestFixture attribute + /// all the assembly will be ignored (even if some class inherit from something which is + /// marked with the attribute in a reference assembly) + /// +#if NUNIT + [TestFixture] + public class NUnitTestAdapterTestsDiscovery + { + } +#endif +} diff --git a/src/NEventStore.Tests/OptimisticEventStoreTests.Async.cs b/src/NEventStore.Tests/OptimisticEventStoreTests.Async.cs new file mode 100644 index 000000000..ca4a92cc9 --- /dev/null +++ b/src/NEventStore.Tests/OptimisticEventStoreTests.Async.cs @@ -0,0 +1,752 @@ + +#pragma warning disable 169 // ReSharper disable InconsistentNaming +#pragma warning disable IDE1006 // Naming Styles + +using FluentAssertions; +using FakeItEasy; +using NEventStore.Persistence; +using NEventStore.Persistence.AcceptanceTests; +using NEventStore.Persistence.AcceptanceTests.BDD; +using System.IO; +#if MSTEST +using Microsoft.VisualStudio.TestTools.UnitTesting; +#endif +#if NUNIT +#endif +#if XUNIT +using Xunit; +using Xunit.Should; +#endif + +namespace NEventStore.Async +{ +#if MSTEST + [TestClass] +#endif + public class when_creating_a_new_stream : using_persistence + { + private IEventStream? _stream; + + protected override void Because() + { + _stream = Store.CreateStream(streamId); + } + + [Fact] + public void should_return_a_new_stream() + { + _stream.Should().NotBeNull(); + } + + [Fact] + public void should_return_a_stream_with_the_correct_stream_identifier() + { + _stream!.StreamId.Should().Be(streamId); + } + + [Fact] + public void should_return_a_stream_with_a_zero_stream_revision() + { + _stream!.StreamRevision.Should().Be(0); + } + + [Fact] + public void should_return_a_stream_with_a_zero_commit_sequence() + { + _stream!.CommitSequence.Should().Be(0); + } + + [Fact] + public void should_return_a_stream_with_no_uncommitted_events() + { + _stream!.UncommittedEvents.Should().BeEmpty(); + } + + [Fact] + public void should_return_a_stream_with_no_committed_events() + { + _stream!.CommittedEvents.Should().BeEmpty(); + } + + [Fact] + public void should_return_a_stream_with_empty_headers() + { + _stream!.UncommittedHeaders.Should().BeEmpty(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_opening_an_empty_stream_starting_at_revision_zero : using_persistence + { + private IEventStream? _stream; + + protected override void Context() + { + // read an empty stream! + A.CallTo(() => Persistence.GetFromAsync(Bucket.Default, streamId, 0, 0, A>.Ignored, CancellationToken.None)) + .Returns(Task.CompletedTask); + } + + protected override async Task BecauseAsync() + { + _stream = await Store.OpenStreamAsync(streamId, 0, 0, CancellationToken.None).ConfigureAwait(false); + } + + [Fact] + public void should_return_a_new_stream() + { + _stream.Should().NotBeNull(); + } + + [Fact] + public void should_return_a_stream_with_the_correct_stream_identifier() + { + _stream!.StreamId.Should().Be(streamId); + } + + [Fact] + public void should_return_a_stream_with_a_zero_stream_revision() + { + _stream!.StreamRevision.Should().Be(0); + } + + [Fact] + public void should_return_a_stream_with_a_zero_commit_sequence() + { + _stream!.CommitSequence.Should().Be(0); + } + + [Fact] + public void should_return_a_stream_with_no_uncommitted_events() + { + _stream!.UncommittedEvents.Should().BeEmpty(); + } + + [Fact] + public void should_return_a_stream_with_no_committed_events() + { + _stream!.CommittedEvents.Should().BeEmpty(); + } + + [Fact] + public void should_return_a_stream_with_empty_headers() + { + _stream!.UncommittedHeaders.Should().BeEmpty(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_opening_an_empty_stream_starting_above_revision_zero : using_persistence + { + private const int MinRevision = 1; + private Exception? _thrown; + + protected override void Context() + { + // read an empty stream! + A.CallTo(() => Persistence.GetFromAsync(Bucket.Default, streamId, MinRevision, int.MaxValue, A>.Ignored, CancellationToken.None)) + .Returns(Task.CompletedTask); + } + + protected override async Task BecauseAsync() + { + _thrown = await Catch.ExceptionAsync(() => Store.OpenStreamAsync(streamId, MinRevision)).ConfigureAwait(false); + } + + [Fact] + public void should_throw_a_StreamNotFoundException() + { + _thrown.Should().BeOfType(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_opening_a_populated_stream : using_persistence + { + private const int MinRevision = 17; + private const int MaxRevision = 42; + private ICommit? _committed; + private IEventStream? _stream; + + protected override void Context() + { + _committed = BuildCommitStub(1, MinRevision, 1); + + A.CallTo(() => Persistence.GetFromAsync(Bucket.Default, streamId, MinRevision, MaxRevision, A>.Ignored, CancellationToken.None)) + .ReturnsLazily(async (string bucketId, string streamId, int minRevision, int maxRevision, IAsyncObserver asyncObserver, CancellationToken cancellation) => + { + await asyncObserver.OnNextAsync(_committed, cancellation).ConfigureAwait(false); + await asyncObserver.OnCompletedAsync(cancellation).ConfigureAwait(false); + }); + + var hook = A.Fake(); + A.CallTo(() => hook.SelectCommit(_committed)).Returns(_committed); + PipelineHooks.Add(hook); + + var hookAsync = A.Fake(); + A.CallTo(() => hookAsync.SelectCommitAsync(_committed, A.Ignored)).Returns(Task.FromResult(_committed)); + PipelineHooksAsync.Add(hookAsync); + } + + protected override async Task BecauseAsync() + { + _stream = await Store.OpenStreamAsync(streamId, MinRevision, MaxRevision).ConfigureAwait(false); + } + + [Fact] + public void should_invoke_the_underlying_infrastructure_with_the_values_provided() + { + A.CallTo(() => Persistence.GetFromAsync(Bucket.Default, streamId, MinRevision, MaxRevision, A>.Ignored, CancellationToken.None)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public void should_provide_the_commits_to_the_selection_hooks() + { + PipelineHooks.ForEach(x => A.CallTo(() => x.SelectCommit(_committed!)).MustHaveHappenedOnceExactly()); + } + + [Fact] + public void should_provide_the_commits_to_the_async_selection_hooks() + { + PipelineHooksAsync.ForEach(x => A.CallTo(() => x.SelectCommitAsync(_committed!, A.Ignored)).MustHaveHappenedOnceExactly()); + } + + [Fact] + public void should_return_an_event_stream_containing_the_correct_stream_identifier() + { + _stream!.StreamId.Should().Be(streamId); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_opening_a_populated_stream_from_a_snapshot : using_persistence + { + private const int MaxRevision = int.MaxValue; + private ICommit[]? _committed; + private Snapshot? _snapshot; + + protected override void Context() + { + _snapshot = new Snapshot(streamId, 42, "snapshot"); + _committed = [BuildCommitStub(1, 42, 0)]; + + A.CallTo(() => Persistence.GetFromAsync(Bucket.Default, streamId, 42, MaxRevision, A>.Ignored, CancellationToken.None)) + .ReturnsLazily(async (string bucketId, string streamId, int minRevision, int maxRevision, IAsyncObserver asyncObserver, CancellationToken cancellation) => + { + foreach (var _commit in _committed) + { + await asyncObserver.OnNextAsync(_commit, cancellation).ConfigureAwait(false); + } + await asyncObserver.OnCompletedAsync(cancellation).ConfigureAwait(false); + }); + } + + protected override Task BecauseAsync() + { + return Store.OpenStreamAsync(_snapshot!, MaxRevision, CancellationToken.None); + } + + [Fact] + public void should_query_the_underlying_storage_using_the_revision_of_the_snapshot() + { + A.CallTo(() => Persistence.GetFromAsync(Bucket.Default, streamId, 42, MaxRevision, A>.Ignored, CancellationToken.None)).MustHaveHappenedOnceExactly(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_opening_a_stream_from_a_snapshot_that_is_at_the_revision_of_the_stream_head : using_persistence + { + private const int HeadStreamRevision = 42; + private const int HeadCommitSequence = 15; + private EnumerableCounter? _committed; + private Snapshot? _snapshot; + private IEventStream? _stream; + + protected override void Context() + { + _snapshot = new Snapshot(streamId, HeadStreamRevision, "snapshot"); + _committed = new EnumerableCounter( + [BuildCommitStub(1, HeadStreamRevision, HeadCommitSequence)]); + + A.CallTo(() => Persistence.GetFromAsync(Bucket.Default, streamId, HeadStreamRevision, int.MaxValue, A>.Ignored, CancellationToken.None)) + .ReturnsLazily(async (string bucketId, string streamId, int minRevision, int maxRevision, IAsyncObserver asyncObserver, CancellationToken cancellation) => + { + foreach (var _commit in _committed) + { + await asyncObserver.OnNextAsync(_commit, cancellation).ConfigureAwait(false); + } + await asyncObserver.OnCompletedAsync(cancellation).ConfigureAwait(false); + }); + } + + protected override async Task BecauseAsync() + { + _stream = await Store.OpenStreamAsync(_snapshot!, int.MaxValue, CancellationToken.None).ConfigureAwait(false); + } + + [Fact] + public void should_return_a_stream_with_the_correct_stream_identifier() + { + _stream!.StreamId.Should().Be(streamId); + } + + [Fact] + public void should_return_a_stream_with_revision_of_the_stream_head() + { + _stream!.StreamRevision.Should().Be(HeadStreamRevision); + } + + [Fact] + public void should_return_a_stream_with_a_commit_sequence_of_the_stream_head() + { + _stream!.CommitSequence.Should().Be(HeadCommitSequence); + } + + [Fact] + public void should_return_a_stream_with_no_committed_events() + { + _stream!.CommittedEvents.Count.Should().Be(0); + } + + [Fact] + public void should_return_a_stream_with_no_uncommitted_events() + { + _stream!.UncommittedEvents.Count.Should().Be(0); + } + + [Fact] + public void should_only_enumerate_the_set_of_commits_once() + { + _committed!.GetEnumeratorCallCount.Should().Be(1); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_reading_from_revision_zero : using_persistence + { + protected override void Context() + { + + // read an empty stream! + A.CallTo(() => Persistence.GetFromAsync(Bucket.Default, streamId, 0, int.MaxValue, A>.Ignored, CancellationToken.None)) + .Returns(Task.CompletedTask); + } + + protected override Task BecauseAsync() + { + return Store.GetFromAsync(streamId, 0, int.MaxValue, new CommitStreamObserver(), CancellationToken.None); + } + + [Fact] + public void should_pass_a_revision_range_to_the_persistence_infrastructure() + { + A.CallTo(() => Persistence.GetFromAsync(Bucket.Default, streamId, 0, int.MaxValue, A>.Ignored, CancellationToken.None)).MustHaveHappenedOnceExactly(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_reading_up_to_revision_revision_zero : using_persistence + { + private ICommit? _committed; + + protected override void Context() + { + _committed = BuildCommitStub(1, 1, 1); + + A.CallTo(() => Persistence.GetFromAsync(Bucket.Default, streamId, 0, int.MaxValue, A>.Ignored, CancellationToken.None)) + .ReturnsLazily(async (string bucketId, string streamId, int minRevision, int maxRevision, IAsyncObserver asyncObserver, CancellationToken cancellation) => + { + await asyncObserver.OnNextAsync(_committed, cancellation).ConfigureAwait(false); + await asyncObserver.OnCompletedAsync(cancellation).ConfigureAwait(false); + }); + } + + protected override Task BecauseAsync() + { + return Store.OpenStreamAsync(streamId, 0, 0); + } + + [Fact] + public void should_pass_the_maximum_possible_revision_to_the_persistence_infrastructure() + { + A.CallTo(() => Persistence.GetFromAsync(Bucket.Default, streamId, 0, int.MaxValue, A>.Ignored, CancellationToken.None)).MustHaveHappenedOnceExactly(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_reading_from_a_null_snapshot : using_persistence + { + private Exception? thrown; + + protected override async Task BecauseAsync() + { +#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. + thrown = await Catch.ExceptionAsync(() => Store.OpenStreamAsync(null , int.MaxValue, CancellationToken.None)).ConfigureAwait(false); +#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. + } + + [Fact] + public void should_throw_an_ArgumentNullException() + { + thrown.Should().BeOfType(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_reading_from_a_snapshot_up_to_revision_revision_zero : using_persistence + { + private ICommit? _committed; + private Snapshot? snapshot; + + protected override void Context() + { + snapshot = new Snapshot(streamId, 1, "snapshot"); + _committed = BuildCommitStub(1, 1, 1); + + A.CallTo(() => Persistence.GetFromAsync(Bucket.Default, streamId, snapshot.StreamRevision, int.MaxValue, A>.Ignored, CancellationToken.None)) + .ReturnsLazily(async (string bucketId, string streamId, int minRevision, int maxRevision, IAsyncObserver asyncObserver, CancellationToken cancellation) => + { + await asyncObserver.OnNextAsync(_committed, cancellation).ConfigureAwait(false); + await asyncObserver.OnCompletedAsync(cancellation).ConfigureAwait(false); + }); + } + + protected override Task BecauseAsync() + { + return Store.OpenStreamAsync(snapshot!, 0, CancellationToken.None); + } + + [Fact] + public void should_pass_the_maximum_possible_revision_to_the_persistence_infrastructure() + { + A.CallTo(() => Persistence.GetFromAsync(Bucket.Default, streamId, snapshot!.StreamRevision, int.MaxValue, A>.Ignored, CancellationToken.None)).MustHaveHappenedOnceExactly(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_committing_a_null_attempt_back_to_the_stream : using_persistence + { + private Exception? thrown; + + protected override async Task BecauseAsync() + { +#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. + thrown = await Catch.ExceptionAsync(() => Store.CommitAsync(null, CancellationToken.None)).ConfigureAwait(false); +#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. + } + + [Fact] + public void should_throw_an_ArgumentNullException() + { + thrown.Should().BeOfType(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_committing_with_a_valid_and_populated_attempt_to_a_stream : using_persistence + { + private CommitAttempt? _populatedAttempt; + private ICommit? _populatedCommit; + + protected override void Context() + { + _populatedAttempt = BuildCommitAttemptStub(1, 1); + + A.CallTo(() => Persistence.CommitAsync(_populatedAttempt, CancellationToken.None)) + .ReturnsLazily((CommitAttempt attempt, CancellationToken _) => + { + _populatedCommit = new Commit(attempt.BucketId, + attempt.StreamId, + attempt.StreamRevision, + attempt.CommitId, + attempt.CommitSequence, + attempt.CommitStamp, + 1, + attempt.Headers, + attempt.Events); + return _populatedCommit; + }); + + var hook = A.Fake(); + A.CallTo(() => hook.PreCommit(_populatedAttempt)).Returns(true); + PipelineHooks.Add(hook); + + var hookAsync = A.Fake(); + A.CallTo(() => hookAsync.PreCommitAsync(_populatedAttempt, A.Ignored)).Returns(Task.FromResult(true)); + PipelineHooksAsync.Add(hookAsync); + } + + protected override Task BecauseAsync() + { + return Store.CommitAsync(_populatedAttempt!, CancellationToken.None); + } + + [Fact] + public void should_provide_the_commit_to_the_PreCommit_hooks() + { + PipelineHooks.ForEach(x => A.CallTo(() => x.PreCommit(_populatedAttempt!)).MustHaveHappenedOnceExactly()); + } + + [Fact] + public void should_provide_the_commit_to_the_async_PreCommit_hooks() + { + PipelineHooksAsync.ForEach(x => A.CallTo(() => x.PreCommitAsync(_populatedAttempt!, A.Ignored)).MustHaveHappenedOnceExactly()); + } + + [Fact] + public void should_provide_the_commit_attempt_to_the_configured_persistence_mechanism() + { + A.CallTo(() => Persistence.CommitAsync(_populatedAttempt!, CancellationToken.None)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public void should_provide_the_commit_to_the_PostCommit_hooks() + { + PipelineHooks.ForEach(x => A.CallTo(() => x.PostCommit(_populatedCommit!)).MustHaveHappenedOnceExactly()); + } + + [Fact] + public void should_provide_the_commit_to_the_async_PostCommit_hooks() + { + PipelineHooksAsync.ForEach(x => A.CallTo(() => x.PostCommitAsync(_populatedCommit!, A.Ignored)).MustHaveHappenedOnceExactly()); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_a_PreCommit_hook_rejects_a_commit : using_persistence + { + private CommitAttempt? _attempt; + private ICommit? _commit; + + protected override void Context() + { + _attempt = BuildCommitAttemptStub(1, 1); + _commit = BuildCommitStub(1, 1, 1); + + var hook = A.Fake(); + A.CallTo(() => hook.PreCommit(_attempt)).Returns(false); + PipelineHooks.Add(hook); + + var hookAsync = A.Fake(); + A.CallTo(() => hookAsync.PreCommitAsync(_attempt, A.Ignored)).Returns(Task.FromResult(true)); + PipelineHooksAsync.Add(hookAsync); + } + + protected override Task BecauseAsync() + { + return Store.CommitAsync(_attempt!, CancellationToken.None); + } + + [Fact] + public void should_not_call_the_underlying_infrastructure() + { + A.CallTo(() => Persistence.CommitAsync(_attempt!, CancellationToken.None)).MustNotHaveHappened(); + } + + [Fact] + public void should_not_provide_the_commit_to_the_PostCommit_hooks() + { + PipelineHooks.ForEach(x => A.CallTo(() => x.PostCommit(_commit!)).MustNotHaveHappened()); + } + + [Fact] + public void should_not_provide_the_commit_to_the_async_PostCommit_hooks() + { + PipelineHooksAsync.ForEach(x => A.CallTo(() => x.PostCommitAsync(_commit!, A.Ignored)).MustNotHaveHappened()); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_an_async_PreCommit_hook_rejects_a_commit : using_persistence + { + private CommitAttempt? _attempt; + private ICommit? _commit; + + protected override void Context() + { + _attempt = BuildCommitAttemptStub(1, 1); + _commit = BuildCommitStub(1, 1, 1); + + var hook = A.Fake(); + A.CallTo(() => hook.PreCommit(_attempt)).Returns(true); + PipelineHooks.Add(hook); + + var hookAsync = A.Fake(); + A.CallTo(() => hookAsync.PreCommitAsync(_attempt, A.Ignored)).Returns(Task.FromResult(false)); + PipelineHooksAsync.Add(hookAsync); + } + + protected override Task BecauseAsync() + { + return Store.CommitAsync(_attempt!, CancellationToken.None); + } + + [Fact] + public void should_not_call_the_underlying_infrastructure() + { + A.CallTo(() => Persistence.CommitAsync(_attempt!, CancellationToken.None)).MustNotHaveHappened(); + } + + [Fact] + public void should_not_provide_the_commit_to_the_PostCommit_hooks() + { + PipelineHooks.ForEach(x => A.CallTo(() => x.PostCommit(_commit!)).MustNotHaveHappened()); + } + + [Fact] + public void should_not_provide_the_commit_to_the_async_PostCommit_hooks() + { + PipelineHooksAsync.ForEach(x => A.CallTo(() => x.PostCommitAsync(_commit!, A.Ignored)).MustNotHaveHappened()); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_accessing_the_underlying_persistence_with_pipeline_hooks : using_persistence + { + protected override void Because() + { + PipelineHooks.Add(A.Fake()); + } + + [Fact] + public void should_return_a_reference_to_the_underlying_persistence_infrastructure_decorator() + { + Store.Advanced.Should().BeOfType(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_accessing_the_underlying_persistence_with_async_pipeline_hooks : using_persistence + { + protected override void Because() + { + PipelineHooksAsync.Add(A.Fake()); + } + + [Fact] + public void should_return_a_reference_to_the_underlying_persistence_infrastructure_decorator() + { + Store.Advanced.Should().BeOfType(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_accessing_the_underlying_persistence_without_pipeline_hooks : using_persistence + { + [Fact] + public void should_return_a_reference_to_the_underlying_persistence() + { + Store.Advanced.Should().BeOfType(Persistence.GetType()); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_disposing_the_event_store : using_persistence + { + protected override void Because() + { + Store.Dispose(); + } + + [Fact] + public void should_dispose_the_underlying_persistence() + { + A.CallTo(() => Persistence.Dispose()).MustHaveHappenedOnceExactly(); + } + } + + public abstract class using_persistence : SpecificationBase + { + private IPersistStreams? persistence; + + private List? pipelineHooks; + private List? pipelineHooksAsync; + private OptimisticEventStore? store; + protected string streamId = Guid.NewGuid().ToString(); + + protected IPersistStreams Persistence + { + get { return persistence ??= A.Fake(); } + } + + protected List PipelineHooks + { + get { return pipelineHooks ??= []; } + } + + protected List PipelineHooksAsync + { + get { return pipelineHooksAsync ??= []; } + } + + protected OptimisticEventStore Store + { + get { return store ??= new OptimisticEventStore(Persistence, PipelineHooks.Select(x => x), PipelineHooksAsync.Select(x => x)); } + } + + protected override void Cleanup() + { + streamId = Guid.NewGuid().ToString(); + } + + protected CommitAttempt BuildCommitAttemptStub(Guid commitId) + { + return new CommitAttempt(Bucket.Default, streamId, 1, commitId, 1, SystemTime.UtcNow, null, []); + } + + protected ICommit BuildCommitStub(long checkpointToken, int streamRevision, int commitSequence) + { + List events = new[] { new EventMessage() }.ToList(); + return new Commit(Bucket.Default, streamId, streamRevision, Guid.NewGuid(), commitSequence, SystemTime.UtcNow, checkpointToken, null, events); + } + + protected CommitAttempt BuildCommitAttemptStub(int streamRevision, int commitSequence) + { + var events = new[] { new EventMessage() }; + return new CommitAttempt(Bucket.Default, streamId, streamRevision, Guid.NewGuid(), commitSequence, SystemTime.UtcNow, null, events); + } + + protected ICommit BuildCommitStub(long checkpointToken, Guid commitId, int streamRevision, int commitSequence) + { + List events = new[] { new EventMessage() }.ToList(); + return new Commit(Bucket.Default, streamId, streamRevision, commitId, commitSequence, SystemTime.UtcNow, checkpointToken, null, events); + } + } +} + +#pragma warning restore IDE1006 // Naming Styles +#pragma warning restore 169 // ReSharper enable InconsistentNaming \ No newline at end of file diff --git a/src/NEventStore.Tests/OptimisticEventStoreTests.cs b/src/NEventStore.Tests/OptimisticEventStoreTests.cs new file mode 100644 index 000000000..2666f0653 --- /dev/null +++ b/src/NEventStore.Tests/OptimisticEventStoreTests.cs @@ -0,0 +1,720 @@ + +#pragma warning disable 169 // ReSharper disable InconsistentNaming +#pragma warning disable IDE1006 // Naming Styles + +using FluentAssertions; +using FakeItEasy; +using NEventStore.Persistence; +using NEventStore.Persistence.AcceptanceTests; +using NEventStore.Persistence.AcceptanceTests.BDD; +#if MSTEST +using Microsoft.VisualStudio.TestTools.UnitTesting; +#endif +#if NUNIT +#endif +#if XUNIT +using Xunit; +using Xunit.Should; +#endif + +namespace NEventStore +{ +#if MSTEST + [TestClass] +#endif + public class when_creating_a_new_stream : using_persistence + { + private IEventStream? _stream; + + protected override void Because() + { + _stream = Store.CreateStream(streamId); + } + + [Fact] + public void should_return_a_new_stream() + { + _stream.Should().NotBeNull(); + } + + [Fact] + public void should_return_a_stream_with_the_correct_stream_identifier() + { + _stream!.StreamId.Should().Be(streamId); + } + + [Fact] + public void should_return_a_stream_with_a_zero_stream_revision() + { + _stream!.StreamRevision.Should().Be(0); + } + + [Fact] + public void should_return_a_stream_with_a_zero_commit_sequence() + { + _stream!.CommitSequence.Should().Be(0); + } + + [Fact] + public void should_return_a_stream_with_no_uncommitted_events() + { + _stream!.UncommittedEvents.Should().BeEmpty(); + } + + [Fact] + public void should_return_a_stream_with_no_committed_events() + { + _stream!.CommittedEvents.Should().BeEmpty(); + } + + [Fact] + public void should_return_a_stream_with_empty_headers() + { + _stream!.UncommittedHeaders.Should().BeEmpty(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_opening_an_empty_stream_starting_at_revision_zero : using_persistence + { + private IEventStream? _stream; + + protected override void Context() + { + A.CallTo(() => Persistence.GetFrom(Bucket.Default, streamId, 0, 0)).Returns([]); + } + + protected override void Because() + { + _stream = Store.OpenStream(streamId, 0, 0); + } + + [Fact] + public void should_return_a_new_stream() + { + _stream.Should().NotBeNull(); + } + + [Fact] + public void should_return_a_stream_with_the_correct_stream_identifier() + { + _stream!.StreamId.Should().Be(streamId); + } + + [Fact] + public void should_return_a_stream_with_a_zero_stream_revision() + { + _stream!.StreamRevision.Should().Be(0); + } + + [Fact] + public void should_return_a_stream_with_a_zero_commit_sequence() + { + _stream!.CommitSequence.Should().Be(0); + } + + [Fact] + public void should_return_a_stream_with_no_uncommitted_events() + { + _stream!.UncommittedEvents.Should().BeEmpty(); + } + + [Fact] + public void should_return_a_stream_with_no_committed_events() + { + _stream!.CommittedEvents.Should().BeEmpty(); + } + + [Fact] + public void should_return_a_stream_with_empty_headers() + { + _stream!.UncommittedHeaders.Should().BeEmpty(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_opening_an_empty_stream_starting_above_revision_zero : using_persistence + { + private const int MinRevision = 1; + private Exception? _thrown; + + protected override void Context() + { + A.CallTo(() => Persistence.GetFrom(Bucket.Default, streamId, MinRevision, int.MaxValue)) + .Returns([]); + } + + protected override void Because() + { + _thrown = Catch.Exception(() => Store.OpenStream(streamId, MinRevision)); + } + + [Fact] + public void should_throw_a_StreamNotFoundException() + { + _thrown.Should().BeOfType(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_opening_a_populated_stream : using_persistence + { + private const int MinRevision = 17; + private const int MaxRevision = 42; + private ICommit? _committed; + private IEventStream? _stream; + + protected override void Context() + { + _committed = BuildCommitStub(1, MinRevision, 1); + + A.CallTo(() => Persistence.GetFrom(Bucket.Default, streamId, MinRevision, MaxRevision)) + .Returns([_committed]); + + var hook = A.Fake(); + A.CallTo(() => hook.SelectCommit(_committed)).Returns(_committed); + PipelineHooks.Add(hook); + + var hookAsync = A.Fake(); + A.CallTo(() => hookAsync.SelectCommitAsync(_committed, A.Ignored)).Returns(Task.FromResult(_committed)); + PipelineHooksAsync.Add(hookAsync); + } + + protected override void Because() + { + _stream = Store.OpenStream(streamId, MinRevision, MaxRevision); + } + + [Fact] + public void should_invoke_the_underlying_infrastructure_with_the_values_provided() + { + A.CallTo(() => Persistence.GetFrom(Bucket.Default, streamId, MinRevision, MaxRevision)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public void should_provide_the_commits_to_the_selection_hooks() + { + PipelineHooks.ForEach(x => A.CallTo(() => x.SelectCommit(_committed!)).MustHaveHappenedOnceExactly()); + } + + [Fact] + public void should_provide_the_commits_to_the_async_selection_hooks() + { + PipelineHooksAsync.ForEach(x => A.CallTo(() => x.SelectCommitAsync(_committed!, A.Ignored)).MustHaveHappenedOnceExactly()); + } + + [Fact] + public void should_return_an_event_stream_containing_the_correct_stream_identifier() + { + _stream!.StreamId.Should().Be(streamId); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_opening_a_populated_stream_from_a_snapshot : using_persistence + { + private const int MaxRevision = int.MaxValue; + private ICommit[]? _committed; + private Snapshot? _snapshot; + + protected override void Context() + { + _snapshot = new Snapshot(streamId, 42, "snapshot"); + _committed = [BuildCommitStub(1, 42, 0)]; + + A.CallTo(() => Persistence.GetFrom(Bucket.Default, streamId, 42, MaxRevision)).Returns(_committed); + } + + protected override void Because() + { + Store.OpenStream(_snapshot!, MaxRevision); + } + + [Fact] + public void should_query_the_underlying_storage_using_the_revision_of_the_snapshot() + { + A.CallTo(() => Persistence.GetFrom(Bucket.Default, streamId, 42, MaxRevision)).MustHaveHappenedOnceExactly(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_opening_a_stream_from_a_snapshot_that_is_at_the_revision_of_the_stream_head : using_persistence + { + private const int HeadStreamRevision = 42; + private const int HeadCommitSequence = 15; + private EnumerableCounter? _committed; + private Snapshot? _snapshot; + private IEventStream? _stream; + + protected override void Context() + { + _snapshot = new Snapshot(streamId, HeadStreamRevision, "snapshot"); + _committed = new EnumerableCounter( + [BuildCommitStub(1, HeadStreamRevision, HeadCommitSequence)]); + + A.CallTo(() => Persistence.GetFrom(Bucket.Default, streamId, HeadStreamRevision, int.MaxValue)) + .Returns(_committed); + } + + protected override void Because() + { + _stream = Store.OpenStream(_snapshot!, int.MaxValue); + } + + [Fact] + public void should_return_a_stream_with_the_correct_stream_identifier() + { + _stream!.StreamId.Should().Be(streamId); + } + + [Fact] + public void should_return_a_stream_with_revision_of_the_stream_head() + { + _stream!.StreamRevision.Should().Be(HeadStreamRevision); + } + + [Fact] + public void should_return_a_stream_with_a_commit_sequence_of_the_stream_head() + { + _stream!.CommitSequence.Should().Be(HeadCommitSequence); + } + + [Fact] + public void should_return_a_stream_with_no_committed_events() + { + _stream!.CommittedEvents.Count.Should().Be(0); + } + + [Fact] + public void should_return_a_stream_with_no_uncommitted_events() + { + _stream!.UncommittedEvents.Count.Should().Be(0); + } + + [Fact] + public void should_only_enumerate_the_set_of_commits_once() + { + _committed!.GetEnumeratorCallCount.Should().Be(1); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_reading_from_revision_zero : using_persistence + { + protected override void Context() + { + A.CallTo(() => Persistence.GetFrom(Bucket.Default, streamId, 0, int.MaxValue)) + .Returns([]); + } + + protected override void Because() + { + // ReSharper disable once ReturnValueOfPureMethodIsNotUsed + // This forces the enumeration of the commits. + Store.GetFrom(streamId, 0, int.MaxValue).ToList(); + } + + [Fact] + public void should_pass_a_revision_range_to_the_persistence_infrastructure() + { + A.CallTo(() => Persistence.GetFrom(Bucket.Default, streamId, 0, int.MaxValue)).MustHaveHappenedOnceExactly(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_reading_up_to_revision_revision_zero : using_persistence + { + private ICommit? _committed; + + protected override void Context() + { + _committed = BuildCommitStub(1, 1, 1); + + A.CallTo(() => Persistence.GetFrom(Bucket.Default, streamId, 0, int.MaxValue)).Returns([_committed]); + } + + protected override void Because() + { + Store.OpenStream(streamId, 0, 0); + } + + [Fact] + public void should_pass_the_maximum_possible_revision_to_the_persistence_infrastructure() + { + A.CallTo(() => Persistence.GetFrom(Bucket.Default, streamId, 0, int.MaxValue)).MustHaveHappenedOnceExactly(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_reading_from_a_null_snapshot : using_persistence + { + private Exception? thrown; + + protected override void Because() + { +#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. + thrown = Catch.Exception(() => Store.OpenStream(null, int.MaxValue)); +#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. + } + + [Fact] + public void should_throw_an_ArgumentNullException() + { + thrown.Should().BeOfType(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_reading_from_a_snapshot_up_to_revision_revision_zero : using_persistence + { + private ICommit? _committed; + private Snapshot? snapshot; + + protected override void Context() + { + snapshot = new Snapshot(streamId, 1, "snapshot"); + _committed = BuildCommitStub(1, 1, 1); + + A.CallTo(() => Persistence.GetFrom(Bucket.Default, streamId, snapshot.StreamRevision, int.MaxValue)) + .Returns([_committed]); + } + + protected override void Because() + { + Store.OpenStream(snapshot!, 0); + } + + [Fact] + public void should_pass_the_maximum_possible_revision_to_the_persistence_infrastructure() + { + A.CallTo(() => Persistence.GetFrom(Bucket.Default, streamId, snapshot!.StreamRevision, int.MaxValue)).MustHaveHappenedOnceExactly(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_committing_a_null_attempt_back_to_the_stream : using_persistence + { + private Exception? thrown; + + protected override void Because() + { +#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. + thrown = Catch.Exception(() => Store.Commit(null)); +#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. + } + + [Fact] + public void should_throw_an_ArgumentNullException() + { + thrown.Should().BeOfType(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_committing_with_a_valid_and_populated_attempt_to_a_stream : using_persistence + { + private CommitAttempt? _populatedAttempt; + private ICommit? _populatedCommit; + + protected override void Context() + { + _populatedAttempt = BuildCommitAttemptStub(1, 1); + + A.CallTo(() => Persistence.Commit(_populatedAttempt)) + .ReturnsLazily((CommitAttempt attempt) => + { + _populatedCommit = new Commit(attempt.BucketId, + attempt.StreamId, + attempt.StreamRevision, + attempt.CommitId, + attempt.CommitSequence, + attempt.CommitStamp, + 1, + attempt.Headers, + attempt.Events); + return _populatedCommit; + }); + + var hook = A.Fake(); + A.CallTo(() => hook.PreCommit(_populatedAttempt)).Returns(true); + PipelineHooks.Add(hook); + + var hookAsync = A.Fake(); + A.CallTo(() => hookAsync.PreCommitAsync(_populatedAttempt, A.Ignored)).Returns(Task.FromResult(true)); + PipelineHooksAsync.Add(hookAsync); + } + + protected override void Because() + { + Store.Commit(_populatedAttempt!); + } + + [Fact] + public void should_provide_the_commit_to_the_PreCommit_hooks() + { + PipelineHooks.ForEach(x => A.CallTo(() => x.PreCommit(_populatedAttempt!)).MustHaveHappenedOnceExactly()); + } + + [Fact] + public void should_provide_the_commit_to_the_async_PreCommit_hooks() + { + PipelineHooksAsync.ForEach(x => A.CallTo(() => x.PreCommitAsync(_populatedAttempt!, A.Ignored)).MustHaveHappenedOnceExactly()); + } + + [Fact] + public void should_provide_the_commit_attempt_to_the_configured_persistence_mechanism() + { + A.CallTo(() => Persistence.Commit(_populatedAttempt!)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public void should_provide_the_commit_to_the_PostCommit_hooks() + { + PipelineHooks.ForEach(x => A.CallTo(() => x.PostCommit(_populatedCommit!)).MustHaveHappenedOnceExactly()); + } + + [Fact] + public void should_provide_the_commit_to_the_async_PostCommit_hooks() + { + PipelineHooksAsync.ForEach(x => A.CallTo(() => x.PostCommitAsync(_populatedCommit!, A.Ignored)).MustHaveHappenedOnceExactly()); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_a_PreCommit_hook_rejects_a_commit : using_persistence + { + private CommitAttempt? _attempt; + private ICommit? _commit; + + protected override void Context() + { + _attempt = BuildCommitAttemptStub(1, 1); + _commit = BuildCommitStub(1, 1, 1); + + var hook = A.Fake(); + A.CallTo(() => hook.PreCommit(_attempt)).Returns(false); + PipelineHooks.Add(hook); + + var hookAsync = A.Fake(); + A.CallTo(() => hookAsync.PreCommitAsync(_attempt, A.Ignored)).Returns(Task.FromResult(true)); + PipelineHooksAsync.Add(hookAsync); + } + + protected override void Because() + { + Store.Commit(_attempt!); + } + + [Fact] + public void should_not_call_the_underlying_infrastructure() + { + A.CallTo(() => Persistence.Commit(_attempt!)).MustNotHaveHappened(); + } + + [Fact] + public void should_not_provide_the_commit_to_the_PostCommit_hooks() + { + PipelineHooks.ForEach(x => A.CallTo(() => x.PostCommit(_commit!)).MustNotHaveHappened()); + } + + [Fact] + public void should_not_provide_the_commit_to_the_async_PostCommit_hooks() + { + PipelineHooksAsync.ForEach(x => A.CallTo(() => x.PostCommitAsync(_commit!, A.Ignored)).MustNotHaveHappened()); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_an_async_PreCommit_hook_rejects_a_commit : using_persistence + { + private CommitAttempt? _attempt; + private ICommit? _commit; + + protected override void Context() + { + _attempt = BuildCommitAttemptStub(1, 1); + _commit = BuildCommitStub(1, 1, 1); + + var hook = A.Fake(); + A.CallTo(() => hook.PreCommit(_attempt)).Returns(true); + PipelineHooks.Add(hook); + + var hookAsync = A.Fake(); + A.CallTo(() => hookAsync.PreCommitAsync(_attempt, A.Ignored)).Returns(Task.FromResult(false)); + PipelineHooksAsync.Add(hookAsync); + } + + protected override void Because() + { + Store.Commit(_attempt!); + } + + [Fact] + public void should_not_call_the_underlying_infrastructure() + { + A.CallTo(() => Persistence.Commit(_attempt!)).MustNotHaveHappened(); + } + + [Fact] + public void should_not_provide_the_commit_to_the_PostCommit_hooks() + { + PipelineHooks.ForEach(x => A.CallTo(() => x.PostCommit(_commit!)).MustNotHaveHappened()); + } + + [Fact] + public void should_not_provide_the_commit_to_the_async_PostCommit_hooks() + { + PipelineHooksAsync.ForEach(x => A.CallTo(() => x.PostCommitAsync(_commit!, A.Ignored)).MustNotHaveHappened()); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_accessing_the_underlying_persistence_with_pipeline_hooks : using_persistence + { + protected override void Because() + { + PipelineHooks.Add(A.Fake()); + } + + [Fact] + public void should_return_a_reference_to_the_underlying_persistence_infrastructure_decorator() + { + Store.Advanced.Should().BeOfType(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_accessing_the_underlying_persistence_with_async_pipeline_hooks : using_persistence + { + protected override void Because() + { + PipelineHooksAsync.Add(A.Fake()); + } + + [Fact] + public void should_return_a_reference_to_the_underlying_persistence_infrastructure_decorator() + { + Store.Advanced.Should().BeOfType(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_accessing_the_underlying_persistence_without_pipeline_hooks : using_persistence + { + [Fact] + public void should_return_a_reference_to_the_underlying_persistence() + { + Store.Advanced.Should().BeOfType(Persistence.GetType()); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_disposing_the_event_store : using_persistence + { + protected override void Because() + { + Store.Dispose(); + } + + [Fact] + public void should_dispose_the_underlying_persistence() + { + A.CallTo(() => Persistence.Dispose()).MustHaveHappenedOnceExactly(); + } + } + + public abstract class using_persistence : SpecificationBase + { + private IPersistStreams? persistence; + + private List? pipelineHooks; + private List? pipelineHooksAsync; + private OptimisticEventStore? store; + protected string streamId = Guid.NewGuid().ToString(); + + protected IPersistStreams Persistence + { + get { return persistence ??= A.Fake(); } + } + + protected List PipelineHooks + { + get { return pipelineHooks ??= []; } + } + + protected List PipelineHooksAsync + { + get { return pipelineHooksAsync ??= []; } + } + + protected OptimisticEventStore Store + { + get { return store ??= new OptimisticEventStore(Persistence, PipelineHooks.Select(x => x), PipelineHooksAsync.Select(x => x)); } + } + + protected override void Cleanup() + { + streamId = Guid.NewGuid().ToString(); + } + + protected CommitAttempt BuildCommitAttemptStub(Guid commitId) + { + return new CommitAttempt(Bucket.Default, streamId, 1, commitId, 1, SystemTime.UtcNow, null, []); + } + + protected ICommit BuildCommitStub(long checkpointToken, int streamRevision, int commitSequence) + { + List events = new[] { new EventMessage() }.ToList(); + return new Commit(Bucket.Default, streamId, streamRevision, Guid.NewGuid(), commitSequence, SystemTime.UtcNow, checkpointToken, null, events); + } + + protected CommitAttempt BuildCommitAttemptStub(int streamRevision, int commitSequence) + { + var events = new[] { new EventMessage() }; + return new CommitAttempt(Bucket.Default, streamId, streamRevision, Guid.NewGuid(), commitSequence, SystemTime.UtcNow, null, events); + } + + protected ICommit BuildCommitStub(long checkpointToken, Guid commitId, int streamRevision, int commitSequence) + { + List events = new[] { new EventMessage() }.ToList(); + return new Commit(Bucket.Default, streamId, streamRevision, commitId, commitSequence, SystemTime.UtcNow, checkpointToken, null, events); + } + } +} + +#pragma warning restore IDE1006 // Naming Styles +#pragma warning restore 169 // ReSharper enable InconsistentNaming \ No newline at end of file diff --git a/src/NEventStore.Tests/OptimisticEventStreamTests.Async.cs b/src/NEventStore.Tests/OptimisticEventStreamTests.Async.cs new file mode 100644 index 000000000..31da8a9e6 --- /dev/null +++ b/src/NEventStore.Tests/OptimisticEventStreamTests.Async.cs @@ -0,0 +1,1038 @@ +using FakeItEasy; +using FluentAssertions; +using NEventStore.Persistence; +using NEventStore.Persistence.AcceptanceTests; +using NEventStore.Persistence.AcceptanceTests.BDD; +#if MSTEST +using Microsoft.VisualStudio.TestTools.UnitTesting; +#endif +#if XUNIT +using Xunit; +using Xunit.Should; +#endif + +#pragma warning disable 169 // ReSharper enable InconsistentNaming +#pragma warning disable IDE1006 // Naming Styles + +namespace NEventStore.Async +{ +#if MSTEST + [TestClass] +#endif + public class when_building_a_stream : on_the_event_stream + { + private const int MinRevision = 2; + private const int MaxRevision = 7; + private readonly int _eachCommitHas = 2; // events + private ICommit[]? _committed; + + protected override void Context() + { + _committed = + [ + BuildCommitStub(1, 2, 1, _eachCommitHas), // 1-2 + BuildCommitStub(2, 4, 2, _eachCommitHas), // 3-4 + BuildCommitStub(3, 6, 3, _eachCommitHas), // 5-6 + BuildCommitStub(4, 8, 4, _eachCommitHas) // 7-8 + ]; + + _committed[0].Headers["Common"] = string.Empty; + _committed[1].Headers["Common"] = string.Empty; + _committed[2].Headers["Common"] = string.Empty; + _committed[3].Headers["Common"] = string.Empty; + _committed[0].Headers["Unique"] = string.Empty; + + A.CallTo(() => PersistenceAsync.GetFromAsync(BucketId, StreamId, MinRevision, MaxRevision, A>.Ignored, CancellationToken.None)) + .ReturnsLazily(async (string bucketId, string streamId, int minRevision, int maxRevision, IAsyncObserver asyncObserver, CancellationToken cancellation) => + { + foreach (var _commit in _committed) + { + await asyncObserver.OnNextAsync(_commit, cancellation).ConfigureAwait(false); + } + await asyncObserver.OnCompletedAsync(cancellation).ConfigureAwait(false); + }); + } + + protected override Task BecauseAsync() + { + Stream = new OptimisticEventStream(BucketId, StreamId, Persistence, PersistenceAsync); + return Stream.InitializeAsync(MinRevision, MaxRevision, CancellationToken.None); + } + + [Fact] + public void should_have_the_correct_stream_identifier() + { + Stream.StreamId.Should().Be(StreamId); + } + + [Fact] + public void should_have_the_correct_head_stream_revision() + { + Stream.StreamRevision.Should().Be(MaxRevision); + } + + [Fact] + public void should_have_the_correct_head_commit_sequence() + { + Stream.CommitSequence.Should().Be(_committed!.Last().CommitSequence); + } + + [Fact] + public void should_not_include_events_below_the_minimum_revision_indicated() + { + Stream.CommittedEvents.First().Should().Be(_committed![0].Events.Last()); + } + + [Fact] + public void should_not_include_events_above_the_maximum_revision_indicated() + { + Stream.CommittedEvents.Last().Should().Be(_committed!.Last().Events.First()); + } + + [Fact] + public void should_have_all_of_the_committed_events_up_to_the_stream_revision_specified() + { + Stream.CommittedEvents.Count.Should().Be(MaxRevision - MinRevision + 1); + } + + [Fact] + public void should_contain_the_headers_from_the_underlying_commits() + { + Stream.CommittedHeaders.Count.Should().Be(2); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_initializing_an_already_initialized_stream: on_the_event_stream + { + private const int MinRevision = 2; + private const int MaxRevision = 7; + private readonly int _eachCommitHas = 2; // events + private ICommit[]? _committed; + private Exception? _thrownInitializeRevisionRange; + private Exception? _thrownInitializeSnapshot; + + protected override Task ContextAsync() + { + _committed = + [ + BuildCommitStub(1, 2, 1, _eachCommitHas), // 1-2 + BuildCommitStub(2, 4, 2, _eachCommitHas), // 3-4 + BuildCommitStub(3, 6, 3, _eachCommitHas), // 5-6 + BuildCommitStub(4, 8, 4, _eachCommitHas) // 7-8 + ]; + + _committed[0].Headers["Common"] = string.Empty; + _committed[1].Headers["Common"] = string.Empty; + _committed[2].Headers["Common"] = string.Empty; + _committed[3].Headers["Common"] = string.Empty; + _committed[0].Headers["Unique"] = string.Empty; + + A.CallTo(() => PersistenceAsync.GetFromAsync(BucketId, StreamId, MinRevision, MaxRevision, A>.Ignored, CancellationToken.None)) + .ReturnsLazily(async (string bucketId, string streamId, int minRevision, int maxRevision, IAsyncObserver asyncObserver, CancellationToken cancellation) => + { + foreach (var _commit in _committed) + { + await asyncObserver.OnNextAsync(_commit, cancellation).ConfigureAwait(false); + } + await asyncObserver.OnCompletedAsync(cancellation).ConfigureAwait(false); + }); + + Stream = new OptimisticEventStream(BucketId, StreamId, Persistence, PersistenceAsync); + return Stream.InitializeAsync(MinRevision, MaxRevision, CancellationToken.None); + } + + protected override async Task BecauseAsync() + { + _thrownInitializeRevisionRange = await Catch.ExceptionAsync(() => Stream.InitializeAsync(MinRevision, MaxRevision, CancellationToken.None)).ConfigureAwait(false); + _thrownInitializeSnapshot = await Catch.ExceptionAsync(() => Stream.InitializeAsync(new Snapshot(BucketId, StreamId, MinRevision, new object()), MaxRevision, CancellationToken.None)).ConfigureAwait(false); + } + + [Fact] + public void cannot_initialize_a_stream_using_revision_range() + { + _thrownInitializeRevisionRange.Should().BeOfType(); + } + + [Fact] + public void cannot_initialize_a_stream_using_snapshot() + { + _thrownInitializeSnapshot.Should().BeOfType(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_initializing_an_already_used_stream_with_revision_range : on_the_event_stream + { + private const int MinRevision = 2; + private const int MaxRevision = 7; + private Exception? _thrownInitializeRevisionRange; + private Exception? _thrownInitializeSnapshot; + + protected override void Context() + { + Stream = new OptimisticEventStream(BucketId, StreamId, Persistence, PersistenceAsync); + Stream.Add(new EventMessage { Body = "Test" }); + } + + protected override async Task BecauseAsync() + { + _thrownInitializeRevisionRange = await Catch.ExceptionAsync(() => Stream.InitializeAsync(MinRevision, MaxRevision, CancellationToken.None)).ConfigureAwait(false); + _thrownInitializeSnapshot = await Catch.ExceptionAsync(() => Stream.InitializeAsync(new Snapshot(BucketId, StreamId, MinRevision, new object()), MaxRevision, CancellationToken.None)).ConfigureAwait(false); + } + + [Fact] + public void cannot_initialize_a_stream_using_revision_range() + { + _thrownInitializeRevisionRange.Should().BeOfType(); + } + + [Fact] + public void cannot_initialize_a_stream_using_snapshot() + { + _thrownInitializeSnapshot.Should().BeOfType(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_the_head_event_revision_is_less_than_the_max_desired_revision : on_the_event_stream + { + private readonly int _eventsPerCommit = 2; + private ICommit[]? _committed; + + protected override void Context() + { + _committed = + [ + BuildCommitStub(1, 2, 1, _eventsPerCommit), // 1-2 + BuildCommitStub(2, 4, 2, _eventsPerCommit), // 3-4 + BuildCommitStub(3, 6, 3, _eventsPerCommit), // 5-6 + BuildCommitStub(4, 8, 4, _eventsPerCommit) // 7-8 + ]; + + A.CallTo(() => PersistenceAsync.GetFromAsync(BucketId, StreamId, 0, int.MaxValue, A>.Ignored, CancellationToken.None)) + .ReturnsLazily(async (string bucketId, string streamId, int minRevision, int maxRevision, IAsyncObserver asyncObserver, CancellationToken cancellation) => + { + foreach (var _commit in _committed) + { + await asyncObserver.OnNextAsync(_commit, cancellation).ConfigureAwait(false); + } + await asyncObserver.OnCompletedAsync(cancellation).ConfigureAwait(false); + }); + } + + protected override Task BecauseAsync() + { + Stream = new OptimisticEventStream(BucketId, StreamId, Persistence, PersistenceAsync); + return Stream.InitializeAsync(0, int.MaxValue, CancellationToken.None); + } + + [Fact] + public void should_set_the_stream_revision_to_the_revision_of_the_most_recent_event() + { + Stream.StreamRevision.Should().Be(_committed!.Last().StreamRevision); + } + + [Fact] + public void should_set_the_commit_sequence_the_most_recent_commit_sequence() + { + Stream.CommitSequence.Should().Be(_committed!.Last().CommitSequence); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_reading_up_to_revision : on_the_event_stream + { + private readonly int _eventsPerCommit = 2; + private ICommit[]? _committed; + + protected override void Context() + { + _committed = + [ + BuildCommitStub(1, 2, 1, _eventsPerCommit), // 1-2 + BuildCommitStub(2, 4, 2, _eventsPerCommit), // 3-4 + BuildCommitStub(3, 6, 3, _eventsPerCommit), // 5-6 + BuildCommitStub(4, 8, 4, _eventsPerCommit) // 7-8 + ]; + + A.CallTo(() => PersistenceAsync.GetFromAsync(BucketId, StreamId, 0, 6, A>.Ignored, CancellationToken.None)) + .ReturnsLazily(async (string bucketId, string streamId, int minRevision, int maxRevision, IAsyncObserver asyncObserver, CancellationToken cancellation) => + { + foreach (var _commit in _committed) + { + await asyncObserver.OnNextAsync(_commit, cancellation).ConfigureAwait(false); + } + await asyncObserver.OnCompletedAsync(cancellation).ConfigureAwait(false); + }); + } + + protected override Task BecauseAsync() + { + Stream = new OptimisticEventStream(BucketId, StreamId, Persistence, PersistenceAsync); + return Stream.InitializeAsync(0, 6, CancellationToken.None); + } + + [Fact] + public void should_set_the_stream_revision_to_the_revision_of_the_correct_commit() + { + Stream.StreamRevision.Should().Be(_committed![2].StreamRevision); + } + + [Fact] + public void should_set_the_commit_sequence_to_the_sequence_of_the_correct_commit() + { + Stream.CommitSequence.Should().Be(_committed![2].CommitSequence); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_adding_a_null_event_message : on_the_event_stream + { + private Exception? _thrown; + + protected override void Because() + { +#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. + _thrown = Catch.Exception(() => Stream.Add(null)); +#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. + } + + [Fact] + public void should_throw() + { + _thrown.Should().BeOfType(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_adding_an_unpopulated_event_message : on_the_event_stream + { + private Exception? _thrown; + + protected override void Because() + { +#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. + _thrown = Catch.Exception(() => Stream.Add(new EventMessage { Body = null })); +#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. + } + + [Fact] + public void should_throw() + { + _thrown.Should().BeOfType(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_adding_a_fully_populated_event_message : on_the_event_stream + { + protected override void Because() + { + Stream.Add(new EventMessage { Body = "populated" }); + } + + [Fact] + public void should_add_the_event_to_the_set_of_uncommitted_events() + { + Stream.UncommittedEvents.Count.Should().Be(1); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_adding_multiple_populated_event_messages : on_the_event_stream + { + protected override void Because() + { + Stream.Add(new EventMessage { Body = "populated" }); + Stream.Add(new EventMessage { Body = "also populated" }); + } + + [Fact] + public void should_add_all_of_the_events_provided_to_the_set_of_uncommitted_events() + { + Stream.UncommittedEvents.Count.Should().Be(2); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_adding_a_simple_object_as_an_event_message : on_the_event_stream + { + private const string MyEvent = "some event data"; + + protected override void Because() + { + Stream.Add(new EventMessage { Body = MyEvent }); + } + + [Fact] + public void should_add_the_uncommitted_event_to_the_set_of_uncommitted_events() + { + Stream.UncommittedEvents.Count.Should().Be(1); + } + + [Fact] + public void should_wrap_the_uncommitted_event_in_an_EventMessage_object() + { + Stream.UncommittedEvents.First().Body.Should().Be(MyEvent); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_clearing_any_uncommitted_changes : on_the_event_stream + { + protected override void Context() + { + Stream.Add(new EventMessage { Body = string.Empty }); + } + + protected override void Because() + { + Stream.ClearChanges(); + } + + [Fact] + public void should_clear_all_uncommitted_events() + { + Stream.UncommittedEvents.Count.Should().Be(0); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_committing_an_empty_changeset : on_the_event_stream + { + protected override Task BecauseAsync() + { + return Stream.CommitChangesAsync(Guid.NewGuid(), CancellationToken.None); + } + + [Fact] + public void should_not_call_the_underlying_infrastructure() + { + A.CallTo(() => PersistenceAsync.CommitAsync(A._, CancellationToken.None)).MustNotHaveHappened(); + } + + [Fact] + public void should_not_increment_the_current_stream_revision() + { + Stream.StreamRevision.Should().Be(0); + } + + [Fact] + public void should_not_increment_the_current_commit_sequence() + { + Stream.CommitSequence.Should().Be(0); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_committing_any_uncommitted_changes : on_the_event_stream + { + private readonly Guid _commitId = Guid.NewGuid(); + private readonly Dictionary _headers = new() { { "key", "value" } }; + private readonly EventMessage _uncommitted = new() { Body = string.Empty }; + private CommitAttempt? _constructed; + + protected override void Context() + { + A.CallTo(() => PersistenceAsync.CommitAsync(A._, CancellationToken.None)) + .Invokes((CommitAttempt commit, CancellationToken _) => _constructed = commit) + .ReturnsLazily((CommitAttempt attempt, CancellationToken _) => new Commit( + attempt.BucketId, + attempt.StreamId, + attempt.StreamRevision, + attempt.CommitId, + attempt.CommitSequence, + attempt.CommitStamp, + 1, + attempt.Headers, + attempt.Events)); + Stream.Add(_uncommitted); + foreach (var item in _headers) + { + Stream.UncommittedHeaders[item.Key] = item.Value; + } + } + + protected override Task BecauseAsync() + { + return Stream.CommitChangesAsync(_commitId, CancellationToken.None); + } + + [Fact] + public void should_provide_a_commit_to_the_underlying_infrastructure() + { + A.CallTo(() => PersistenceAsync.CommitAsync(A._, CancellationToken.None)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public void should_build_the_commit_with_the_correct_bucket_identifier() + { + _constructed!.BucketId.Should().Be(BucketId); + } + + [Fact] + public void should_build_the_commit_with_the_correct_stream_identifier() + { + _constructed!.StreamId.Should().Be(StreamId); + } + + [Fact] + public void should_build_the_commit_with_the_correct_stream_revision() + { + _constructed!.StreamRevision.Should().Be(DefaultStreamRevision); + } + + [Fact] + public void should_build_the_commit_with_the_correct_commit_identifier() + { + _constructed!.CommitId.Should().Be(_commitId); + } + + [Fact] + public void should_build_the_commit_with_an_incremented_commit_sequence() + { + _constructed!.CommitSequence.Should().Be(DefaultCommitSequence); + } + + [Fact] + public void should_build_the_commit_with_the_correct_commit_stamp() + { + SystemTime.UtcNow.Should().Be(_constructed!.CommitStamp); + } + + [Fact] + public void should_build_the_commit_with_the_headers_provided() + { + _constructed!.Headers[_headers.First().Key].Should().Be(_headers.First().Value); + } + + [Fact] + public void should_build_the_commit_containing_all_uncommitted_events() + { + _constructed!.Events.Count.Should().Be(_headers.Count); + } + + [Fact] + public void should_build_the_commit_using_the_event_messages_provided() + { + _constructed!.Events.First().Should().Be(_uncommitted); + } + + [Fact] + public void should_contain_a_copy_of_the_headers_provided() + { + _constructed!.Headers.Should().NotBeEmpty(); + } + + [Fact] + public void should_update_the_stream_revision() + { + Stream.StreamRevision.Should().Be(_constructed!.StreamRevision); + } + + [Fact] + public void should_update_the_commit_sequence() + { + Stream.CommitSequence.Should().Be(_constructed!.CommitSequence); + } + + [Fact] + public void should_add_the_uncommitted_events_the_committed_events() + { + Stream.CommittedEvents.Last().Should().Be(_uncommitted); + } + + [Fact] + public void should_clear_the_uncommitted_events_on_the_stream() + { + Stream.UncommittedEvents.Should().BeEmpty(); + } + + [Fact] + public void should_clear_the_uncommitted_headers_on_the_stream() + { + Stream.UncommittedHeaders.Should().BeEmpty(); + } + + [Fact] + public void should_copy_the_uncommitted_headers_to_the_committed_stream_headers() + { + Stream.CommittedHeaders.Count.Should().Be(_headers.Count); + } + } + + /// + /// This behavior is primarily to support a NoSQL storage solution where CommitId is not being used as the "primary key" + /// in a NoSQL environment, we'll most likely use StreamId + CommitSequence, which also enables optimistic concurrency. + /// +#if MSTEST + [TestClass] +#endif + public class when_committing_with_an_identifier_that_was_previously_read : on_the_event_stream + { + private ICommit[]? _committed; + private Guid _duplicateCommitId; + private Exception? _thrown; + + protected override Task ContextAsync() + { + _committed = [BuildCommitStub(1, 1, 1, 1)]; + _duplicateCommitId = _committed[0].CommitId; + + A.CallTo(() => PersistenceAsync.GetFromAsync(BucketId, StreamId, 0, int.MaxValue, A>.Ignored, CancellationToken.None)) + .ReturnsLazily(async (string bucketId, string streamId, int minRevision, int maxRevision, IAsyncObserver asyncObserver, CancellationToken cancellation) => + { + foreach (var _commit in _committed) + { + await asyncObserver.OnNextAsync(_commit, cancellation).ConfigureAwait(false); + } + await asyncObserver.OnCompletedAsync(cancellation).ConfigureAwait(false); + }); + + Stream = new OptimisticEventStream(BucketId, StreamId, Persistence, PersistenceAsync); + return Stream.InitializeAsync(0, int.MaxValue, CancellationToken.None); + } + + protected override async Task BecauseAsync() + { + _thrown = await Catch.ExceptionAsync(() => Stream.CommitChangesAsync(_duplicateCommitId, CancellationToken.None)).ConfigureAwait(false); + } + + [Fact] + public void should_throw_a_DuplicateCommitException() + { + _thrown.Should().BeOfType(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_committing_after_another_thread_or_process_has_moved_the_stream_head : on_the_event_stream + { + private const int StreamRevision = 1; + private readonly EventMessage _uncommitted = new() { Body = string.Empty }; + private ICommit[]? _committed; + private ICommit[]? _discoveredOnCommit; + private Exception? _thrown; + + protected override async Task ContextAsync() + { + _committed = [BuildCommitStub(1, 1, 1, 1)]; + _discoveredOnCommit = [BuildCommitStub(2, 3, 2, 2)]; + + A.CallTo(() => PersistenceAsync.CommitAsync(A._, CancellationToken.None)).Throws(new ConcurrencyException()); + A.CallTo(() => PersistenceAsync.GetFromAsync(BucketId, StreamId, StreamRevision, int.MaxValue, A>.Ignored, CancellationToken.None)) + .ReturnsLazily(async (string bucketId, string streamId, int minRevision, int maxRevision, IAsyncObserver asyncObserver, CancellationToken cancellation) => + { + foreach (var _commit in _committed) + { + await asyncObserver.OnNextAsync(_commit, cancellation).ConfigureAwait(false); + } + await asyncObserver.OnCompletedAsync(cancellation).ConfigureAwait(false); + }); + A.CallTo(() => PersistenceAsync.GetFromAsync(BucketId, StreamId, StreamRevision +1, int.MaxValue, A>.Ignored, CancellationToken.None)) + .ReturnsLazily(async (string bucketId, string streamId, int minRevision, int maxRevision, IAsyncObserver asyncObserver, CancellationToken cancellation) => + { + foreach (var _commit in _discoveredOnCommit) + { + await asyncObserver.OnNextAsync(_commit, cancellation).ConfigureAwait(false); + } + await asyncObserver.OnCompletedAsync(cancellation).ConfigureAwait(false); + }); + + Stream = new OptimisticEventStream(BucketId, StreamId, Persistence, PersistenceAsync); + await Stream.InitializeAsync(StreamRevision, int.MaxValue, CancellationToken.None); + Stream.Add(_uncommitted); + } + + protected override async Task BecauseAsync() + { + _thrown = await Catch.ExceptionAsync(() => Stream.CommitChangesAsync(Guid.NewGuid(), CancellationToken.None)).ConfigureAwait(false); + } + + [Fact] + public void should_throw_a_ConcurrencyException() + { + _thrown.Should().BeOfType(); + } + + [Fact] + public void should_query_the_underlying_storage_to_discover_the_new_commits() + { + A.CallTo(() => PersistenceAsync.GetFromAsync(BucketId, StreamId, StreamRevision + 1, int.MaxValue, A>.Ignored, CancellationToken.None)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public void should_update_the_stream_revision_accordingly() + { + Stream.StreamRevision.Should().Be(_discoveredOnCommit![0].StreamRevision); + } + + [Fact] + public void should_update_the_commit_sequence_accordingly() + { + Stream.CommitSequence.Should().Be(_discoveredOnCommit![0].CommitSequence); + } + + [Fact] + public void should_add_the_newly_discovered_committed_events_to_the_set_of_committed_events_accordingly() + { + Stream.CommittedEvents.Count.Should().Be(_discoveredOnCommit![0].Events.Count + 1); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_attempting_to_invoke_behavior_on_a_disposed_stream : on_the_event_stream + { + private Exception? _thrown; + + protected override void Context() + { + Stream.Dispose(); + } + + protected override async Task BecauseAsync() + { + _thrown = await Catch.ExceptionAsync(() => Stream.CommitChangesAsync(Guid.NewGuid(), CancellationToken.None)).ConfigureAwait(false); + } + + [Fact] + public void should_throw_a_ObjectDisposedException() + { + _thrown.Should().BeOfType(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_attempting_to_modify_the_event_collections : on_the_event_stream + { + [Fact] + public void should_throw_an_exception_when_adding_to_the_committed_collection() + { + Catch.Exception(() => Stream.CommittedEvents.Add(new EventMessage())).Should().BeOfType(); + } + + [Fact] + public void should_throw_an_exception_when_adding_to_the_uncommitted_collection() + { + Catch.Exception(() => Stream.UncommittedEvents.Add(new EventMessage())).Should().BeOfType(); + } + + [Fact] + public void should_throw_an_exception_when_clearing_the_committed_collection() + { + Catch.Exception(() => Stream.CommittedEvents.Clear()).Should().BeOfType(); + } + + [Fact] + public void should_throw_an_exception_when_clearing_the_uncommitted_collection() + { + Catch.Exception(() => Stream.UncommittedEvents.Clear()).Should().BeOfType(); + } + + [Fact] + public void should_throw_an_exception_when_removing_from_the_committed_collection() + { + Catch.Exception(() => Stream.CommittedEvents.Remove(new EventMessage())).Should().BeOfType(); + } + + [Fact] + public void should_throw_an_exception_when_removing_from_the_uncommitted_collection() + { + Catch.Exception(() => Stream.UncommittedEvents.Remove(new EventMessage())).Should().BeOfType(); + } + } + + /// + /// All other cases should be handled by persistence providers that have unique indexes on + /// the CommitSequence (the providers should be able to detect that the last commit loaded, after + /// which we are appending new events, is not the last one of the stream) + /// https://github.com/NEventStore/NEventStore/issues/420 + /// +#if MSTEST + [TestClass] +#endif + public class issue_420_when_adding_events_in_the_middle_of_the_last_commit : on_the_event_stream + { + private const int MinRevision = 2; + private const int MaxRevision = 7; + private readonly int _eventsPerCommit = 2; + private ICommit[]? _committed; + private Exception? _thrown1; + private Exception? _thrown2; + + protected override Task ContextAsync() + { + _committed = + [ + BuildCommitStub(1, 2, 1, _eventsPerCommit), // 1-2 + BuildCommitStub(2, 4, 2, _eventsPerCommit), // 3-4 + BuildCommitStub(3, 6, 3, _eventsPerCommit), // 5-6 + BuildCommitStub(4, 8, 4, _eventsPerCommit) // 7-8 + ]; + + A.CallTo(() => PersistenceAsync.GetFromAsync(BucketId, StreamId, MinRevision, MaxRevision, A>.Ignored, CancellationToken.None)) + .ReturnsLazily(async (string bucketId, string streamId, int minRevision, int maxRevision, IAsyncObserver asyncObserver, CancellationToken cancellation) => + { + foreach (var _commit in _committed) + { + await asyncObserver.OnNextAsync(_commit, cancellation).ConfigureAwait(false); + } + await asyncObserver.OnCompletedAsync(cancellation).ConfigureAwait(false); + }); + A.CallTo(() => PersistenceAsync.CommitAsync(A._, CancellationToken.None)) + .ReturnsLazily((CommitAttempt attempt, CancellationToken _) => new Commit( + attempt.BucketId, + attempt.StreamId, + attempt.StreamRevision, + attempt.CommitId, + attempt.CommitSequence, + attempt.CommitStamp, + 1, + attempt.Headers, + attempt.Events)); + + Stream = new OptimisticEventStream(BucketId, StreamId, Persistence, PersistenceAsync); + return Stream.InitializeAsync(MinRevision, MaxRevision, CancellationToken.None); + } + + protected override async Task BecauseAsync() + { + Stream.Add(new EventMessage() { Body = "Test" }); + // this should fail and cause the stream to be reloaded + _thrown1 = await Catch.ExceptionAsync(() => Stream.CommitChangesAsync(Guid.NewGuid(), CancellationToken.None)).ConfigureAwait(false); + // this should succeed, events will be appended at the end + _thrown2 = await Catch.ExceptionAsync(() => Stream.CommitChangesAsync(Guid.NewGuid(), CancellationToken.None)).ConfigureAwait(false); + } + + [Fact] + public void should_throw_ConcurrencyException() + { + _thrown1.Should().BeOfType(); + } + + public void second_attempt_should_succeed_stream_was_refreshed() + { + _thrown2.Should().BeNull(); + } + + [Fact] + public void events_will_be_appended_to_the_stream_when_committed_on_second_attempt() + { + Stream.CommittedEvents.Count.Should().Be(7); + Stream.CommittedEvents.Last().Body.Should().Be("Test"); + } + } + + /// + /// All other cases should be handled by persistence providers that have unique indexes on + /// the CommitSequence (the providers should be able to detect that the last commit loaded, after + /// which we are appending new events, is not the last one of the stream) + /// https://github.com/NEventStore/NEventStore/issues/420 + /// +#if MSTEST + [TestClass] +#endif + public class issue_420_when_adding_events_in_the_middle_a_commit_if_persistence_returns_too_many_commits : on_the_event_stream + { + private const int MinRevision = 2; + private const int MaxRevision = 6; + private readonly int _eventsPerCommit = 2; + private ICommit[]? _committed; + private Exception? _thrown1; + private Exception? _thrown2; + + protected override Task ContextAsync() + { + _committed = + [ + BuildCommitStub(1, 2, 1, _eventsPerCommit), // 1-2 + BuildCommitStub(2, 4, 2, _eventsPerCommit), // 3-4 + BuildCommitStub(3, 6, 3, _eventsPerCommit), // 5-6 <-- asked up to this one + BuildCommitStub(4, 8, 4, _eventsPerCommit) // 7-8 + ]; + + // the persistence returns all the data in the stream + A.CallTo(() => PersistenceAsync.GetFromAsync(BucketId, StreamId, MinRevision, MaxRevision, A>.Ignored, CancellationToken.None)) + .ReturnsLazily(async (string bucketId, string streamId, int minRevision, int maxRevision, IAsyncObserver asyncObserver, CancellationToken cancellation) => + { + foreach (var _commit in _committed) + { + await asyncObserver.OnNextAsync(_commit, cancellation).ConfigureAwait(false); + } + await asyncObserver.OnCompletedAsync(cancellation).ConfigureAwait(false); + }); + A.CallTo(() => PersistenceAsync.CommitAsync(A._, CancellationToken.None)) + .ReturnsLazily((CommitAttempt attempt, CancellationToken _) => new Commit( + attempt.BucketId, + attempt.StreamId, + attempt.StreamRevision, + attempt.CommitId, + attempt.CommitSequence, + attempt.CommitStamp, + 1, + attempt.Headers, + attempt.Events)); + + Stream = new OptimisticEventStream(BucketId, StreamId, Persistence, PersistenceAsync); + return Stream.InitializeAsync(MinRevision, MaxRevision, CancellationToken.None); + } + + protected override async Task BecauseAsync() + { + Stream.Add(new EventMessage() { Body = "Test" }); + // this should fail and cause the stream to be reloaded + _thrown1 = await Catch.ExceptionAsync(() => Stream.CommitChangesAsync(Guid.NewGuid(), CancellationToken.None)).ConfigureAwait(false); + // this should succeed, events will be appended at the end + _thrown2 = await Catch.ExceptionAsync(() => Stream.CommitChangesAsync(Guid.NewGuid(), CancellationToken.None)).ConfigureAwait(false); + } + + [Fact] + public void first_attempt_should_throw_ConcurrencyException() + { + _thrown1.Should().BeOfType(); + } + + [Fact] + public void second_attempt_should_succeed_stream_was_refreshed() + { + _thrown2.Should().BeNull(); + } + + [Fact] + public void events_will_be_appended_to_the_stream_when_committed_on_second_attempt() + { + Stream.CommittedEvents.Count.Should().Be(6); + Stream.CommittedEvents.Last().Body.Should().Be("Test"); + } + } + + public abstract class on_the_event_stream : SpecificationBase +#if XUNIT + , IUseFixture +#endif + { + protected const int DefaultStreamRevision = 1; + protected const int DefaultCommitSequence = 1; + private ICommitEvents? _persistence; + private ICommitEventsAsync? _persistenceAsync; + private OptimisticEventStream? _stream; + protected const string BucketId = "bucket"; + protected readonly string StreamId = Guid.NewGuid().ToString(); + +#if MSTEST + // todo: we have a problem with ClassInitialize and inheritance, they are not called + // a possible workaround is to use the appdomain unload: https://vijayvepa.wordpress.com/2011/06/16/test-classinitialize-and-classcleanup-inheritance/ + // but each test is run in isolation in MSTest + [ClassInitialize] + public static void ClassInitialize(TestContext context) + { + SystemTime.Resolver = () => new DateTime(2012, 1, 1, 13, 0, 0); + } + + [ClassCleanup] + public static void ClassCleanup() + { + SystemTime.Resolver = null; + } + + public on_the_event_stream() + { + SystemTime.Resolver = () => new DateTime(2012, 1, 1, 13, 0, 0); + } + + protected override void Cleanup() + { + SystemTime.Resolver = null; + } +#endif + +#if NUNIT + // can also consider using the NUnit test attributes instead of the constructor + + protected on_the_event_stream() + { + SystemTime.Resolver = () => new DateTime(2012, 1, 1, 13, 0, 0); + } + + protected override void Cleanup() + { + SystemTime.Resolver = null; + } + +#endif + + protected ICommitEvents Persistence + { + get { return _persistence ??= A.Fake(); } + } + protected ICommitEventsAsync PersistenceAsync + { + get { return _persistenceAsync ??= A.Fake(); } + } + + protected OptimisticEventStream Stream + { + get { return _stream ??= new OptimisticEventStream(BucketId, StreamId, Persistence, PersistenceAsync); } + set { _stream = value; } + } + + protected ICommit BuildCommitStub(long checkpointToken, int revision, int sequence, int eventCount) + { + var events = new List(eventCount); + for (int i = 0; i < eventCount; i++) + { + events.Add(new EventMessage() { Body = "Body " + (revision - eventCount + i + 1) }); + } + + return new Commit(Bucket.Default, StreamId, revision, Guid.NewGuid(), sequence, SystemTime.UtcNow, checkpointToken, null, events); + } + } + + public class FakeTimeFixture : IDisposable + { + public FakeTimeFixture() + { + SystemTime.Resolver = () => new DateTime(2012, 1, 1, 13, 0, 0); + } + + public void Dispose() + { + SystemTime.Resolver = null; + GC.SuppressFinalize(this); + } + } +} + +#pragma warning restore IDE1006 // Naming Styles +#pragma warning restore 169 // ReSharper enable InconsistentNaming \ No newline at end of file diff --git a/src/NEventStore.Tests/OptimisticEventStreamTests.cs b/src/NEventStore.Tests/OptimisticEventStreamTests.cs new file mode 100644 index 000000000..ca3faa403 --- /dev/null +++ b/src/NEventStore.Tests/OptimisticEventStreamTests.cs @@ -0,0 +1,966 @@ +using FakeItEasy; +using FluentAssertions; +using NEventStore.Persistence; +using NEventStore.Persistence.AcceptanceTests; +using NEventStore.Persistence.AcceptanceTests.BDD; +#if MSTEST +using Microsoft.VisualStudio.TestTools.UnitTesting; +#endif +#if XUNIT +using Xunit; +using Xunit.Should; +#endif + +#pragma warning disable 169 // ReSharper enable InconsistentNaming +#pragma warning disable IDE1006 // Naming Styles + +namespace NEventStore +{ +#if MSTEST + [TestClass] +#endif + public class when_building_a_stream : on_the_event_stream + { + private const int MinRevision = 2; + private const int MaxRevision = 7; + private readonly int _eachCommitHas = 2; // events + private ICommit[]? _committed; + + protected override void Context() + { + _committed = + [ + BuildCommitStub(1, 2, 1, _eachCommitHas), // 1-2 + BuildCommitStub(2, 4, 2, _eachCommitHas), // 3-4 + BuildCommitStub(3, 6, 3, _eachCommitHas), // 5-6 + BuildCommitStub(4, 8, 4, _eachCommitHas) // 7-8 + ]; + + _committed[0].Headers["Common"] = string.Empty; + _committed[1].Headers["Common"] = string.Empty; + _committed[2].Headers["Common"] = string.Empty; + _committed[3].Headers["Common"] = string.Empty; + _committed[0].Headers["Unique"] = string.Empty; + + A.CallTo(() => Persistence.GetFrom(BucketId, StreamId, MinRevision, MaxRevision)).Returns(_committed); + } + + protected override void Because() + { + Stream = new OptimisticEventStream(BucketId, StreamId, Persistence, PersistenceAsync); + Stream.Initialize(MinRevision, MaxRevision); + } + + [Fact] + public void should_have_the_correct_stream_identifier() + { + Stream.StreamId.Should().Be(StreamId); + } + + [Fact] + public void should_have_the_correct_head_stream_revision() + { + Stream.StreamRevision.Should().Be(MaxRevision); + } + + [Fact] + public void should_have_the_correct_head_commit_sequence() + { + Stream.CommitSequence.Should().Be(_committed!.Last().CommitSequence); + } + + [Fact] + public void should_not_include_events_below_the_minimum_revision_indicated() + { + Stream.CommittedEvents.First().Should().Be(_committed![0].Events.Last()); + } + + [Fact] + public void should_not_include_events_above_the_maximum_revision_indicated() + { + Stream.CommittedEvents.Last().Should().Be(_committed!.Last().Events.First()); + } + + [Fact] + public void should_have_all_of_the_committed_events_up_to_the_stream_revision_specified() + { + Stream.CommittedEvents.Count.Should().Be(MaxRevision - MinRevision + 1); + } + + [Fact] + public void should_contain_the_headers_from_the_underlying_commits() + { + Stream.CommittedHeaders.Count.Should().Be(2); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_initializing_an_already_initialized_stream: on_the_event_stream + { + private const int MinRevision = 2; + private const int MaxRevision = 7; + private readonly int _eachCommitHas = 2; // events + private ICommit[]? _committed; + private Exception? _thrownInitializeRevisionRange; + private Exception? _thrownInitializeSnapshot; + + protected override void Context() + { + _committed = + [ + BuildCommitStub(1, 2, 1, _eachCommitHas), // 1-2 + BuildCommitStub(2, 4, 2, _eachCommitHas), // 3-4 + BuildCommitStub(3, 6, 3, _eachCommitHas), // 5-6 + BuildCommitStub(4, 8, 4, _eachCommitHas) // 7-8 + ]; + + _committed[0].Headers["Common"] = string.Empty; + _committed[1].Headers["Common"] = string.Empty; + _committed[2].Headers["Common"] = string.Empty; + _committed[3].Headers["Common"] = string.Empty; + _committed[0].Headers["Unique"] = string.Empty; + + A.CallTo(() => Persistence.GetFrom(BucketId, StreamId, MinRevision, MaxRevision)).Returns(_committed); + + Stream = new OptimisticEventStream(BucketId, StreamId, Persistence, PersistenceAsync); + Stream.Initialize(MinRevision, MaxRevision); + } + + protected override void Because() + { + _thrownInitializeRevisionRange = Catch.Exception(() => Stream.Initialize(MinRevision, MaxRevision)); + _thrownInitializeSnapshot = Catch.Exception(() => Stream.Initialize(new Snapshot(BucketId, StreamId, MinRevision, new object()), MaxRevision)); + } + + [Fact] + public void cannot_initialize_a_stream_using_revision_range() + { + _thrownInitializeRevisionRange.Should().BeOfType(); + } + + [Fact] + public void cannot_initialize_a_stream_using_snapshot() + { + _thrownInitializeSnapshot.Should().BeOfType(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_initializing_an_already_used_stream_with_revision_range : on_the_event_stream + { + private const int MinRevision = 2; + private const int MaxRevision = 7; + private Exception? _thrownInitializeRevisionRange; + private Exception? _thrownInitializeSnapshot; + + protected override void Context() + { + Stream = new OptimisticEventStream(BucketId, StreamId, Persistence, PersistenceAsync); + Stream.Add(new EventMessage { Body = "Test" }); + } + + protected override void Because() + { + _thrownInitializeRevisionRange = Catch.Exception(() => Stream.Initialize(MinRevision, MaxRevision)); + _thrownInitializeSnapshot = Catch.Exception(() => Stream.Initialize(new Snapshot(BucketId, StreamId, MinRevision, new object()), MaxRevision)); + } + + [Fact] + public void cannot_initialize_a_stream_using_revision_range() + { + _thrownInitializeRevisionRange.Should().BeOfType(); + } + + [Fact] + public void cannot_initialize_a_stream_using_snapshot() + { + _thrownInitializeSnapshot.Should().BeOfType(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_the_head_event_revision_is_less_than_the_max_desired_revision : on_the_event_stream + { + private readonly int _eventsPerCommit = 2; + private ICommit[]? _committed; + + protected override void Context() + { + _committed = + [ + BuildCommitStub(1, 2, 1, _eventsPerCommit), // 1-2 + BuildCommitStub(2, 4, 2, _eventsPerCommit), // 3-4 + BuildCommitStub(3, 6, 3, _eventsPerCommit), // 5-6 + BuildCommitStub(4, 8, 4, _eventsPerCommit) // 7-8 + ]; + + A.CallTo(() => Persistence.GetFrom(BucketId, StreamId, 0, int.MaxValue)).Returns(_committed); + } + + protected override void Because() + { + Stream = new OptimisticEventStream(BucketId, StreamId, Persistence, PersistenceAsync); + Stream.Initialize(0, int.MaxValue); + } + + [Fact] + public void should_set_the_stream_revision_to_the_revision_of_the_most_recent_event() + { + Stream.StreamRevision.Should().Be(_committed!.Last().StreamRevision); + } + + [Fact] + public void should_set_the_commit_sequence_the_most_recent_commit_sequence() + { + Stream.CommitSequence.Should().Be(_committed!.Last().CommitSequence); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_reading_up_to_revision : on_the_event_stream + { + private readonly int _eventsPerCommit = 2; + private ICommit[]? _committed; + + protected override void Context() + { + _committed = + [ + BuildCommitStub(1, 2, 1, _eventsPerCommit), // 1-2 + BuildCommitStub(2, 4, 2, _eventsPerCommit), // 3-4 + BuildCommitStub(3, 6, 3, _eventsPerCommit), // 5-6 + BuildCommitStub(4, 8, 4, _eventsPerCommit) // 7-8 + ]; + + A.CallTo(() => Persistence.GetFrom(BucketId, StreamId, 0, 6)).Returns(_committed); + } + + protected override void Because() + { + Stream = new OptimisticEventStream(BucketId, StreamId, Persistence, PersistenceAsync); + Stream.Initialize(0, 6); + } + + [Fact] + public void should_set_the_stream_revision_to_the_revision_of_the_correct_commit() + { + Stream.StreamRevision.Should().Be(_committed![2].StreamRevision); + } + + [Fact] + public void should_set_the_commit_sequence_to_the_sequence_of_the_correct_commit() + { + Stream.CommitSequence.Should().Be(_committed![2].CommitSequence); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_adding_a_null_event_message : on_the_event_stream + { + private Exception? _thrown; + + protected override void Because() + { +#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. + _thrown = Catch.Exception(() => Stream.Add(null)); +#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. + } + + [Fact] + public void should_throw() + { + _thrown.Should().BeOfType(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_adding_an_unpopulated_event_message : on_the_event_stream + { + private Exception? _thrown; + + protected override void Because() + { +#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. + _thrown = Catch.Exception(() => Stream.Add(new EventMessage { Body = null })); +#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. + } + + [Fact] + public void should_throw() + { + _thrown.Should().BeOfType(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_adding_a_fully_populated_event_message : on_the_event_stream + { + protected override void Because() + { + Stream.Add(new EventMessage { Body = "populated" }); + } + + [Fact] + public void should_add_the_event_to_the_set_of_uncommitted_events() + { + Stream.UncommittedEvents.Count.Should().Be(1); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_adding_multiple_populated_event_messages : on_the_event_stream + { + protected override void Because() + { + Stream.Add(new EventMessage { Body = "populated" }); + Stream.Add(new EventMessage { Body = "also populated" }); + } + + [Fact] + public void should_add_all_of_the_events_provided_to_the_set_of_uncommitted_events() + { + Stream.UncommittedEvents.Count.Should().Be(2); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_adding_a_simple_object_as_an_event_message : on_the_event_stream + { + private const string MyEvent = "some event data"; + + protected override void Because() + { + Stream.Add(new EventMessage { Body = MyEvent }); + } + + [Fact] + public void should_add_the_uncommitted_event_to_the_set_of_uncommitted_events() + { + Stream.UncommittedEvents.Count.Should().Be(1); + } + + [Fact] + public void should_wrap_the_uncommitted_event_in_an_EventMessage_object() + { + Stream.UncommittedEvents.First().Body.Should().Be(MyEvent); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_clearing_any_uncommitted_changes : on_the_event_stream + { + protected override void Context() + { + Stream.Add(new EventMessage { Body = string.Empty }); + } + + protected override void Because() + { + Stream.ClearChanges(); + } + + [Fact] + public void should_clear_all_uncommitted_events() + { + Stream.UncommittedEvents.Count.Should().Be(0); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_committing_an_empty_changeset : on_the_event_stream + { + protected override void Because() + { + Stream.CommitChanges(Guid.NewGuid()); + } + + [Fact] + public void should_not_call_the_underlying_infrastructure() + { + A.CallTo(() => Persistence.Commit(A._)).MustNotHaveHappened(); + } + + [Fact] + public void should_not_increment_the_current_stream_revision() + { + Stream.StreamRevision.Should().Be(0); + } + + [Fact] + public void should_not_increment_the_current_commit_sequence() + { + Stream.CommitSequence.Should().Be(0); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_committing_any_uncommitted_changes : on_the_event_stream + { + private readonly Guid _commitId = Guid.NewGuid(); + private readonly Dictionary _headers = new() { { "key", "value" } }; + private readonly EventMessage _uncommitted = new() { Body = string.Empty }; + private CommitAttempt? _constructed; + + protected override void Context() + { + A.CallTo(() => Persistence.Commit(A._)) + .Invokes((CommitAttempt _) => _constructed = _) + .ReturnsLazily((CommitAttempt attempt) => new Commit( + attempt.BucketId, + attempt.StreamId, + attempt.StreamRevision, + attempt.CommitId, + attempt.CommitSequence, + attempt.CommitStamp, + 1, + attempt.Headers, + attempt.Events)); + Stream.Add(_uncommitted); + foreach (var item in _headers) + { + Stream.UncommittedHeaders[item.Key] = item.Value; + } + } + + protected override void Because() + { + Stream.CommitChanges(_commitId); + } + + [Fact] + public void should_provide_a_commit_to_the_underlying_infrastructure() + { + A.CallTo(() => Persistence.Commit(A._)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public void should_build_the_commit_with_the_correct_bucket_identifier() + { + _constructed!.BucketId.Should().Be(BucketId); + } + + [Fact] + public void should_build_the_commit_with_the_correct_stream_identifier() + { + _constructed!.StreamId.Should().Be(StreamId); + } + + [Fact] + public void should_build_the_commit_with_the_correct_stream_revision() + { + _constructed!.StreamRevision.Should().Be(DefaultStreamRevision); + } + + [Fact] + public void should_build_the_commit_with_the_correct_commit_identifier() + { + _constructed!.CommitId.Should().Be(_commitId); + } + + [Fact] + public void should_build_the_commit_with_an_incremented_commit_sequence() + { + _constructed!.CommitSequence.Should().Be(DefaultCommitSequence); + } + + [Fact] + public void should_build_the_commit_with_the_correct_commit_stamp() + { + SystemTime.UtcNow.Should().Be(_constructed!.CommitStamp); + } + + [Fact] + public void should_build_the_commit_with_the_headers_provided() + { + _constructed!.Headers[_headers.First().Key].Should().Be(_headers.First().Value); + } + + [Fact] + public void should_build_the_commit_containing_all_uncommitted_events() + { + _constructed!.Events.Count.Should().Be(_headers.Count); + } + + [Fact] + public void should_build_the_commit_using_the_event_messages_provided() + { + _constructed!.Events.First().Should().Be(_uncommitted); + } + + [Fact] + public void should_contain_a_copy_of_the_headers_provided() + { + _constructed!.Headers.Should().NotBeEmpty(); + } + + [Fact] + public void should_update_the_stream_revision() + { + Stream.StreamRevision.Should().Be(_constructed!.StreamRevision); + } + + [Fact] + public void should_update_the_commit_sequence() + { + Stream.CommitSequence.Should().Be(_constructed!.CommitSequence); + } + + [Fact] + public void should_add_the_uncommitted_events_the_committed_events() + { + Stream.CommittedEvents.Last().Should().Be(_uncommitted); + } + + [Fact] + public void should_clear_the_uncommitted_events_on_the_stream() + { + Stream.UncommittedEvents.Should().BeEmpty(); + } + + [Fact] + public void should_clear_the_uncommitted_headers_on_the_stream() + { + Stream.UncommittedHeaders.Should().BeEmpty(); + } + + [Fact] + public void should_copy_the_uncommitted_headers_to_the_committed_stream_headers() + { + Stream.CommittedHeaders.Count.Should().Be(_headers.Count); + } + } + + /// + /// This behavior is primarily to support a NoSQL storage solution where CommitId is not being used as the "primary key" + /// in a NoSQL environment, we'll most likely use StreamId + CommitSequence, which also enables optimistic concurrency. + /// +#if MSTEST + [TestClass] +#endif + public class when_committing_with_an_identifier_that_was_previously_read : on_the_event_stream + { + private ICommit[]? _committed; + private Guid _duplicateCommitId; + private Exception? _thrown; + + protected override void Context() + { + _committed = [BuildCommitStub(1, 1, 1, 1)]; + _duplicateCommitId = _committed[0].CommitId; + + A.CallTo(() => Persistence.GetFrom(BucketId, StreamId, 0, int.MaxValue)).Returns(_committed); + + Stream = new OptimisticEventStream(BucketId, StreamId, Persistence, PersistenceAsync); + Stream.Initialize(0, int.MaxValue); + } + + protected override void Because() + { + _thrown = Catch.Exception(() => Stream.CommitChanges(_duplicateCommitId)); + } + + [Fact] + public void should_throw_a_DuplicateCommitException() + { + _thrown.Should().BeOfType(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_committing_after_another_thread_or_process_has_moved_the_stream_head : on_the_event_stream + { + private const int StreamRevision = 1; + private readonly EventMessage _uncommitted = new() { Body = string.Empty }; + private ICommit[]? _committed; + private ICommit[]? _discoveredOnCommit; + private Exception? _thrown; + + protected override void Context() + { + _committed = [BuildCommitStub(1, 1, 1, 1)]; + _discoveredOnCommit = [BuildCommitStub(2, 3, 2, 2)]; + + A.CallTo(() => Persistence.Commit(A._)).Throws(new ConcurrencyException()); + A.CallTo(() => Persistence.GetFrom(BucketId, StreamId, StreamRevision, int.MaxValue)).Returns(_committed); + A.CallTo(() => Persistence.GetFrom(BucketId, StreamId, StreamRevision + 1, int.MaxValue)).Returns(_discoveredOnCommit); + + Stream = new OptimisticEventStream(BucketId, StreamId, Persistence, PersistenceAsync); + Stream.Initialize(StreamRevision, int.MaxValue); + Stream.Add(_uncommitted); + } + + protected override void Because() + { + _thrown = Catch.Exception(() => Stream.CommitChanges(Guid.NewGuid())); + } + + [Fact] + public void should_throw_a_ConcurrencyException() + { + _thrown.Should().BeOfType(); + } + + [Fact] + public void should_query_the_underlying_storage_to_discover_the_new_commits() + { + A.CallTo(() => Persistence.GetFrom(BucketId, StreamId, StreamRevision + 1, int.MaxValue)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public void should_update_the_stream_revision_accordingly() + { + Stream.StreamRevision.Should().Be(_discoveredOnCommit![0].StreamRevision); + } + + [Fact] + public void should_update_the_commit_sequence_accordingly() + { + Stream.CommitSequence.Should().Be(_discoveredOnCommit![0].CommitSequence); + } + + [Fact] + public void should_add_the_newly_discovered_committed_events_to_the_set_of_committed_events_accordingly() + { + Stream.CommittedEvents.Count.Should().Be(_discoveredOnCommit![0].Events.Count + 1); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_attempting_to_invoke_behavior_on_a_disposed_stream : on_the_event_stream + { + private Exception? _thrown; + + protected override void Context() + { + Stream.Dispose(); + } + + protected override void Because() + { + _thrown = Catch.Exception(() => Stream.CommitChanges(Guid.NewGuid())); + } + + [Fact] + public void should_throw_a_ObjectDisposedException() + { + _thrown.Should().BeOfType(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_attempting_to_modify_the_event_collections : on_the_event_stream + { + [Fact] + public void should_throw_an_exception_when_adding_to_the_committed_collection() + { + Catch.Exception(() => Stream.CommittedEvents.Add(new EventMessage())).Should().BeOfType(); + } + + [Fact] + public void should_throw_an_exception_when_adding_to_the_uncommitted_collection() + { + Catch.Exception(() => Stream.UncommittedEvents.Add(new EventMessage())).Should().BeOfType(); + } + + [Fact] + public void should_throw_an_exception_when_clearing_the_committed_collection() + { + Catch.Exception(() => Stream.CommittedEvents.Clear()).Should().BeOfType(); + } + + [Fact] + public void should_throw_an_exception_when_clearing_the_uncommitted_collection() + { + Catch.Exception(() => Stream.UncommittedEvents.Clear()).Should().BeOfType(); + } + + [Fact] + public void should_throw_an_exception_when_removing_from_the_committed_collection() + { + Catch.Exception(() => Stream.CommittedEvents.Remove(new EventMessage())).Should().BeOfType(); + } + + [Fact] + public void should_throw_an_exception_when_removing_from_the_uncommitted_collection() + { + Catch.Exception(() => Stream.UncommittedEvents.Remove(new EventMessage())).Should().BeOfType(); + } + } + + /// + /// All other cases should be handled by persistence providers that have unique indexes on + /// the CommitSequence (the providers should be able to detect that the last commit loaded, after + /// which we are appending new events, is not the last one of the stream) + /// https://github.com/NEventStore/NEventStore/issues/420 + /// +#if MSTEST + [TestClass] +#endif + public class issue_420_when_adding_events_in_the_middle_of_the_last_commit : on_the_event_stream + { + private const int MinRevision = 2; + private const int MaxRevision = 7; + private readonly int _eventsPerCommit = 2; + private ICommit[]? _committed; + private Exception? _thrown1; + private Exception? _thrown2; + + protected override void Context() + { + _committed = + [ + BuildCommitStub(1, 2, 1, _eventsPerCommit), // 1-2 + BuildCommitStub(2, 4, 2, _eventsPerCommit), // 3-4 + BuildCommitStub(3, 6, 3, _eventsPerCommit), // 5-6 + BuildCommitStub(4, 8, 4, _eventsPerCommit) // 7-8 + ]; + + A.CallTo(() => Persistence.GetFrom(BucketId, StreamId, MinRevision, MaxRevision)).Returns(_committed); + A.CallTo(() => Persistence.Commit(A._)) + .ReturnsLazily((CommitAttempt attempt) => new Commit( + attempt.BucketId, + attempt.StreamId, + attempt.StreamRevision, + attempt.CommitId, + attempt.CommitSequence, + attempt.CommitStamp, + 1, + attempt.Headers, + attempt.Events)); + + Stream = new OptimisticEventStream(BucketId, StreamId, Persistence, PersistenceAsync); + Stream.Initialize(MinRevision, MaxRevision); + } + + protected override void Because() + { + Stream.Add(new EventMessage() { Body = "Test" }); + // this should fail and cause the stream to be reloaded + _thrown1 = Catch.Exception(() => Stream.CommitChanges(Guid.NewGuid())); + // this should succeed, events will be appended at the end + _thrown2 = Catch.Exception(() => Stream.CommitChanges(Guid.NewGuid())); + } + + [Fact] + public void should_throw_ConcurrencyException() + { + _thrown1.Should().BeOfType(); + } + + public void second_attempt_should_succeed_stream_was_refreshed() + { + _thrown2.Should().BeNull(); + } + + [Fact] + public void events_will_be_appended_to_the_stream_when_committed_on_second_attempt() + { + Stream.CommittedEvents.Count.Should().Be(7); + Stream.CommittedEvents.Last().Body.Should().Be("Test"); + } + } + + /// + /// All other cases should be handled by persistence providers that have unique indexes on + /// the CommitSequence (the providers should be able to detect that the last commit loaded, after + /// which we are appending new events, is not the last one of the stream) + /// https://github.com/NEventStore/NEventStore/issues/420 + /// +#if MSTEST + [TestClass] +#endif + public class issue_420_when_adding_events_in_the_middle_a_commit_if_persistence_returns_too_many_commits : on_the_event_stream + { + private const int MinRevision = 2; + private const int MaxRevision = 6; + private readonly int _eventsPerCommit = 2; + private ICommit[]? _committed; + private Exception? _thrown1; + private Exception? _thrown2; + + protected override void Context() + { + _committed = + [ + BuildCommitStub(1, 2, 1, _eventsPerCommit), // 1-2 + BuildCommitStub(2, 4, 2, _eventsPerCommit), // 3-4 + BuildCommitStub(3, 6, 3, _eventsPerCommit), // 5-6 <-- asked up to this one + BuildCommitStub(4, 8, 4, _eventsPerCommit) // 7-8 + ]; + + // the persistence returns all the data in the stream + A.CallTo(() => Persistence.GetFrom(BucketId, StreamId, MinRevision, MaxRevision)).Returns(_committed); + A.CallTo(() => Persistence.Commit(A._)) + .ReturnsLazily((CommitAttempt attempt) => new Commit( + attempt.BucketId, + attempt.StreamId, + attempt.StreamRevision, + attempt.CommitId, + attempt.CommitSequence, + attempt.CommitStamp, + 1, + attempt.Headers, + attempt.Events)); + + Stream = new OptimisticEventStream(BucketId, StreamId, Persistence, PersistenceAsync); + Stream.Initialize(MinRevision, MaxRevision); + } + + protected override void Because() + { + Stream.Add(new EventMessage() { Body = "Test" }); + // this should fail and cause the stream to be reloaded + _thrown1 = Catch.Exception(() => Stream.CommitChanges(Guid.NewGuid())); + // this should succeed, events will be appended at the end + _thrown2 = Catch.Exception(() => Stream.CommitChanges(Guid.NewGuid())); + } + + [Fact] + public void first_attempt_should_throw_ConcurrencyException() + { + _thrown1.Should().BeOfType(); + } + + [Fact] + public void second_attempt_should_succeed_stream_was_refreshed() + { + _thrown2.Should().BeNull(); + } + + [Fact] + public void events_will_be_appended_to_the_stream_when_committed_on_second_attempt() + { + Stream.CommittedEvents.Count.Should().Be(6); + Stream.CommittedEvents.Last().Body.Should().Be("Test"); + } + } + + public abstract class on_the_event_stream : SpecificationBase +#if XUNIT + , IUseFixture +#endif + { + protected const int DefaultStreamRevision = 1; + protected const int DefaultCommitSequence = 1; + private ICommitEvents? _persistence; + private ICommitEventsAsync? _persistenceAsync; + private OptimisticEventStream? _stream; + protected const string BucketId = "bucket"; + protected readonly string StreamId = Guid.NewGuid().ToString(); + +#if MSTEST + // todo: we have a problem with ClassInitialize and inheritance, they are not called + // a possible workaround is to use the appdomain unload: https://vijayvepa.wordpress.com/2011/06/16/test-classinitialize-and-classcleanup-inheritance/ + // but each test is run in isolation in MSTest + [ClassInitialize] + public static void ClassInitialize(TestContext context) + { + SystemTime.Resolver = () => new DateTime(2012, 1, 1, 13, 0, 0); + } + + [ClassCleanup] + public static void ClassCleanup() + { + SystemTime.Resolver = null; + } + + public on_the_event_stream() + { + SystemTime.Resolver = () => new DateTime(2012, 1, 1, 13, 0, 0); + } + + protected override void Cleanup() + { + SystemTime.Resolver = null; + } +#endif + +#if NUNIT + // can also consider using the NUnit test attributes instead of the constructor + + protected on_the_event_stream() + { + SystemTime.Resolver = () => new DateTime(2012, 1, 1, 13, 0, 0); + } + + protected override void Cleanup() + { + SystemTime.Resolver = null; + } + +#endif + + protected ICommitEvents Persistence + { + get { return _persistence ??= A.Fake(); } + } + protected ICommitEventsAsync PersistenceAsync + { + get { return _persistenceAsync ??= A.Fake(); } + } + + protected OptimisticEventStream Stream + { + get { return _stream ??= new OptimisticEventStream(BucketId, StreamId, Persistence, PersistenceAsync); } + set { _stream = value; } + } + + protected ICommit BuildCommitStub(long checkpointToken, int revision, int sequence, int eventCount) + { + var events = new List(eventCount); + for (int i = 0; i < eventCount; i++) + { + events.Add(new EventMessage() { Body = "Body " + (revision - eventCount + i + 1) }); + } + + return new Commit(Bucket.Default, StreamId, revision, Guid.NewGuid(), sequence, SystemTime.UtcNow, checkpointToken, null, events); + } + } + + public class FakeTimeFixture : IDisposable + { + public FakeTimeFixture() + { + SystemTime.Resolver = () => new DateTime(2012, 1, 1, 13, 0, 0); + } + + public void Dispose() + { + SystemTime.Resolver = null; + GC.SuppressFinalize(this); + } + } +} + +#pragma warning restore IDE1006 // Naming Styles +#pragma warning restore 169 // ReSharper enable InconsistentNaming \ No newline at end of file diff --git a/src/NEventStore.Tests/OptimisticPipelineHookTests.cs b/src/NEventStore.Tests/OptimisticPipelineHookTests.cs new file mode 100644 index 000000000..383861bab --- /dev/null +++ b/src/NEventStore.Tests/OptimisticPipelineHookTests.cs @@ -0,0 +1,458 @@ + +#pragma warning disable 169 // ReSharper disable InconsistentNaming +#pragma warning disable IDE1006 // Naming Styles + +using NEventStore.Persistence; +using NEventStore.Persistence.AcceptanceTests; +using NEventStore.Persistence.AcceptanceTests.BDD; +using FluentAssertions; +#if MSTEST +using Microsoft.VisualStudio.TestTools.UnitTesting; +#endif +#if NUNIT +#endif +#if XUNIT +using Xunit; +using Xunit.Should; +#endif + +namespace NEventStore +{ + public static class OptimisticPipelineHookTests + { +#if MSTEST + [TestClass] +#endif + public class when_committing_with_a_sequence_beyond_the_known_end_of_a_stream : using_commit_hooks + { + private const int HeadStreamRevision = 5; + private const int HeadCommitSequence = 1; + private const int ExpectedNextCommitSequence = HeadCommitSequence + 1; + private const int BeyondEndOfStreamCommitSequence = ExpectedNextCommitSequence + 1; + private ICommit? _alreadyCommitted; + private CommitAttempt? _beyondEndOfStream; + private Exception? _thrown; + + protected override void Context() + { + _alreadyCommitted = BuildCommitStub(1, HeadStreamRevision, HeadCommitSequence); + _beyondEndOfStream = BuildCommitAttemptStub(HeadStreamRevision + 1, BeyondEndOfStreamCommitSequence); + + Hook.PostCommit(_alreadyCommitted); + } + + protected override void Because() + { + _thrown = Catch.Exception(() => Hook.PreCommit(_beyondEndOfStream!)); + } + + [Fact] + public void should_throw_a_PersistenceException() + { + _thrown.Should().BeOfType(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_committing_with_a_revision_beyond_the_known_end_of_a_stream : using_commit_hooks + { + private const int HeadCommitSequence = 1; + private const int HeadStreamRevision = 1; + private const int NumberOfEventsBeingCommitted = 1; + private const int ExpectedNextStreamRevision = HeadStreamRevision + 1 + NumberOfEventsBeingCommitted; + private const int BeyondEndOfStreamRevision = ExpectedNextStreamRevision + 1; + private ICommit? _alreadyCommitted; + private CommitAttempt? _beyondEndOfStream; + private Exception? _thrown; + + protected override void Context() + { + _alreadyCommitted = BuildCommitStub(1, HeadStreamRevision, HeadCommitSequence); + _beyondEndOfStream = BuildCommitAttemptStub(BeyondEndOfStreamRevision, HeadCommitSequence + 1); + + Hook.PostCommit(_alreadyCommitted); + } + + protected override void Because() + { + _thrown = Catch.Exception(() => Hook.PreCommit(_beyondEndOfStream!)); + } + + [Fact] + public void should_throw_a_PersistenceException() + { + _thrown.Should().BeOfType(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_committing_with_a_sequence_less_or_equal_to_the_most_recent_sequence_for_the_stream : using_commit_hooks + { + private const int HeadStreamRevision = 42; + private const int HeadCommitSequence = 42; + private const int DuplicateCommitSequence = HeadCommitSequence; + private CommitAttempt? Attempt; + private ICommit? Committed; + + private Exception? thrown; + + protected override void Context() + { + Committed = BuildCommitStub(1, HeadStreamRevision, HeadCommitSequence); + Attempt = BuildCommitAttemptStub(HeadStreamRevision + 1, DuplicateCommitSequence); + + Hook.PostCommit(Committed); + } + + protected override void Because() + { + thrown = Catch.Exception(() => Hook.PreCommit(Attempt!)); + } + + [Fact] + public void should_throw_a_ConcurrencyException() + { + thrown.Should().BeOfType(); + } + + [Fact] + public void ConcurrencyException_should_have_good_message() + { + thrown!.Message.Should().Contain(Attempt!.StreamId); + thrown.Message.Should().Contain("CommitSequence [" + Attempt.CommitSequence); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_committing_with_a_revision_less_or_equal_to_than_the_most_recent_revision_read_for_the_stream : using_commit_hooks + { + private const int HeadStreamRevision = 3; + private const int HeadCommitSequence = 2; + private const int DuplicateStreamRevision = HeadStreamRevision; + private ICommit? _committed; + private CommitAttempt? _failedAttempt; + private Exception? _thrown; + + protected override void Context() + { + _committed = BuildCommitStub(1, HeadStreamRevision, HeadCommitSequence); + _failedAttempt = BuildCommitAttemptStub(DuplicateStreamRevision, HeadCommitSequence + 1); + + Hook.PostCommit(_committed); + } + + protected override void Because() + { + _thrown = Catch.Exception(() => Hook.PreCommit(_failedAttempt!)); + } + + [Fact] + public void should_throw_a_ConcurrencyException() + { + _thrown.Should().BeOfType(); + } + + [Fact] + public void ConcurrencyException_should_have_good_message() + { + _thrown!.Message.Should().Contain(_failedAttempt!.StreamId); + _thrown.Message.Should().Contain("StreamRevision [" + _failedAttempt.CommitSequence); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_committing_with_a_commit_sequence_less_than_or_equal_to_the_most_recent_commit_for_the_stream : using_commit_hooks + { + private const int DuplicateCommitSequence = 1; + private CommitAttempt? _failedAttempt; + private ICommit? _successfulAttempt; + private Exception? _thrown; + + protected override void Context() + { + _successfulAttempt = BuildCommitStub(1, 1, DuplicateCommitSequence); + _failedAttempt = BuildCommitAttemptStub(2, DuplicateCommitSequence); + + Hook.PostCommit(_successfulAttempt); + } + + protected override void Because() + { + _thrown = Catch.Exception(() => Hook.PreCommit(_failedAttempt!)); + } + + [Fact] + public void should_throw_a_ConcurrencyException() + { + _thrown.Should().BeOfType(); + } + + [Fact] + public void ConcurrencyException_should_have_good_message() + { + _thrown!.Message.Should().Contain(_failedAttempt!.StreamId); + _thrown.Message.Should().Contain("CommitSequence [" + _failedAttempt.CommitSequence); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_committing_with_a_stream_revision_less_than_or_equal_to_the_most_recent_commit_for_the_stream : using_commit_hooks + { + private const int DuplicateStreamRevision = 2; + + private CommitAttempt? _failedAttempt; + private ICommit? _successfulAttempt; + private Exception? _thrown; + + protected override void Context() + { + _successfulAttempt = BuildCommitStub(1, DuplicateStreamRevision, 1); + _failedAttempt = BuildCommitAttemptStub(DuplicateStreamRevision, 2); + + Hook.PostCommit(_successfulAttempt); + } + + protected override void Because() + { + _thrown = Catch.Exception(() => Hook.PreCommit(_failedAttempt!)); + } + + [Fact] + public void should_throw_a_ConcurrencyException() + { + _thrown.Should().BeOfType(); + } + + [Fact] + public void Concurrency_exception_should_have_good_message() + { + _thrown!.Message.Should().Contain(_failedAttempt!.StreamId); + _thrown.Message.Should().Contain(_failedAttempt.StreamRevision.ToString()); + } + } + + public class when_tracking_commits : SpecificationBase + { + private const int MaxStreamsToTrack = 2; + private ICommit[]? _trackedCommitAttempts; + + private OptimisticPipelineHook? _hook; + + protected override void Context() + { + _trackedCommitAttempts = + [ + BuildCommit(1, Guid.NewGuid(), Guid.NewGuid()), + BuildCommit(2, Guid.NewGuid(), Guid.NewGuid()), + BuildCommit(3, Guid.NewGuid(), Guid.NewGuid()) + ]; + + _hook = new OptimisticPipelineHook(MaxStreamsToTrack); + } + + protected override void Because() + { + foreach (var commit in _trackedCommitAttempts!) + { + _hook!.Track(commit); + } + } + + [Fact] + public void should_only_contain_streams_explicitly_tracked() + { + ICommit untracked = BuildCommit(4, Guid.Empty, _trackedCommitAttempts![0].CommitId); + _hook!.Contains(untracked).Should().BeFalse(); + } + + [Fact] + public void should_find_tracked_streams() + { + var lastCommit = _trackedCommitAttempts!.Last(); + ICommit stillTracked = BuildCommit(lastCommit.CheckpointToken, lastCommit.StreamId, lastCommit.CommitId); + _hook!.Contains(stillTracked).Should().BeTrue(); + } + + [Fact] + public void should_only_track_the_specified_number_of_streams() + { + var firstCommit = _trackedCommitAttempts![0]; + ICommit droppedFromTracking = BuildCommit(firstCommit.CheckpointToken, firstCommit.StreamId, firstCommit.CommitId); + _hook!.Contains(droppedFromTracking).Should().BeFalse(); + } + + private static Commit BuildCommit(long checkpointToken, Guid streamId, Guid commitId) + { + return BuildCommit(checkpointToken, streamId.ToString(), commitId); + } + + private static Commit BuildCommit(long checkpointToken, string streamId, Guid commitId) + { + return new Commit(Bucket.Default, streamId, 1, commitId, 1, SystemTime.UtcNow, checkpointToken, null, null); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_purging : SpecificationBase + { + private ICommit? _trackedCommit; + private OptimisticPipelineHook? _hook; + + protected override void Context() + { + _trackedCommit = BuildCommit(Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid()); + _hook = new OptimisticPipelineHook(); + _hook.Track(_trackedCommit); + } + + protected override void Because() + { + _hook!.OnPurge(); + } + + [Fact] + public void should_not_track_commit() + { + _hook!.Contains(_trackedCommit!).Should().BeFalse(); + } + + private static Commit BuildCommit(Guid bucketId, Guid streamId, Guid commitId) + { + return new Commit(bucketId.ToString(), streamId.ToString(), 0, commitId, 0, SystemTime.UtcNow, + 1, null, null); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_purging_a_bucket : SpecificationBase + { + private Commit? _trackedCommitBucket1; + private Commit? _trackedCommitBucket2; + private OptimisticPipelineHook? _hook; + + protected override void Context() + { + _trackedCommitBucket1 = BuildCommit(1, Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid()); + _trackedCommitBucket2 = BuildCommit(2, Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid()); + _hook = new OptimisticPipelineHook(); + _hook.Track(_trackedCommitBucket1); + _hook.Track(_trackedCommitBucket2); + } + + protected override void Because() + { + _hook!.OnPurge(_trackedCommitBucket1!.BucketId); + } + + [Fact] + public void should_not_track_the_commit_in_bucket() + { + _hook!.Contains(_trackedCommitBucket1!).Should().BeFalse(); + } + + [Fact] + public void should_track_the_commit_in_other_bucket() + { + _hook!.Contains(_trackedCommitBucket2!).Should().BeTrue(); + } + + private static Commit BuildCommit(long checkpointToken, Guid bucketId, Guid streamId, Guid commitId) + { + return new Commit(bucketId.ToString(), streamId.ToString(), 0, commitId, 0, SystemTime.UtcNow, + checkpointToken, null, null); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_deleting_a_stream : SpecificationBase + { + private Commit? _trackedCommit; + private Commit? _trackedCommitDeleted; + private OptimisticPipelineHook? _hook; + private readonly Guid _bucketId = Guid.NewGuid(); + private readonly Guid _streamIdDeleted = Guid.NewGuid(); + + protected override void Context() + { + _trackedCommit = BuildCommit(1, _bucketId, Guid.NewGuid(), Guid.NewGuid()); + _trackedCommitDeleted = BuildCommit(2, _bucketId, _streamIdDeleted, Guid.NewGuid()); + _hook = new OptimisticPipelineHook(); + _hook.Track(_trackedCommit); + _hook.Track(_trackedCommitDeleted); + } + + protected override void Because() + { + _hook!.OnDeleteStream(_trackedCommitDeleted!.BucketId, _trackedCommitDeleted.StreamId); + } + + [Fact] + public void should_not_track_the_commit_in_the_deleted_stream() + { + _hook!.Contains(_trackedCommitDeleted!).Should().BeFalse(); + } + + [Fact] + public void should_track_the_commit_that_is_not_in_the_deleted_stream() + { + _hook!.Contains(_trackedCommit!).Should().BeTrue(); + } + + private static Commit BuildCommit(long checkpointToken, Guid bucketId, Guid streamId, Guid commitId) + { + return new Commit(bucketId.ToString(), streamId.ToString(), 0, commitId, 0, SystemTime.UtcNow, + checkpointToken, null, null); + } + } + + public abstract class using_commit_hooks : SpecificationBase + { + protected readonly OptimisticPipelineHook Hook = new(); + private readonly string _streamId = Guid.NewGuid().ToString(); + + /* + protected CommitAttempt BuildCommitAttempt(Guid commitId) + { + return new CommitAttempt(_streamId, 1, commitId, 1, SystemTime.UtcNow, null, null); + } + */ + + protected ICommit BuildCommitStub(long checkpointToken, int streamRevision, int commitSequence) + { + List events = new[] { new EventMessage() }.ToList(); + return new Commit(Bucket.Default, _streamId, streamRevision, Guid.NewGuid(), commitSequence, SystemTime.UtcNow, checkpointToken, null, events); + } + + protected CommitAttempt BuildCommitAttemptStub(int streamRevision, int commitSequence) + { + EventMessage[] events = [new EventMessage()]; + return new CommitAttempt(Bucket.Default, _streamId, streamRevision, Guid.NewGuid(), commitSequence, SystemTime.UtcNow, null, events); + } + + protected ICommit BuildCommitStub(long checkpointToken, Guid commitId, int streamRevision, int commitSequence) + { + List events = new[] { new EventMessage() }.ToList(); + return new Commit(Bucket.Default, _streamId, streamRevision, commitId, commitSequence, SystemTime.UtcNow, checkpointToken, null, events); + } + } + } +} + +#pragma warning restore 169 // ReSharper enable InconsistentNaming +#pragma warning restore IDE1006 // Naming Styles \ No newline at end of file diff --git a/src/NEventStore.Tests/Persistence/InMemory/InMemoryPersistenceTests.cs b/src/NEventStore.Tests/Persistence/InMemory/InMemoryPersistenceTests.cs new file mode 100644 index 000000000..a947d4ea8 --- /dev/null +++ b/src/NEventStore.Tests/Persistence/InMemory/InMemoryPersistenceTests.cs @@ -0,0 +1,84 @@ +using NEventStore.Persistence.AcceptanceTests.BDD; +#pragma warning disable IDE1006 // Naming Styles + +using FluentAssertions; +#if MSTEST +using Microsoft.VisualStudio.TestTools.UnitTesting; +#endif +#if NUNIT +#endif +#if XUNIT +using Xunit; +using Xunit.Should; +#endif + +namespace NEventStore.Persistence.InMemory +{ +#if MSTEST + [TestClass] +#endif + public class when_getting_from_to_then_should_not_get_later_commits : SpecificationBase + { + private readonly DateTime _endDate = new(2013, 1, 2); + private readonly DateTime _startDate = new(2013, 1, 1); + private ICommit[]? _commits; + private InMemoryPersistenceEngine? _engine; + + protected override void Context() + { + _engine = new InMemoryPersistenceEngine(); + _engine.Initialize(); + var streamId = Guid.NewGuid().ToString(); + _engine.Commit(new CommitAttempt(streamId, 1, Guid.NewGuid(), 1, _startDate, new Dictionary(), [new EventMessage()])); + _engine.Commit(new CommitAttempt(streamId, 2, Guid.NewGuid(), 2, _endDate, new Dictionary(), [new EventMessage()])); + } + + protected override void Because() + { + _commits = _engine!.GetFromTo(Bucket.Default, _startDate, _endDate).ToArray(); + } + + [Fact] + public void should_return_two_commits() + { + _commits!.Length.Should().Be(1); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_getting_from_to_checkpoint_then_should_not_get_later_commits : SpecificationBase + { + private readonly DateTime _endDate = new(2013, 1, 2); + private readonly DateTime _startDate = new(2013, 1, 1); + private ICommit[]? _commits; + private InMemoryPersistenceEngine? _engine; + private ICommit? _commit1; + private ICommit? _commit2; + + protected override void Context() + { + _engine = new InMemoryPersistenceEngine(); + _engine.Initialize(); + var streamId = Guid.NewGuid().ToString(); + _commit1 = _engine.Commit(new CommitAttempt(streamId, 1, Guid.NewGuid(), 1, _startDate, new Dictionary(), [new EventMessage()])); + _commit2 = _engine.Commit(new CommitAttempt(streamId, 2, Guid.NewGuid(), 2, _endDate, new Dictionary(), [new EventMessage()])); + _engine.Commit(new CommitAttempt(streamId, 3, Guid.NewGuid(), 3, _endDate.AddDays(1), new Dictionary(), [new EventMessage()])); + } + + protected override void Because() + { + _commits = _engine!.GetFromTo(Bucket.Default, _commit1!.CheckpointToken, _commit2!.CheckpointToken).ToArray(); + } + + [Fact] + public void should_return_two_commits() + { + _commits!.Length.Should().Be(1); + _commit2!.Should().Be(_commits![0]); + } + } +} + +#pragma warning restore IDE1006 // Naming Styles diff --git a/src/NEventStore.Tests/Persistence/InMemory/PersistenceEngineFixture.cs b/src/NEventStore.Tests/Persistence/InMemory/PersistenceEngineFixture.cs new file mode 100644 index 000000000..15463be7f --- /dev/null +++ b/src/NEventStore.Tests/Persistence/InMemory/PersistenceEngineFixture.cs @@ -0,0 +1,14 @@ +// ReSharper disable once CheckNamespace +using NEventStore.Persistence.InMemory; + +namespace NEventStore.Persistence.AcceptanceTests +{ + public partial class PersistenceEngineFixture + { + public PersistenceEngineFixture() + { + _createPersistence = _ => + new InMemoryPersistenceEngine(); + } + } +} diff --git a/src/NEventStore.Tests/Persistence/InMemory/PersistenceEngineFixtureAsync.cs b/src/NEventStore.Tests/Persistence/InMemory/PersistenceEngineFixtureAsync.cs new file mode 100644 index 000000000..4b3ab88c3 --- /dev/null +++ b/src/NEventStore.Tests/Persistence/InMemory/PersistenceEngineFixtureAsync.cs @@ -0,0 +1,14 @@ +// ReSharper disable once CheckNamespace +using NEventStore.Persistence.InMemory; + +namespace NEventStore.Persistence.AcceptanceTests.Async +{ + public partial class PersistenceEngineFixtureAsync + { + public PersistenceEngineFixtureAsync() + { + _createPersistence = _ => + new InMemoryPersistenceEngine(); + } + } +} \ No newline at end of file diff --git a/src/NEventStore.Tests/PipelineHooksAwarePersistanceDecoratorTests.Async.cs b/src/NEventStore.Tests/PipelineHooksAwarePersistanceDecoratorTests.Async.cs new file mode 100644 index 000000000..4513d2e81 --- /dev/null +++ b/src/NEventStore.Tests/PipelineHooksAwarePersistanceDecoratorTests.Async.cs @@ -0,0 +1,823 @@ +using FakeItEasy; +using FluentAssertions; +using NEventStore.Persistence; +using NEventStore.Persistence.AcceptanceTests.BDD; +#if MSTEST +using Microsoft.VisualStudio.TestTools.UnitTesting; +using FluentAssertions; +#endif +#if NUNIT +#endif +#if XUNIT +using Xunit; +using Xunit.Should; +#endif + +#pragma warning disable 169 +// ReSharper disable InconsistentNaming +#pragma warning disable IDE1006 // Naming Styles + +namespace NEventStore.Async +{ +#if MSTEST + [TestClass] +#endif + public class when_disposing_the_decorator : using_underlying_persistence + { + protected override void Because() + { + Decorator.Dispose(); + } + + [Fact] + public void should_dispose_the_underlying_persistence() + { + A.CallTo(() => persistence.Dispose()).MustHaveHappenedOnceExactly(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_reading_the_all_events_in_a_bucket_from_date : using_underlying_persistence + { + private ICommit? _commit; + private DateTime _date; + private IPipelineHook? _hook1; + private IPipelineHook? _hook2; + private IPipelineHookAsync? _hook1Async; + private IPipelineHookAsync? _hook2Async; + + protected override void Context() + { + _date = DateTime.Now; + _commit = new Commit(Bucket.Default, streamId, 1, Guid.NewGuid(), 1, _date, 1, null, null); + + _hook1 = A.Fake(); + A.CallTo(() => _hook1.SelectCommit(_commit)).Returns(_commit); + pipelineHooks.Add(_hook1); + + _hook2 = A.Fake(); + A.CallTo(() => _hook2.SelectCommit(_commit)).Returns(_commit); + pipelineHooks.Add(_hook2); + + _hook1Async = A.Fake(); + A.CallTo(() => _hook1Async.SelectCommitAsync(_commit, A.Ignored)).Returns(Task.FromResult(_commit)); + pipelineHooksAsync.Add(_hook1Async); + + _hook2Async = A.Fake(); + A.CallTo(() => _hook2Async.SelectCommitAsync(_commit, A.Ignored)).Returns(Task.FromResult(_commit)); + pipelineHooksAsync.Add(_hook2Async); + + A.CallTo(() => persistence.GetFrom(Bucket.Default, _date)).Returns([_commit]); + } + + protected override void Because() + { + // ReSharper disable once ReturnValueOfPureMethodIsNotUsed + // Forces enumeration of commits. + Decorator.GetFrom(Bucket.Default, _date).ToList(); + } + + [Fact] + public void should_call_the_underlying_persistence_to_get_events() + { + A.CallTo(() => persistence.GetFrom(Bucket.Default, _date)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public void should_pass_all_events_through_the_pipeline_hooks() + { + A.CallTo(() => _hook1!.SelectCommit(_commit!)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _hook2!.SelectCommit(_commit!)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public void should_pass_all_events_through_the_async_pipeline_hooks() + { + A.CallTo(() => _hook1Async!.SelectCommitAsync(_commit!, A.Ignored)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _hook2Async!.SelectCommitAsync(_commit!, A.Ignored)).MustHaveHappenedOnceExactly(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_reading_the_all_events_in_a_bucket_from_min_to_max_revision : using_underlying_persistence + { + private ICommit? _commit; + private IPipelineHook? _hook1; + private IPipelineHook? _hook2; + private IPipelineHookAsync? _hook1Async; + private IPipelineHookAsync? _hook2Async; + + protected override void Context() + { + _commit = new Commit(Bucket.Default, streamId, 1, Guid.NewGuid(), 1, DateTime.Now, 1, null, null); + + _hook1 = A.Fake(); + A.CallTo(() => _hook1.SelectCommit(_commit)).Returns(_commit); + pipelineHooks.Add(_hook1); + + _hook2 = A.Fake(); + A.CallTo(() => _hook2.SelectCommit(_commit)).Returns(_commit); + pipelineHooks.Add(_hook2); + + _hook1Async = A.Fake(); + A.CallTo(() => _hook1Async.SelectCommitAsync(_commit, A.Ignored)).Returns(Task.FromResult(_commit)); + pipelineHooksAsync.Add(_hook1Async); + + _hook2Async = A.Fake(); + A.CallTo(() => _hook2Async.SelectCommitAsync(_commit, A.Ignored)).Returns(Task.FromResult(_commit)); + pipelineHooksAsync.Add(_hook2Async); + + A.CallTo(() => persistence.GetFromAsync(Bucket.Default, _commit.StreamId, 0, int.MaxValue, A>.Ignored, CancellationToken.None)) + .ReturnsLazily(async (string bucketId, string streamId, int minRevision, int maxRevision, IAsyncObserver asyncObserver, CancellationToken cancellation) => + { + await asyncObserver.OnNextAsync(_commit, cancellation).ConfigureAwait(false); + await asyncObserver.OnCompletedAsync(cancellation).ConfigureAwait(false); + }); + } + + protected override Task BecauseAsync() + { + // ReSharper disable once ReturnValueOfPureMethodIsNotUsed + // Forces enumeration of commits. + var streamObserver = new CommitStreamObserver(); + return Decorator.GetFromAsync(Bucket.Default, _commit!.StreamId, 0, int.MaxValue, streamObserver, CancellationToken.None); + } + + [Fact] + public void should_call_the_underlying_persistence_to_get_events() + { + A.CallTo(() => persistence.GetFromAsync(Bucket.Default, _commit!.StreamId, 0, int.MaxValue, A>.Ignored, CancellationToken.None)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public void should_pass_all_events_through_the_pipeline_hooks() + { + A.CallTo(() => _hook1!.SelectCommit(_commit!)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _hook2!.SelectCommit(_commit!)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public void should_pass_all_events_through_the_async_pipeline_hooks() + { + A.CallTo(() => _hook1Async!.SelectCommitAsync(_commit!, A.Ignored)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _hook2Async!.SelectCommitAsync(_commit!, A.Ignored)).MustHaveHappenedOnceExactly(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_reading_all_events_in_a_bucket_from_date_to_date : using_underlying_persistence + { + private ICommit? _commit; + private DateTime _end; + private IPipelineHook? _hook1; + private IPipelineHook? _hook2; + private IPipelineHookAsync? _hook1Async; + private IPipelineHookAsync? _hook2Async; + private DateTime _start; + + protected override void Context() + { + _start = DateTime.Now; + _end = DateTime.Now; + _commit = new Commit(Bucket.Default, streamId, 1, Guid.NewGuid(), 1, DateTime.Now, 1, null, null); + + _hook1 = A.Fake(); + A.CallTo(() => _hook1.SelectCommit(_commit)).Returns(_commit); + pipelineHooks.Add(_hook1); + + _hook2 = A.Fake(); + A.CallTo(() => _hook2.SelectCommit(_commit)).Returns(_commit); + pipelineHooks.Add(_hook2); + + _hook1Async = A.Fake(); + A.CallTo(() => _hook1Async.SelectCommitAsync(_commit, A.Ignored)).Returns(Task.FromResult(_commit)); + pipelineHooksAsync.Add(_hook1Async); + + _hook2Async = A.Fake(); + A.CallTo(() => _hook2Async.SelectCommitAsync(_commit, A.Ignored)).Returns(Task.FromResult(_commit)); + pipelineHooksAsync.Add(_hook2Async); + + A.CallTo(() => persistence.GetFromTo(Bucket.Default, _start, _end)).Returns([_commit]); + } + + protected override void Because() + { + // ReSharper disable once ReturnValueOfPureMethodIsNotUsed + // Forces enumeration of commits + Decorator.GetFromTo(Bucket.Default, _start, _end).ToList(); + } + + [Fact] + public void should_call_the_underlying_persistence_to_get_events() + { + A.CallTo(() => persistence.GetFromTo(Bucket.Default, _start, _end)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public void should_pass_all_events_through_the_pipeline_hooks() + { + A.CallTo(() => _hook1!.SelectCommit(_commit!)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _hook2!.SelectCommit(_commit!)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public void should_pass_all_events_through_the_async_pipeline_hooks() + { + A.CallTo(() => _hook1Async!.SelectCommitAsync(_commit!, A.Ignored)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _hook2Async!.SelectCommitAsync(_commit!, A.Ignored)).MustHaveHappenedOnceExactly(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_reading_all_events_in_all_buckets_from_checkpoint_to_checkpoint : using_underlying_persistence + { + private ICommit? _commit; + private Int64 _end; + private IPipelineHook? _hook1; + private IPipelineHook? _hook2; + private IPipelineHookAsync? _hook1Async; + private IPipelineHookAsync? _hook2Async; + private Int64 _start; + + protected override void Context() + { + _start = 0; + _end = 1; + _commit = new Commit(Bucket.Default, streamId, 1, Guid.NewGuid(), 1, DateTime.Now, 1, null, null); + + _hook1 = A.Fake(); + A.CallTo(() => _hook1.SelectCommit(_commit)).Returns(_commit); + pipelineHooks.Add(_hook1); + + _hook2 = A.Fake(); + A.CallTo(() => _hook2.SelectCommit(_commit)).Returns(_commit); + pipelineHooks.Add(_hook2); + + _hook1Async = A.Fake(); + A.CallTo(() => _hook1Async.SelectCommitAsync(_commit, A.Ignored)).Returns(Task.FromResult(_commit)); + pipelineHooksAsync.Add(_hook1Async); + + _hook2Async = A.Fake(); + A.CallTo(() => _hook2Async.SelectCommitAsync(_commit, A.Ignored)).Returns(Task.FromResult(_commit)); + pipelineHooksAsync.Add(_hook2Async); + + A.CallTo(() => persistence.GetFromToAsync(_start, _end, A>.Ignored, CancellationToken.None)) + .ReturnsLazily(async (long fromCheckpointToken, long toCheckpointToken, IAsyncObserver asyncObserver, CancellationToken cancellation) => + { + await asyncObserver.OnNextAsync(_commit, cancellation).ConfigureAwait(false); + await asyncObserver.OnCompletedAsync(cancellation).ConfigureAwait(false); + }); + } + + protected override Task BecauseAsync() + { + return Decorator.GetFromToAsync(_start, _end, new CommitStreamObserver(), CancellationToken.None); + } + + [Fact] + public void should_call_the_underlying_persistence_to_get_events() + { + A.CallTo(() => persistence.GetFromToAsync(_start, _end, A>.Ignored, CancellationToken.None)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public void should_pass_all_events_through_the_pipeline_hooks() + { + A.CallTo(() => _hook1!.SelectCommit(_commit!)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _hook2!.SelectCommit(_commit!)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public void should_pass_all_events_through_the_async_pipeline_hooks() + { + A.CallTo(() => _hook1Async!.SelectCommitAsync(_commit!, A.Ignored)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _hook2Async!.SelectCommitAsync(_commit!, A.Ignored)).MustHaveHappenedOnceExactly(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_reading_all_events_in_a_bucket_from_checkpoint_to_checkpoint : using_underlying_persistence + { + private ICommit? _commit; + private Int64 _end; + private IPipelineHook? _hook1; + private IPipelineHook? _hook2; + private IPipelineHookAsync? _hook1Async; + private IPipelineHookAsync? _hook2Async; + private Int64 _start; + + protected override void Context() + { + _start = 0; + _end = 1; + _commit = new Commit(Bucket.Default, streamId, 1, Guid.NewGuid(), 1, DateTime.Now, 1, null, null); + + _hook1 = A.Fake(); + A.CallTo(() => _hook1.SelectCommit(_commit)).Returns(_commit); + pipelineHooks.Add(_hook1); + + _hook2 = A.Fake(); + A.CallTo(() => _hook2.SelectCommit(_commit)).Returns(_commit); + pipelineHooks.Add(_hook2); + + _hook1Async = A.Fake(); + A.CallTo(() => _hook1Async.SelectCommitAsync(_commit, A.Ignored)).Returns(Task.FromResult(_commit)); + pipelineHooksAsync.Add(_hook1Async); + + _hook2Async = A.Fake(); + A.CallTo(() => _hook2Async.SelectCommitAsync(_commit, A.Ignored)).Returns(Task.FromResult(_commit)); + pipelineHooksAsync.Add(_hook2Async); + + A.CallTo(() => persistence.GetFromToAsync(Bucket.Default, _start, _end, A>.Ignored, CancellationToken.None)) + .ReturnsLazily(async (string bucketId, long fromCheckpointToken, long toCheckpointToken, IAsyncObserver asyncObserver, CancellationToken cancellation) => + { + await asyncObserver.OnNextAsync(_commit, cancellation).ConfigureAwait(false); + await asyncObserver.OnCompletedAsync(cancellation).ConfigureAwait(false); + }); + } + + protected override Task BecauseAsync() + { + return Decorator.GetFromToAsync(Bucket.Default, _start, _end, new CommitStreamObserver(), CancellationToken.None); + } + + [Fact] + public void should_call_the_underlying_persistence_to_get_events() + { + A.CallTo(() => persistence.GetFromToAsync(Bucket.Default, _start, _end, A>.Ignored, CancellationToken.None)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public void should_pass_all_events_through_the_pipeline_hooks() + { + A.CallTo(() => _hook1!.SelectCommit(_commit!)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _hook2!.SelectCommit(_commit!)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public void should_pass_all_events_through_the_async_pipeline_hooks() + { + A.CallTo(() => _hook1Async!.SelectCommitAsync(_commit!, A.Ignored)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _hook2Async!.SelectCommitAsync(_commit!, A.Ignored)).MustHaveHappenedOnceExactly(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_committing : using_underlying_persistence + { + private CommitAttempt? _attempt; + + protected override void Context() + { + _attempt = new CommitAttempt(streamId, 1, Guid.NewGuid(), 1, DateTime.Now, null, [new EventMessage()]); + } + + protected override Task BecauseAsync() + { + return Decorator.CommitAsync(_attempt!, CancellationToken.None); + } + + [Fact] + public void should_dispose_the_underlying_persistence() + { + A.CallTo(() => persistence.CommitAsync(_attempt!, CancellationToken.None)).MustHaveHappenedOnceExactly(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_reading_the_all_events_from_checkpoint : using_underlying_persistence + { + private ICommit? _commit; + private IPipelineHook? _hook1; + private IPipelineHook? _hook2; + private IPipelineHookAsync? _hook1Async; + private IPipelineHookAsync? _hook2Async; + + protected override void Context() + { + _commit = new Commit(Bucket.Default, streamId, 1, Guid.NewGuid(), 1, DateTime.Now, 1, null, null); + + _hook1 = A.Fake(); + A.CallTo(() => _hook1.SelectCommit(_commit)).Returns(_commit); + pipelineHooks.Add(_hook1); + + _hook2 = A.Fake(); + A.CallTo(() => _hook2.SelectCommit(_commit)).Returns(_commit); + pipelineHooks.Add(_hook2); + + _hook1Async = A.Fake(); + A.CallTo(() => _hook1Async.SelectCommitAsync(_commit, A.Ignored)).Returns(Task.FromResult(_commit)); + pipelineHooksAsync.Add(_hook1Async); + + _hook2Async = A.Fake(); + A.CallTo(() => _hook2Async.SelectCommitAsync(_commit, A.Ignored)).Returns(Task.FromResult(_commit)); + pipelineHooksAsync.Add(_hook2Async); + + A.CallTo(() => persistence.GetFromAsync(0, A>.Ignored, CancellationToken.None)) + .ReturnsLazily(async (long checkpointToken, IAsyncObserver asyncObserver, CancellationToken cancellation) => + { + await asyncObserver.OnNextAsync(_commit, cancellation).ConfigureAwait(false); + await asyncObserver.OnCompletedAsync(cancellation).ConfigureAwait(false); + }); + } + + protected override Task BecauseAsync() + { + return Decorator.GetFromAsync(0, new CommitStreamObserver(), CancellationToken.None); + } + + [Fact] + public void should_call_the_underlying_persistence_to_get_events() + { + A.CallTo(() => persistence.GetFromAsync(0, A>.Ignored, CancellationToken.None)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public void should_pass_all_events_through_the_pipeline_hooks() + { + A.CallTo(() => _hook1!.SelectCommit(_commit!)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _hook2!.SelectCommit(_commit!)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public void should_pass_all_events_through_the_async_pipeline_hooks() + { + A.CallTo(() => _hook1Async!.SelectCommitAsync(_commit!, A.Ignored)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _hook2Async!.SelectCommitAsync(_commit!, A.Ignored)).MustHaveHappenedOnceExactly(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_reading_the_all_events_in_a_bucket_from_checkpoint : using_underlying_persistence + { + private ICommit? _commit; + private IPipelineHook? _hook1; + private IPipelineHook? _hook2; + private IPipelineHookAsync? _hook1Async; + private IPipelineHookAsync? _hook2Async; + + protected override void Context() + { + _commit = new Commit(Bucket.Default, streamId, 1, Guid.NewGuid(), 1, DateTime.Now, 1, null, null); + + _hook1 = A.Fake(); + A.CallTo(() => _hook1.SelectCommit(_commit)).Returns(_commit); + pipelineHooks.Add(_hook1); + + _hook2 = A.Fake(); + A.CallTo(() => _hook2.SelectCommit(_commit)).Returns(_commit); + pipelineHooks.Add(_hook2); + + _hook1Async = A.Fake(); + A.CallTo(() => _hook1Async.SelectCommitAsync(_commit, A.Ignored)).Returns(Task.FromResult(_commit)); + pipelineHooksAsync.Add(_hook1Async); + + _hook2Async = A.Fake(); + A.CallTo(() => _hook2Async.SelectCommitAsync(_commit, A.Ignored)).Returns(Task.FromResult(_commit)); + pipelineHooksAsync.Add(_hook2Async); + + A.CallTo(() => persistence.GetFromAsync(Bucket.Default, 0, A>.Ignored, CancellationToken.None)) + .ReturnsLazily(async (string bucketId, long checkpointToken, IAsyncObserver asyncObserver, CancellationToken cancellation) => + { + await asyncObserver.OnNextAsync(_commit, cancellation).ConfigureAwait(false); + await asyncObserver.OnCompletedAsync(cancellation).ConfigureAwait(false); + }); + } + + protected override Task BecauseAsync() + { + return Decorator.GetFromAsync(Bucket.Default, 0, new CommitStreamObserver(), CancellationToken.None); + } + + [Fact] + public void should_call_the_underlying_persistence_to_get_events() + { + A.CallTo(() => persistence.GetFromAsync(Bucket.Default, 0, A>.Ignored, CancellationToken.None)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public void should_pass_all_events_through_the_pipeline_hooks() + { + A.CallTo(() => _hook1!.SelectCommit(_commit!)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _hook2!.SelectCommit(_commit!)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public void should_pass_all_events_through_the_async_pipeline_hooks() + { + A.CallTo(() => _hook1Async!.SelectCommitAsync(_commit!, A.Ignored)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _hook2Async!.SelectCommitAsync(_commit!, A.Ignored)).MustHaveHappenedOnceExactly(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_purging : using_underlying_persistence + { + private IPipelineHook? _hook; + private IPipelineHookAsync? _hookAsync; + + protected override void Context() + { + _hook = A.Fake(); + pipelineHooks.Add(_hook); + + _hookAsync = A.Fake(); + pipelineHooksAsync.Add(_hookAsync); + } + + protected override Task BecauseAsync() + { + return Decorator.PurgeAsync(CancellationToken.None); + } + + [Fact] + public void should_call_the_pipeline_hook_purge() + { + A.CallTo(() => _hook!.OnPurge(null)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public void should_call_the_async_pipeline_hook_purge() + { + A.CallTo(() => _hookAsync!.OnPurgeAsync(null, CancellationToken.None)).MustHaveHappenedOnceExactly(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_purging_a_bucket : using_underlying_persistence + { + private IPipelineHook? _hook; + private IPipelineHookAsync? _hookAsync; + private const string _bucketId = "Bucket"; + + protected override void Context() + { + _hook = A.Fake(); + pipelineHooks.Add(_hook); + + _hookAsync = A.Fake(); + pipelineHooksAsync.Add(_hookAsync); + } + + protected override Task BecauseAsync() + { + return Decorator.PurgeAsync(_bucketId, CancellationToken.None); + } + + [Fact] + public void should_call_the_pipeline_hook_purge() + { + A.CallTo(() => _hook!.OnPurge(_bucketId)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public void should_call_the_async_pipeline_hook_purge() + { + A.CallTo(() => _hookAsync!.OnPurgeAsync(_bucketId, CancellationToken.None)).MustHaveHappenedOnceExactly(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_deleting_a_stream : using_underlying_persistence + { + private IPipelineHook? _hook; + private IPipelineHookAsync? _hookAsync; + private const string _bucketId = "Bucket"; + private const string _streamId = "Stream"; + + protected override void Context() + { + _hook = A.Fake(); + pipelineHooks.Add(_hook); + + _hookAsync = A.Fake(); + pipelineHooksAsync.Add(_hookAsync); + } + + protected override Task BecauseAsync() + { + return Decorator.DeleteStreamAsync(_bucketId, _streamId, CancellationToken.None); + } + + [Fact] + public void should_call_the_pipeline_hook_purge() + { + A.CallTo(() => _hook!.OnDeleteStream(_bucketId, _streamId)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public void should_call_the_async_pipeline_hook_purge() + { + A.CallTo(() => _hookAsync!.OnDeleteStreamAsync(_bucketId, _streamId, CancellationToken.None)).MustHaveHappenedOnceExactly(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_reading_the_all_events_from_checkpoint_with_filtering_PipelineHook_Sync : using_underlying_persistence + { + private ICommit? _commit; + private IPipelineHook? _hook1; + private IPipelineHook? _hook2; + private IPipelineHookAsync? _hook1Async; + private IPipelineHookAsync? _hook2Async; + private IList? _commits; + + protected override void Context() + { + _commit = new Commit(Bucket.Default, streamId, 1, Guid.NewGuid(), 1, DateTime.Now, 1, null, null); + + _hook1 = A.Fake(); + A.CallTo(() => _hook1.SelectCommit(_commit)).Returns(null); + pipelineHooks.Add(_hook1); + + _hook2 = A.Fake(); + A.CallTo(() => _hook2.SelectCommit(_commit)).Returns(_commit); + pipelineHooks.Add(_hook2); + + _hook1Async = A.Fake(); + A.CallTo(() => _hook1Async.SelectCommitAsync(_commit, A.Ignored)).Returns(Task.FromResult(_commit)); + pipelineHooksAsync.Add(_hook1Async); + + _hook2Async = A.Fake(); + A.CallTo(() => _hook2Async.SelectCommitAsync(_commit, A.Ignored)).Returns(Task.FromResult(_commit)); + pipelineHooksAsync.Add(_hook2Async); + + A.CallTo(() => persistence.GetFromAsync(0, A>.Ignored, CancellationToken.None)) + .ReturnsLazily(async (long checkpointToken, IAsyncObserver asyncObserver, CancellationToken cancellation) => + { + await asyncObserver.OnNextAsync(_commit, cancellation).ConfigureAwait(false); + await asyncObserver.OnCompletedAsync(cancellation).ConfigureAwait(false); + }); + } + + protected override async Task BecauseAsync() + { + var observer = new CommitStreamObserver(); + await Decorator.GetFromAsync(0, observer, CancellationToken.None).ConfigureAwait(false); + _commits = observer.Commits; + } + + [Fact] + public void should_call_the_underlying_persistence_to_get_events() + { + A.CallTo(() => persistence.GetFromAsync(0, A>.Ignored, CancellationToken.None)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public void should_pass_all_events_through_the_first_pipeline_hooks() + { + A.CallTo(() => _hook1!.SelectCommit(_commit!)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public void should_not_pass_the_events_through_the_second_pipeline_hooks() + { + A.CallTo(() => _hook2!.SelectCommit(_commit!)).MustNotHaveHappened(); + } + + [Fact] + public void should_not_pass_the_events_through_the_first_async_pipeline_hooks() + { + A.CallTo(() => _hook1Async!.SelectCommitAsync(_commit!, A.Ignored)).MustNotHaveHappened(); + } + + [Fact] + public void should_not_pass_the_events_through_the_second_async_pipeline_hooks() + { + A.CallTo(() => _hook2Async!.SelectCommitAsync(_commit!, A.Ignored)).MustNotHaveHappened(); + } + + [Fact] + public void commit_list_should_be_empty() + { + _commits.Should().BeEmpty(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_reading_the_all_events_from_checkpoint_with_filtering_PipelineHook_Async : using_underlying_persistence + { + private ICommit? _commit; + private IPipelineHook? _hook1; + private IPipelineHook? _hook2; + private IPipelineHookAsync? _hook1Async; + private IPipelineHookAsync? _hook2Async; + private IList? _commits; + + protected override void Context() + { + _commit = new Commit(Bucket.Default, streamId, 1, Guid.NewGuid(), 1, DateTime.Now, 1, null, null); + + _hook1 = A.Fake(); + A.CallTo(() => _hook1.SelectCommit(_commit)).Returns(_commit); + pipelineHooks.Add(_hook1); + + _hook2 = A.Fake(); + A.CallTo(() => _hook2.SelectCommit(_commit)).Returns(_commit); + pipelineHooks.Add(_hook2); + + _hook1Async = A.Fake(); + A.CallTo(() => _hook1Async.SelectCommitAsync(_commit, A.Ignored)).Returns(Task.FromResult(null)); + pipelineHooksAsync.Add(_hook1Async); + + _hook2Async = A.Fake(); + A.CallTo(() => _hook2Async.SelectCommitAsync(_commit, A.Ignored)).Returns(Task.FromResult(_commit)); + pipelineHooksAsync.Add(_hook2Async); + + A.CallTo(() => persistence.GetFromAsync(0, A>.Ignored, CancellationToken.None)) + .ReturnsLazily(async (long checkpointToken, IAsyncObserver asyncObserver, CancellationToken cancellation) => + { + await asyncObserver.OnNextAsync(_commit, cancellation).ConfigureAwait(false); + await asyncObserver.OnCompletedAsync(cancellation).ConfigureAwait(false); + }); + } + + protected override async Task BecauseAsync() + { + var observer = new CommitStreamObserver(); + await Decorator.GetFromAsync(0, observer, CancellationToken.None).ConfigureAwait(false); + _commits = observer.Commits; + } + + [Fact] + public void should_call_the_underlying_persistence_to_get_events() + { + A.CallTo(() => persistence.GetFromAsync(0, A>.Ignored, CancellationToken.None)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public void should_pass_all_events_through_the_first_pipeline_hooks() + { + A.CallTo(() => _hook1!.SelectCommit(_commit!)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public void should_pass_the_events_through_the_second_pipeline_hooks() + { + A.CallTo(() => _hook2!.SelectCommit(_commit!)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public void should_pass_the_events_through_the_first_async_pipeline_hooks() + { + A.CallTo(() => _hook1Async!.SelectCommitAsync(_commit!, A.Ignored)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public void should_not_pass_the_events_through_the_second_async_pipeline_hooks() + { + A.CallTo(() => _hook2Async!.SelectCommitAsync(_commit!, A.Ignored)).MustNotHaveHappened(); + } + + [Fact] + public void commit_list_should_be_empty() + { + _commits.Should().BeEmpty(); + } + } + + public abstract class using_underlying_persistence : SpecificationBase + { + private PipelineHooksAwarePersistStreamsDecorator? decorator; + protected readonly IPersistStreams persistence = A.Fake(); + protected readonly List pipelineHooks = []; + protected readonly List pipelineHooksAsync = []; + protected readonly string streamId = Guid.NewGuid().ToString(); + + public PipelineHooksAwarePersistStreamsDecorator Decorator + { + get { return decorator ??= new PipelineHooksAwarePersistStreamsDecorator(persistence, pipelineHooks.Select(x => x), pipelineHooksAsync.Select(x => x)); } + set { decorator = value; } + } + } +} + +#pragma warning restore IDE1006 // Naming Styles +// ReSharper enable InconsistentNaming +#pragma warning restore 169 \ No newline at end of file diff --git a/src/NEventStore.Tests/PipelineHooksAwarePersistanceDecoratorTests.cs b/src/NEventStore.Tests/PipelineHooksAwarePersistanceDecoratorTests.cs new file mode 100644 index 000000000..5ba601795 --- /dev/null +++ b/src/NEventStore.Tests/PipelineHooksAwarePersistanceDecoratorTests.cs @@ -0,0 +1,788 @@ +using FakeItEasy; +using FluentAssertions; +using NEventStore.Persistence; +using NEventStore.Persistence.AcceptanceTests.BDD; +#if MSTEST +using Microsoft.VisualStudio.TestTools.UnitTesting; +using FluentAssertions; +#endif +#if NUNIT +#endif +#if XUNIT +using Xunit; +using Xunit.Should; +#endif + +#pragma warning disable 169 +// ReSharper disable InconsistentNaming +#pragma warning disable IDE1006 // Naming Styles + +namespace NEventStore +{ +#if MSTEST + [TestClass] +#endif + public class when_disposing_the_decorator : using_underlying_persistence + { + protected override void Because() + { + Decorator.Dispose(); + } + + [Fact] + public void should_dispose_the_underlying_persistence() + { + A.CallTo(() => persistence.Dispose()).MustHaveHappenedOnceExactly(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_reading_the_all_events_in_a_bucket_from_date : using_underlying_persistence + { + private ICommit? _commit; + private DateTime _date; + private IPipelineHook? _hook1; + private IPipelineHook? _hook2; + private IPipelineHookAsync? _hook1Async; + private IPipelineHookAsync? _hook2Async; + + protected override void Context() + { + _date = DateTime.Now; + _commit = new Commit(Bucket.Default, streamId, 1, Guid.NewGuid(), 1, _date, 1, null, null); + + _hook1 = A.Fake(); + A.CallTo(() => _hook1.SelectCommit(_commit)).Returns(_commit); + pipelineHooks.Add(_hook1); + + _hook2 = A.Fake(); + A.CallTo(() => _hook2.SelectCommit(_commit)).Returns(_commit); + pipelineHooks.Add(_hook2); + + _hook1Async = A.Fake(); + A.CallTo(() => _hook1Async.SelectCommitAsync(_commit, A.Ignored)).Returns(Task.FromResult(_commit)); + pipelineHooksAsync.Add(_hook1Async); + + _hook2Async = A.Fake(); + A.CallTo(() => _hook2Async.SelectCommitAsync(_commit, A.Ignored)).Returns(Task.FromResult(_commit)); + pipelineHooksAsync.Add(_hook2Async); + + A.CallTo(() => persistence.GetFrom(Bucket.Default, _date)).Returns([_commit]); + } + + protected override void Because() + { + // ReSharper disable once ReturnValueOfPureMethodIsNotUsed + // Forces enumeration of commits. + Decorator.GetFrom(Bucket.Default, _date).ToList(); + } + + [Fact] + public void should_call_the_underlying_persistence_to_get_events() + { + A.CallTo(() => persistence.GetFrom(Bucket.Default, _date)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public void should_pass_all_events_through_the_pipeline_hooks() + { + A.CallTo(() => _hook1!.SelectCommit(_commit!)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _hook2!.SelectCommit(_commit!)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public void should_pass_all_events_through_the_async_pipeline_hooks() + { + A.CallTo(() => _hook1Async!.SelectCommitAsync(_commit!, A.Ignored)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _hook2Async!.SelectCommitAsync(_commit!, A.Ignored)).MustHaveHappenedOnceExactly(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_reading_the_all_events_in_a_bucket_from_min_to_max_revision : using_underlying_persistence + { + private Commit? _commit; + private IPipelineHook? _hook1; + private IPipelineHook? _hook2; + private IPipelineHookAsync? _hook1Async; + private IPipelineHookAsync? _hook2Async; + + protected override void Context() + { + _commit = new Commit(Bucket.Default, streamId, 1, Guid.NewGuid(), 1, DateTime.Now, 1, null, null); + + _hook1 = A.Fake(); + A.CallTo(() => _hook1.SelectCommit(_commit)).Returns(_commit); + pipelineHooks.Add(_hook1); + + _hook2 = A.Fake(); + A.CallTo(() => _hook2.SelectCommit(_commit)).Returns(_commit); + pipelineHooks.Add(_hook2); + + _hook1Async = A.Fake(); + A.CallTo(() => _hook1Async.SelectCommitAsync(_commit, A.Ignored)).Returns(Task.FromResult(_commit)); + pipelineHooksAsync.Add(_hook1Async); + + _hook2Async = A.Fake(); + A.CallTo(() => _hook2Async.SelectCommitAsync(_commit, A.Ignored)).Returns(Task.FromResult(_commit)); + pipelineHooksAsync.Add(_hook2Async); + + A.CallTo(() => persistence.GetFrom(Bucket.Default, _commit.StreamId, 0, int.MaxValue)) + .Returns([_commit]); + } + + protected override void Because() + { + // ReSharper disable once ReturnValueOfPureMethodIsNotUsed + // Forces enumeration of commits. + Decorator.GetFrom(Bucket.Default, _commit!.StreamId, 0, int.MaxValue).ToList(); + } + + [Fact] + public void should_call_the_underlying_persistence_to_get_events() + { + A.CallTo(() => persistence.GetFrom(Bucket.Default, _commit!.StreamId, 0, int.MaxValue)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public void should_pass_all_events_through_the_pipeline_hooks() + { + A.CallTo(() => _hook1!.SelectCommit(_commit!)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _hook2!.SelectCommit(_commit!)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public void should_pass_all_events_through_the_async_pipeline_hooks() + { + A.CallTo(() => _hook1Async!.SelectCommitAsync(_commit!, A.Ignored)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _hook2Async!.SelectCommitAsync(_commit!, A.Ignored)).MustHaveHappenedOnceExactly(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_reading_all_events_in_a_bucket_from_date_to_date : using_underlying_persistence + { + private ICommit? _commit; + private DateTime _end; + private IPipelineHook? _hook1; + private IPipelineHook? _hook2; + private IPipelineHookAsync? _hook1Async; + private IPipelineHookAsync? _hook2Async; + private DateTime _start; + + protected override void Context() + { + _start = DateTime.Now; + _end = DateTime.Now; + _commit = new Commit(Bucket.Default, streamId, 1, Guid.NewGuid(), 1, DateTime.Now, 1, null, null); + + _hook1 = A.Fake(); + A.CallTo(() => _hook1.SelectCommit(_commit)).Returns(_commit); + pipelineHooks.Add(_hook1); + + _hook2 = A.Fake(); + A.CallTo(() => _hook2.SelectCommit(_commit)).Returns(_commit); + pipelineHooks.Add(_hook2); + + _hook1Async = A.Fake(); + A.CallTo(() => _hook1Async.SelectCommitAsync(_commit, A.Ignored)).Returns(Task.FromResult(_commit)); + pipelineHooksAsync.Add(_hook1Async); + + _hook2Async = A.Fake(); + A.CallTo(() => _hook2Async.SelectCommitAsync(_commit, A.Ignored)).Returns(Task.FromResult(_commit)); + pipelineHooksAsync.Add(_hook2Async); + + A.CallTo(() => persistence.GetFromTo(Bucket.Default, _start, _end)).Returns([_commit]); + } + + protected override void Because() + { + // ReSharper disable once ReturnValueOfPureMethodIsNotUsed + // Forces enumeration of commits + Decorator.GetFromTo(Bucket.Default, _start, _end).ToList(); + } + + [Fact] + public void should_call_the_underlying_persistence_to_get_events() + { + A.CallTo(() => persistence.GetFromTo(Bucket.Default, _start, _end)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public void should_pass_all_events_through_the_pipeline_hooks() + { + A.CallTo(() => _hook1!.SelectCommit(_commit!)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _hook2!.SelectCommit(_commit!)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public void should_pass_all_events_through_the_async_pipeline_hooks() + { + A.CallTo(() => _hook1Async!.SelectCommitAsync(_commit!, A.Ignored)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _hook2Async!.SelectCommitAsync(_commit!, A.Ignored)).MustHaveHappenedOnceExactly(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_reading_all_events_in_all_buckets_from_checkpoint_to_checkpoint : using_underlying_persistence + { + private ICommit? _commit; + private Int64 _end; + private IPipelineHook? _hook1; + private IPipelineHook? _hook2; + private IPipelineHookAsync? _hook1Async; + private IPipelineHookAsync? _hook2Async; + private Int64 _start; + + protected override void Context() + { + _start = 0; + _end = 1; + _commit = new Commit(Bucket.Default, streamId, 1, Guid.NewGuid(), 1, DateTime.Now, 1, null, null); + + _hook1 = A.Fake(); + A.CallTo(() => _hook1.SelectCommit(_commit)).Returns(_commit); + pipelineHooks.Add(_hook1); + + _hook2 = A.Fake(); + A.CallTo(() => _hook2.SelectCommit(_commit)).Returns(_commit); + pipelineHooks.Add(_hook2); + + _hook1Async = A.Fake(); + A.CallTo(() => _hook1Async.SelectCommitAsync(_commit, A.Ignored)).Returns(Task.FromResult(_commit)); + pipelineHooksAsync.Add(_hook1Async); + + _hook2Async = A.Fake(); + A.CallTo(() => _hook2Async.SelectCommitAsync(_commit, A.Ignored)).Returns(Task.FromResult(_commit)); + pipelineHooksAsync.Add(_hook2Async); + + A.CallTo(() => persistence.GetFromTo(_start, _end)).Returns([_commit]); + } + + protected override void Because() + { + // ReSharper disable once ReturnValueOfPureMethodIsNotUsed + // Forces enumeration of commits + Decorator.GetFromTo(_start, _end).ToList(); + } + + [Fact] + public void should_call_the_underlying_persistence_to_get_events() + { + A.CallTo(() => persistence.GetFromTo(_start, _end)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public void should_pass_all_events_through_the_pipeline_hooks() + { + A.CallTo(() => _hook1!.SelectCommit(_commit!)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _hook2!.SelectCommit(_commit!)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public void should_pass_all_events_through_the_async_pipeline_hooks() + { + A.CallTo(() => _hook1Async!.SelectCommitAsync(_commit!, A.Ignored)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _hook2Async!.SelectCommitAsync(_commit!, A.Ignored)).MustHaveHappenedOnceExactly(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_reading_all_events_in_a_bucket_from_checkpoint_to_checkpoint : using_underlying_persistence + { + private ICommit? _commit; + private Int64 _end; + private IPipelineHook? _hook1; + private IPipelineHook? _hook2; + private IPipelineHookAsync? _hook1Async; + private IPipelineHookAsync? _hook2Async; + private Int64 _start; + + protected override void Context() + { + _start = 0; + _end = 1; + _commit = new Commit(Bucket.Default, streamId, 1, Guid.NewGuid(), 1, DateTime.Now, 1, null, null); + + _hook1 = A.Fake(); + A.CallTo(() => _hook1.SelectCommit(_commit)).Returns(_commit); + pipelineHooks.Add(_hook1); + + _hook2 = A.Fake(); + A.CallTo(() => _hook2.SelectCommit(_commit)).Returns(_commit); + pipelineHooks.Add(_hook2); + + _hook1Async = A.Fake(); + A.CallTo(() => _hook1Async.SelectCommitAsync(_commit, A.Ignored)).Returns(Task.FromResult(_commit)); + pipelineHooksAsync.Add(_hook1Async); + + _hook2Async = A.Fake(); + A.CallTo(() => _hook2Async.SelectCommitAsync(_commit, A.Ignored)).Returns(Task.FromResult(_commit)); + pipelineHooksAsync.Add(_hook2Async); + + A.CallTo(() => persistence.GetFromTo(Bucket.Default, _start, _end)).Returns([_commit]); + } + + protected override void Because() + { + // ReSharper disable once ReturnValueOfPureMethodIsNotUsed + // Forces enumeration of commits + Decorator.GetFromTo(Bucket.Default, _start, _end).ToList(); + } + + [Fact] + public void should_call_the_underlying_persistence_to_get_events() + { + A.CallTo(() => persistence.GetFromTo(Bucket.Default, _start, _end)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public void should_pass_all_events_through_the_pipeline_hooks() + { + A.CallTo(() => _hook1!.SelectCommit(_commit!)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _hook2!.SelectCommit(_commit!)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public void should_pass_all_events_through_the_async_pipeline_hooks() + { + A.CallTo(() => _hook1Async!.SelectCommitAsync(_commit!, A.Ignored)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _hook2Async!.SelectCommitAsync(_commit!, A.Ignored)).MustHaveHappenedOnceExactly(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_committing : using_underlying_persistence + { + private CommitAttempt? _attempt; + + protected override void Context() + { + _attempt = new CommitAttempt(streamId, 1, Guid.NewGuid(), 1, DateTime.Now, null, [new EventMessage()]); + } + + protected override void Because() + { + Decorator.Commit(_attempt!); + } + + [Fact] + public void should_dispose_the_underlying_persistence() + { + A.CallTo(() => persistence.Commit(_attempt!)).MustHaveHappenedOnceExactly(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_reading_the_all_events_from_checkpoint : using_underlying_persistence + { + private ICommit? _commit; + private IPipelineHook? _hook1; + private IPipelineHook? _hook2; + private IPipelineHookAsync? _hook1Async; + private IPipelineHookAsync? _hook2Async; + + protected override void Context() + { + _commit = new Commit(Bucket.Default, streamId, 1, Guid.NewGuid(), 1, DateTime.Now, 1, null, null); + + _hook1 = A.Fake(); + A.CallTo(() => _hook1.SelectCommit(_commit)).Returns(_commit); + pipelineHooks.Add(_hook1); + + _hook2 = A.Fake(); + A.CallTo(() => _hook2.SelectCommit(_commit)).Returns(_commit); + pipelineHooks.Add(_hook2); + + _hook1Async = A.Fake(); + A.CallTo(() => _hook1Async.SelectCommitAsync(_commit, A.Ignored)).Returns(Task.FromResult(_commit)); + pipelineHooksAsync.Add(_hook1Async); + + _hook2Async = A.Fake(); + A.CallTo(() => _hook2Async.SelectCommitAsync(_commit, A.Ignored)).Returns(Task.FromResult(_commit)); + pipelineHooksAsync.Add(_hook2Async); + + A.CallTo(() => persistence.GetFrom(0)).Returns([_commit]); + } + + protected override void Because() + { + Decorator.GetFrom(0).ToList(); + } + + [Fact] + public void should_call_the_underlying_persistence_to_get_events() + { + A.CallTo(() => persistence.GetFrom(0)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public void should_pass_all_events_through_the_pipeline_hooks() + { + A.CallTo(() => _hook1!.SelectCommit(_commit!)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _hook2!.SelectCommit(_commit!)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public void should_pass_all_events_through_the_async_pipeline_hooks() + { + A.CallTo(() => _hook1Async!.SelectCommitAsync(_commit!, A.Ignored)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _hook2Async!.SelectCommitAsync(_commit!, A.Ignored)).MustHaveHappenedOnceExactly(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_reading_the_all_events_in_a_bucket_from_checkpoint : using_underlying_persistence + { + private ICommit? _commit; + private IPipelineHook? _hook1; + private IPipelineHook? _hook2; + private IPipelineHookAsync? _hook1Async; + private IPipelineHookAsync? _hook2Async; + + protected override void Context() + { + _commit = new Commit(Bucket.Default, streamId, 1, Guid.NewGuid(), 1, DateTime.Now, 1, null, null); + + _hook1 = A.Fake(); + A.CallTo(() => _hook1.SelectCommit(_commit)).Returns(_commit); + pipelineHooks.Add(_hook1); + + _hook2 = A.Fake(); + A.CallTo(() => _hook2.SelectCommit(_commit)).Returns(_commit); + pipelineHooks.Add(_hook2); + + _hook1Async = A.Fake(); + A.CallTo(() => _hook1Async.SelectCommitAsync(_commit, A.Ignored)).Returns(Task.FromResult(_commit)); + pipelineHooksAsync.Add(_hook1Async); + + _hook2Async = A.Fake(); + A.CallTo(() => _hook2Async.SelectCommitAsync(_commit, A.Ignored)).Returns(Task.FromResult(_commit)); + pipelineHooksAsync.Add(_hook2Async); + + A.CallTo(() => persistence.GetFrom(Bucket.Default, 0)).Returns([_commit]); + } + + protected override void Because() + { + Decorator.GetFrom(Bucket.Default, 0).ToList(); + } + + [Fact] + public void should_call_the_underlying_persistence_to_get_events() + { + A.CallTo(() => persistence.GetFrom(Bucket.Default, 0)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public void should_pass_all_events_through_the_pipeline_hooks() + { + A.CallTo(() => _hook1!.SelectCommit(_commit!)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _hook2!.SelectCommit(_commit!)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public void should_pass_all_events_through_the_async_pipeline_hooks() + { + A.CallTo(() => _hook1Async!.SelectCommitAsync(_commit!, A.Ignored)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _hook2Async!.SelectCommitAsync(_commit!, A.Ignored)).MustHaveHappenedOnceExactly(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_purging : using_underlying_persistence + { + private IPipelineHook? _hook; + private IPipelineHookAsync? _hookAsync; + + protected override void Context() + { + _hook = A.Fake(); + pipelineHooks.Add(_hook); + + _hookAsync = A.Fake(); + pipelineHooksAsync.Add(_hookAsync); + } + + protected override void Because() + { + Decorator.Purge(); + } + + [Fact] + public void should_call_the_pipeline_hook_purge() + { + A.CallTo(() => _hook!.OnPurge(null)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public void should_call_the_async_pipeline_hook_purge() + { + A.CallTo(() => _hookAsync!.OnPurgeAsync(null, A.Ignored)).MustHaveHappenedOnceExactly(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_purging_a_bucket : using_underlying_persistence + { + private IPipelineHook? _hook; + private IPipelineHookAsync? _hookAsync; + private const string _bucketId = "Bucket"; + + protected override void Context() + { + _hook = A.Fake(); + pipelineHooks.Add(_hook); + + _hookAsync = A.Fake(); + pipelineHooksAsync.Add(_hookAsync); + } + + protected override void Because() + { + Decorator.Purge(_bucketId); + } + + [Fact] + public void should_call_the_pipeline_hook_purge() + { + A.CallTo(() => _hook!.OnPurge(_bucketId)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public void should_call_the_async_pipeline_hook_purge() + { + A.CallTo(() => _hookAsync!.OnPurgeAsync(_bucketId, A.Ignored)).MustHaveHappenedOnceExactly(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_deleting_a_stream : using_underlying_persistence + { + private IPipelineHook? _hook; + private IPipelineHookAsync? _hookAsync; + private const string _bucketId = "Bucket"; + private const string _streamId = "Stream"; + + protected override void Context() + { + _hook = A.Fake(); + pipelineHooks.Add(_hook); + + _hookAsync = A.Fake(); + pipelineHooksAsync.Add(_hookAsync); + } + + protected override void Because() + { + Decorator.DeleteStream(_bucketId, _streamId); + } + + [Fact] + public void should_call_the_pipeline_hook_purge() + { + A.CallTo(() => _hook!.OnDeleteStream(_bucketId, _streamId)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public void should_call_the_async_pipeline_hook_purge() + { + A.CallTo(() => _hookAsync!.OnDeleteStreamAsync(_bucketId, _streamId, A.Ignored)).MustHaveHappenedOnceExactly(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_reading_the_all_events_from_checkpoint_with_filtering_PipelineHook_Sync : using_underlying_persistence + { + private ICommit? _commit; + private IPipelineHook? _hook1; + private IPipelineHook? _hook2; + private IPipelineHookAsync? _hook1Async; + private IPipelineHookAsync? _hook2Async; + private List? _commits; + + protected override void Context() + { + _commit = new Commit(Bucket.Default, streamId, 1, Guid.NewGuid(), 1, DateTime.Now, 1, null, null); + + _hook1 = A.Fake(); + A.CallTo(() => _hook1.SelectCommit(_commit)).Returns(null); + pipelineHooks.Add(_hook1); + + _hook2 = A.Fake(); + A.CallTo(() => _hook2.SelectCommit(_commit)).Returns(_commit); + pipelineHooks.Add(_hook2); + + _hook1Async = A.Fake(); + A.CallTo(() => _hook1Async.SelectCommitAsync(_commit, A.Ignored)).Returns(Task.FromResult(_commit)); + pipelineHooksAsync.Add(_hook1Async); + + _hook2Async = A.Fake(); + A.CallTo(() => _hook2Async.SelectCommitAsync(_commit, A.Ignored)).Returns(Task.FromResult(_commit)); + pipelineHooksAsync.Add(_hook2Async); + + A.CallTo(() => persistence.GetFrom(0)).Returns([_commit]); + } + + protected override void Because() + { + _commits = Decorator.GetFrom(0).ToList(); + } + + [Fact] + public void should_call_the_underlying_persistence_to_get_events() + { + A.CallTo(() => persistence.GetFrom(0)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public void should_pass_all_events_through_the_first_pipeline_hooks() + { + A.CallTo(() => _hook1!.SelectCommit(_commit!)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public void should_not_pass_the_events_through_the_second_pipeline_hooks() + { + A.CallTo(() => _hook2!.SelectCommit(_commit!)).MustNotHaveHappened(); + } + + [Fact] + public void should_not_pass_the_events_through_the_first_async_pipeline_hooks() + { + A.CallTo(() => _hook1Async!.SelectCommitAsync(_commit!, A.Ignored)).MustNotHaveHappened(); + } + + [Fact] + public void should_not_pass_the_events_through_the_second_async_pipeline_hooks() + { + A.CallTo(() => _hook2Async!.SelectCommitAsync(_commit!, A.Ignored)).MustNotHaveHappened(); + } + + [Fact] + public void commit_list_should_be_empty() + { + _commits.Should().BeEmpty(); + } + } + +#if MSTEST + [TestClass] +#endif + public class when_reading_the_all_events_from_checkpoint_with_filtering_PipelineHook_Async : using_underlying_persistence + { + private ICommit? _commit; + private IPipelineHook? _hook1; + private IPipelineHook? _hook2; + private IPipelineHookAsync? _hook1Async; + private IPipelineHookAsync? _hook2Async; + private List? _commits; + + protected override void Context() + { + _commit = new Commit(Bucket.Default, streamId, 1, Guid.NewGuid(), 1, DateTime.Now, 1, null, null); + + _hook1 = A.Fake(); + A.CallTo(() => _hook1.SelectCommit(_commit)).Returns(_commit); + pipelineHooks.Add(_hook1); + + _hook2 = A.Fake(); + A.CallTo(() => _hook2.SelectCommit(_commit)).Returns(_commit); + pipelineHooks.Add(_hook2); + + _hook1Async = A.Fake(); + A.CallTo(() => _hook1Async.SelectCommitAsync(_commit, A.Ignored)).Returns(Task.FromResult(null)); + pipelineHooksAsync.Add(_hook1Async); + + _hook2Async = A.Fake(); + A.CallTo(() => _hook2Async.SelectCommitAsync(_commit, A.Ignored)).Returns(Task.FromResult(_commit)); + pipelineHooksAsync.Add(_hook2Async); + + A.CallTo(() => persistence.GetFrom(0)).Returns([_commit]); + } + + protected override void Because() + { + _commits = Decorator.GetFrom(0).ToList(); + } + + [Fact] + public void should_call_the_underlying_persistence_to_get_events() + { + A.CallTo(() => persistence.GetFrom(0)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public void should_pass_all_events_through_the_first_pipeline_hooks() + { + A.CallTo(() => _hook1!.SelectCommit(_commit!)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public void should_pass_the_events_through_the_second_pipeline_hooks() + { + A.CallTo(() => _hook2!.SelectCommit(_commit!)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public void should_pass_the_events_through_the_first_async_pipeline_hooks() + { + A.CallTo(() => _hook1Async!.SelectCommitAsync(_commit!, A.Ignored)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public void should_not_pass_the_events_through_the_second_async_pipeline_hooks() + { + A.CallTo(() => _hook2Async!.SelectCommitAsync(_commit!, A.Ignored)).MustNotHaveHappened(); + } + + [Fact] + public void commit_list_should_be_empty() + { + _commits.Should().BeEmpty(); + } + } + + public abstract class using_underlying_persistence : SpecificationBase + { + private PipelineHooksAwarePersistStreamsDecorator? decorator; + protected readonly IPersistStreams persistence = A.Fake(); + protected readonly List pipelineHooks = []; + protected readonly List pipelineHooksAsync = []; + protected readonly string streamId = Guid.NewGuid().ToString(); + + public PipelineHooksAwarePersistStreamsDecorator Decorator + { + get { return decorator ??= new PipelineHooksAwarePersistStreamsDecorator(persistence, pipelineHooks.Select(x => x), pipelineHooksAsync.Select(x => x)); } + set { decorator = value; } + } + } +} + +#pragma warning restore IDE1006 // Naming Styles +// ReSharper enable InconsistentNaming +#pragma warning restore 169 \ No newline at end of file diff --git a/src/NEventStore.Tests/Properties/AssemblyInfo.cs b/src/NEventStore.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..cee49ba8b --- /dev/null +++ b/src/NEventStore.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,37 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("NEventStore.Tests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("NEventStore")] +[assembly: AssemblyCopyright("Copyright © 2013")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. + +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM + +[assembly: Guid("ce2d3ce5-5061-43e0-8b5b-6e14c8ab0e7b")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +//[assembly: AssemblyVersion("8.0.0.0")] +//[assembly: AssemblyFileVersion("8.0.0.0")] +//[assembly: AssemblyInformationalVersion("8.0.0-beta.5+Branch.release-8.0.0.Sha.4029904d0dae687763bdf2f11ad4c7754927a8c6")] diff --git a/src/NEventStore.Tests/TestableWireup.cs b/src/NEventStore.Tests/TestableWireup.cs new file mode 100644 index 000000000..7c8305b24 --- /dev/null +++ b/src/NEventStore.Tests/TestableWireup.cs @@ -0,0 +1,14 @@ +namespace NEventStore.Tests +{ + public class TestableWireup : Wireup + { + public TestableWireup(Wireup inner) : base(inner) + { + } + + public new NanoContainer Container + { + get { return base.Container; } + } + } +} diff --git a/src/NEventStore.Tests/TestableWireupExtensions.cs b/src/NEventStore.Tests/TestableWireupExtensions.cs new file mode 100644 index 000000000..9dd623511 --- /dev/null +++ b/src/NEventStore.Tests/TestableWireupExtensions.cs @@ -0,0 +1,10 @@ +namespace NEventStore.Tests +{ + public static class TestableWireupExtensions + { + public static TestableWireup UseTestableWireup(this Wireup wireup) + { + return new TestableWireup(wireup); + } + } +} diff --git a/src/NEventStore/AccessSnapshotsExtensions.cs b/src/NEventStore/AccessSnapshotsExtensions.cs new file mode 100644 index 000000000..1016e9379 --- /dev/null +++ b/src/NEventStore/AccessSnapshotsExtensions.cs @@ -0,0 +1,126 @@ +using NEventStore.Persistence; + +namespace NEventStore +{ + /// + /// Provides extension methods for and . + /// + public static class AccessSnapshotsExtensions + { + /// + /// Gets the most recent snapshot which was taken on or before the revision indicated from the default bucket. + /// + /// The instance. + /// The stream to be searched for a snapshot. + /// The maximum revision possible for the desired snapshot. + /// If found, it returns the snapshot; otherwise null is returned. + /// + /// + public static ISnapshot? GetSnapshot(this IAccessSnapshots accessSnapshots, Guid streamId, int maxRevision) + { + return GetSnapshot(accessSnapshots, streamId.ToString(), maxRevision); + } + + /// + /// Gets the most recent snapshot which was taken on or before the revision indicated from the default bucket. + /// + /// The instance. + /// The stream to be searched for a snapshot. + /// The maximum revision possible for the desired snapshot. + /// If found, it returns the snapshot; otherwise null is returned. + /// + /// + public static ISnapshot? GetSnapshot(this IAccessSnapshots accessSnapshots, string streamId, int maxRevision) + { + return accessSnapshots.GetSnapshot(Bucket.Default, streamId, maxRevision); + } + + /// + /// Gets the most recent snapshot which was taken on or before the revision indicated. + /// + /// The instance. + /// The value which uniquely identifies bucket the stream belongs to. + /// The stream to be searched for a snapshot. + /// The maximum revision possible for the desired snapshot. + /// If found, it returns the snapshot; otherwise null is returned. + /// + /// + public static ISnapshot? GetSnapshot(this IAccessSnapshots accessSnapshots, string bucketId, Guid streamId, int maxRevision) + { + return accessSnapshots.GetSnapshot(bucketId, streamId.ToString(), maxRevision); + } + + /// + /// Gets identifiers for all streams whose head and last snapshot revisions differ by at least the threshold specified for the default bucket. + /// + /// The instance. + /// The maximum difference between the head and most recent snapshot revisions. + /// The streams for which the head and snapshot revisions differ by at least the threshold specified. + /// + /// + public static IEnumerable GetStreamsToSnapshot(this IAccessSnapshots accessSnapshots, int maxThreshold) + { + return accessSnapshots.GetStreamsToSnapshot(Bucket.Default, maxThreshold); + } + + /// + /// Gets the most recent snapshot which was taken on or before the revision indicated from the default bucket. + /// + /// The instance. + /// The stream to be searched for a snapshot. + /// The maximum revision possible for the desired snapshot. + /// The token to monitor for cancellation requests. + /// If found, it returns the snapshot; otherwise null is returned. + /// + /// + public static Task GetSnapshotAsync(this IAccessSnapshotsAsync accessSnapshots, Guid streamId, int maxRevision, CancellationToken cancellationToken = default) + { + return GetSnapshotAsync(accessSnapshots, streamId.ToString(), maxRevision, cancellationToken); + } + + /// + /// Gets the most recent snapshot which was taken on or before the revision indicated from the default bucket. + /// + /// The instance. + /// The stream to be searched for a snapshot. + /// The maximum revision possible for the desired snapshot. + /// The token to monitor for cancellation requests. + /// If found, it returns the snapshot; otherwise null is returned. + /// + /// + public static Task GetSnapshotAsync(this IAccessSnapshotsAsync accessSnapshots, string streamId, int maxRevision, CancellationToken cancellationToken = default) + { + return accessSnapshots.GetSnapshotAsync(Bucket.Default, streamId, maxRevision, cancellationToken); + } + + /// + /// Gets the most recent snapshot which was taken on or before the revision indicated. + /// + /// The instance. + /// The value which uniquely identifies bucket the stream belongs to. + /// The stream to be searched for a snapshot. + /// The maximum revision possible for the desired snapshot. + /// The token to monitor for cancellation requests. + /// If found, it returns the snapshot; otherwise null is returned. + /// + /// + public static Task GetSnapshotAsync(this IAccessSnapshotsAsync accessSnapshots, string bucketId, Guid streamId, int maxRevision, CancellationToken cancellationToken = default) + { + return accessSnapshots.GetSnapshotAsync(bucketId, streamId.ToString(), maxRevision, cancellationToken); + } + + /// + /// Gets identifiers for all streams whose head and last snapshot revisions differ by at least the threshold specified for the default bucket. + /// + /// The instance. + /// The maximum difference between the head and most recent snapshot revisions. + /// The observer to receive the streams. + /// The token to monitor for cancellation requests. + /// + /// + public static Task GetStreamsToSnapshotAsync(this IAccessSnapshotsAsync accessSnapshots, int maxThreshold, IAsyncObserver asyncObserver, CancellationToken cancellationToken = default) + { + return accessSnapshots.GetStreamsToSnapshotAsync(Bucket.Default, maxThreshold, asyncObserver, cancellationToken); + } + } +} \ No newline at end of file diff --git a/src/NEventStore/Bucket.cs b/src/NEventStore/Bucket.cs new file mode 100644 index 000000000..7ddcc95c8 --- /dev/null +++ b/src/NEventStore/Bucket.cs @@ -0,0 +1,13 @@ +namespace NEventStore +{ + /// + /// Provides a way to identify a bucket. + /// + public static class Bucket + { + /// + /// The default bucket Id. + /// + public const string Default = "default"; + } +} \ No newline at end of file diff --git a/src/NEventStore/CommitAttempt.cs b/src/NEventStore/CommitAttempt.cs new file mode 100644 index 000000000..c59c25d10 --- /dev/null +++ b/src/NEventStore/CommitAttempt.cs @@ -0,0 +1,136 @@ +#pragma warning disable RCS1170 // Use read-only auto-implemented property. + +namespace NEventStore +{ + /// + /// Commit Attempt represents a single attempt to commit a set of events to a stream. + /// + public class CommitAttempt + { + /// + /// Initializes a new instance of the Commit class for the default bucket. + /// + /// The value which uniquely identifies the stream in a bucket to which the commit belongs. + /// The value which indicates the revision of the most recent event in the stream to which this commit applies. + /// The value which uniquely identifies the commit within the stream. + /// The value which indicates the sequence (or position) in the stream to which this commit applies. + /// The point in time at which the commit was persisted. + /// The metadata which provides additional, unstructured information about this commit. + /// The collection of event messages to be committed as a single unit. + public CommitAttempt( + Guid streamId, + int streamRevision, + Guid commitId, + int commitSequence, + DateTime commitStamp, + IDictionary? headers, + ICollection events) + : this(Bucket.Default, streamId.ToString(), streamRevision, commitId, commitSequence, commitStamp, headers, events) + { } + + /// + /// Initializes a new instance of the Commit class for the default bucket. + /// + /// The value which uniquely identifies the stream in a bucket to which the commit belongs. + /// The value which indicates the revision of the most recent event in the stream to which this commit applies. + /// The value which uniquely identifies the commit within the stream. + /// The value which indicates the sequence (or position) in the stream to which this commit applies. + /// The point in time at which the commit was persisted. + /// The metadata which provides additional, unstructured information about this commit. + /// The collection of event messages to be committed as a single unit. + public CommitAttempt( + string streamId, + int streamRevision, + Guid commitId, + int commitSequence, + DateTime commitStamp, + IDictionary? headers, + ICollection events) + : this(Bucket.Default, streamId, streamRevision, commitId, commitSequence, commitStamp, headers, events) + { } + + /// + /// Initializes a new instance of the Commit class. + /// + /// The value which identifies bucket to which the stream and the commit belongs + /// The value which uniquely identifies the stream in a bucket to which the commit belongs. + /// The value which indicates the revision of the most recent event in the stream to which this commit applies. + /// The value which uniquely identifies the commit within the stream. + /// The value which indicates the sequence (or position) in the stream to which this commit applies. + /// The point in time at which the commit was persisted. + /// The metadata which provides additional, unstructured information about this commit. + /// The collection of event messages to be committed as a single unit. + public CommitAttempt( + string bucketId, + string streamId, + int streamRevision, + Guid commitId, + int commitSequence, + DateTime commitStamp, + IDictionary? headers, + ICollection events) + { + Guard.NotNullOrWhiteSpace(() => bucketId, bucketId); + Guard.NotNullOrWhiteSpace(() => streamId, streamId); + Guard.NotLessThanOrEqualTo(() => streamRevision, streamRevision, 0); + Guard.NotDefault(() => commitId, commitId); + Guard.NotLessThanOrEqualTo(() => commitSequence, commitSequence, 0); + Guard.NotLessThan(() => commitSequence, streamRevision, 0); + Guard.NotEmpty(() => events, events); + + BucketId = bucketId; + StreamId = streamId; + StreamRevision = streamRevision; + CommitId = commitId; + CommitSequence = commitSequence; + CommitStamp = commitStamp; + Headers = headers ?? new Dictionary(); + Events = events; + //Events = events == null ? + // new ReadOnlyCollection(new List()) : + // new ReadOnlyCollection(events.ToList()); + } + + /// + /// Gets the value which identifies bucket to which the stream and the commit belongs. + /// + public string BucketId { get; private set; } + + /// + /// Gets the value which uniquely identifies the stream to which the commit belongs. + /// + public string StreamId { get; private set; } + + /// + /// Gets the value which indicates the revision of the most recent event in the stream to which this commit applies. + /// + public int StreamRevision { get; private set; } + + /// + /// Gets the value which uniquely identifies the commit within the stream. + /// + public Guid CommitId { get; private set; } + + /// + /// Gets the value which indicates the sequence (or position) in the stream to which this commit applies. + /// + public int CommitSequence { get; private set; } + + /// + /// Gets the point in time at which the commit was persisted. + /// + public DateTime CommitStamp { get; private set; } + + /// + /// Gets the metadata which provides additional, unstructured information about this commit. + /// + public IDictionary Headers { get; private set; } + + /// + /// Gets the collection of event messages to be committed as a single unit. + /// + public ICollection Events { get; private set; } + } +} + +#pragma warning restore RCS1170 // Use read-only auto-implemented property. \ No newline at end of file diff --git a/src/NEventStore/CommitEqualityComparer.cs b/src/NEventStore/CommitEqualityComparer.cs new file mode 100644 index 000000000..b402e9149 --- /dev/null +++ b/src/NEventStore/CommitEqualityComparer.cs @@ -0,0 +1,43 @@ +namespace NEventStore +{ + /// + /// Compares two commits for equality based on their bucket identity, stream identity, and commit identity. + /// + public sealed class CommitEqualityComparer : IEqualityComparer + { + /// + public bool Equals(ICommit x, ICommit y) + { + if (ReferenceEquals(x, y)) + { + return true; + } + if (x is null) + { + return false; + } + if (y is null) + { + return false; + } + if (x.GetType() != y.GetType()) + { + return false; + } + return string.Equals(x.BucketId, y.BucketId, StringComparison.Ordinal) + && string.Equals(x.StreamId, y.StreamId, StringComparison.Ordinal) + && string.Equals(x.CommitId, y.CommitId); + } + + /// + public int GetHashCode(ICommit obj) + { + unchecked + { + int hashCode = obj.BucketId?.GetHashCode() ?? 0; + hashCode = (hashCode * 397) ^ (obj.StreamId?.GetHashCode() ?? 0); + return (hashCode * 397) ^ obj.CommitId.GetHashCode(); + } + } + } +} \ No newline at end of file diff --git a/src/NEventStore/CommitEventsExtensions.cs b/src/NEventStore/CommitEventsExtensions.cs new file mode 100644 index 000000000..f10af7a8c --- /dev/null +++ b/src/NEventStore/CommitEventsExtensions.cs @@ -0,0 +1,43 @@ +using NEventStore.Persistence; + +namespace NEventStore +{ + /// + /// Provides extension methods for and instances. + /// + public static class CommitEventsExtensions + { + /// + /// Gets the corresponding commits from the stream indicated starting at the revision specified until the + /// end of the stream sorted in ascending order--from oldest to newest from the default bucket. + /// + /// The instance. + /// The stream from which the events will be read. + /// The minimum revision of the stream to be read. + /// The maximum revision of the stream to be read. + /// A series of committed events from the stream specified sorted in ascending order. + /// + /// + public static IEnumerable GetFrom(this ICommitEvents commitEvents, string streamId, int minRevision, int maxRevision) + { + return commitEvents.GetFrom(Bucket.Default, streamId, minRevision, maxRevision); + } + + /// + /// Gets the corresponding commits from the stream indicated starting at the revision specified until the + /// end of the stream sorted in ascending order--from oldest to newest from the default bucket. + /// + /// The instance. + /// The stream from which the events will be read. + /// The minimum revision of the stream to be read. + /// The maximum revision of the stream to be read. + /// Observer to receive the commits. + /// The token to monitor for cancellation requests. + /// + /// + public static Task GetFromAsync(this ICommitEventsAsync commitEvents, string streamId, int minRevision, int maxRevision, IAsyncObserver observer, CancellationToken cancellationToken = default) + { + return commitEvents.GetFromAsync(Bucket.Default, streamId, minRevision, maxRevision, observer, cancellationToken); + } + } +} \ No newline at end of file diff --git a/src/NEventStore/CommitStreamObserver.cs b/src/NEventStore/CommitStreamObserver.cs new file mode 100644 index 000000000..d91026f16 --- /dev/null +++ b/src/NEventStore/CommitStreamObserver.cs @@ -0,0 +1,53 @@ +using System.Runtime.ExceptionServices; + +namespace NEventStore +{ + /// + /// Represents an async observer that can receive and stores commits from a stream. + /// Can be used as base class for other observers. + /// + public class CommitStreamObserver : IAsyncObserver + { + /// + /// The list of commits read from the stream. + /// + public IList Commits { get; } = []; + + /// + /// Indicates if the read operation has completed. + /// + public bool ReadCompleted { get; private set; } + + /// + /// Store the commits received from the stream in the collection. + /// + public virtual Task OnNextAsync(ICommit value, CancellationToken cancellationToken) + { + Commits.Add(value); + return Task.FromResult(true); + } + + /// + /// Notifies the observer that the provider has experienced an error condition. + /// + /// Preserve the stack trace and rethrow the exception that occurred while reading commits from the stream. + /// + /// + /// Override this method to log and handle the error. + /// + /// + public virtual Task OnErrorAsync(Exception ex, CancellationToken cancellationToken) + { + // Preserve the stack trace and rethrow the exception + ExceptionDispatchInfo.Capture(ex).Throw(); + return Task.CompletedTask; + } + + /// + public virtual Task OnCompletedAsync(CancellationToken cancellationToken) + { + ReadCompleted = true; + return Task.CompletedTask; + } + } +} \ No newline at end of file diff --git a/src/NEventStore/ConcurrencyException.cs b/src/NEventStore/ConcurrencyException.cs new file mode 100644 index 000000000..247e38b41 --- /dev/null +++ b/src/NEventStore/ConcurrencyException.cs @@ -0,0 +1,44 @@ +namespace NEventStore +{ + using System; + using System.Runtime.Serialization; + + /// + /// Represents an optimistic concurrency conflict between multiple writers. + /// + [Serializable] + public class ConcurrencyException : Exception + { + /// + /// Initializes a new instance of the ConcurrencyException class. + /// + public ConcurrencyException() + { } + + /// + /// Initializes a new instance of the ConcurrencyException class. + /// + /// The message that describes the error. + public ConcurrencyException(string message) + : base(message) + { } + + /// + /// Initializes a new instance of the ConcurrencyException class. + /// + /// The message that describes the error. + /// The message that is the cause of the current exception. + public ConcurrencyException(string message, Exception innerException) + : base(message, innerException) + { } + + /// + /// Initializes a new instance of the ConcurrencyException class. + /// + /// The SerializationInfo that holds the serialized object data of the exception being thrown. + /// The StreamingContext that contains contextual information about the source or destination. + protected ConcurrencyException(SerializationInfo info, StreamingContext context) + : base(info, context) + {} + } +} \ No newline at end of file diff --git a/src/NEventStore/Conversion/EventUpconverterPipelineHook.cs b/src/NEventStore/Conversion/EventUpconverterPipelineHook.cs new file mode 100644 index 000000000..016be79d1 --- /dev/null +++ b/src/NEventStore/Conversion/EventUpconverterPipelineHook.cs @@ -0,0 +1,80 @@ +using Microsoft.Extensions.Logging; +using NEventStore.Logging; +using NEventStore.Persistence; + +namespace NEventStore.Conversion +{ + /// + /// Represents a pipeline hook that upconverts events. + /// + public class EventUpconverterPipelineHook : PipelineHookBase + { + private static readonly ILogger Logger = LogFactory.BuildLogger(typeof(EventUpconverterPipelineHook)); + private readonly IDictionary> _converters; + + /// + /// Initializes a new instance of the EventUpconverterPipelineHook class. + /// + /// + public EventUpconverterPipelineHook(IDictionary> converters) + { + _converters = converters ?? throw new ArgumentNullException(nameof(converters)); + } + + /// + public override ICommit? SelectCommit(ICommit committed) + { + bool converted = false; + var eventMessages = committed + .Events + .Select(eventMessage => + { + object convert = Convert(eventMessage.Body); + if (ReferenceEquals(convert, eventMessage.Body)) + { + return eventMessage; + } + converted = true; + return new EventMessage { Headers = eventMessage.Headers, Body = convert }; + }) + .ToArray(); + if (!converted) + { + return committed; + } + return new Commit(committed.BucketId, + committed.StreamId, + committed.StreamRevision, + committed.CommitId, + committed.CommitSequence, + committed.CommitStamp, + committed.CheckpointToken, + committed.Headers, + eventMessages); + } + + /// + protected override void Dispose(bool disposing) + { + _converters.Clear(); + base.Dispose(disposing); + } + + private object Convert(object source) + { + Type sourceType = source.GetType(); + if (!_converters.TryGetValue(sourceType, out Func converter)) + { + return source; + } + + object target = converter(source); + if (Logger.IsEnabled(LogLevel.Debug)) + { + Logger.LogDebug(Resources.ConvertingEvent, sourceType, target.GetType()); + } + + return Convert(target); + } + } +} \ No newline at end of file diff --git a/src/NEventStore/Conversion/IUpconvertEvents.cs b/src/NEventStore/Conversion/IUpconvertEvents.cs new file mode 100644 index 000000000..8b4323cb5 --- /dev/null +++ b/src/NEventStore/Conversion/IUpconvertEvents.cs @@ -0,0 +1,19 @@ +namespace NEventStore.Conversion +{ + /// + /// Provides the ability to upconvert an event from one type to another. + /// + /// The source event type from which to convert. + /// The target event type. + public interface IUpconvertEvents + where TSource : class + where TTarget : class + { + /// + /// Converts an event from one type to another. + /// + /// The event to be converted. + /// The converted event. + TTarget Convert(TSource sourceEvent); + } +} \ No newline at end of file diff --git a/src/NEventStore/Conversion/MultipleConvertersFoundException.cs b/src/NEventStore/Conversion/MultipleConvertersFoundException.cs new file mode 100644 index 000000000..93b67cb67 --- /dev/null +++ b/src/NEventStore/Conversion/MultipleConvertersFoundException.cs @@ -0,0 +1,44 @@ +namespace NEventStore.Conversion +{ + using System; + using System.Runtime.Serialization; + + /// + /// Represents the failure that occurs when there are two or more event converters created for the same source type. + /// + [Serializable] + public class MultipleConvertersFoundException : Exception + { + /// + /// Initializes a new instance of the MultipleConvertersFoundException class. + /// + public MultipleConvertersFoundException() + { } + + /// + /// Initializes a new instance of the MultipleConvertersFoundException class. + /// + /// The message that describes the error. + public MultipleConvertersFoundException(string message) + : base(message) + { } + + /// + /// Initializes a new instance of the MultipleConvertersFoundException class. + /// + /// The message that describes the error. + /// The exception that is the cause of the current exception. + public MultipleConvertersFoundException(string message, Exception innerException) + : base(message, innerException) + { } + + /// + /// Initializes a new instance of the MultipleConvertersFoundException class. + /// + /// The serialization info. + /// The streaming context. + protected MultipleConvertersFoundException(SerializationInfo info, StreamingContext context) + : base(info, context) + { } + } +} \ No newline at end of file diff --git a/src/NEventStore/Diagnostics/PerformanceCounterPersistenceEngine.cs b/src/NEventStore/Diagnostics/PerformanceCounterPersistenceEngine.cs new file mode 100644 index 000000000..cfc010d50 --- /dev/null +++ b/src/NEventStore/Diagnostics/PerformanceCounterPersistenceEngine.cs @@ -0,0 +1,261 @@ +using System.Diagnostics; +using NEventStore.Persistence; + +namespace NEventStore.Diagnostics +{ + // PerformanceCounters are not cross platform + +#if NET462 + + /// + /// Decorate an IPersistStreams implementation with performance counters. + /// + public class PerformanceCounterPersistenceEngine : IPersistStreams + { + private readonly PerformanceCounters _counters; + private readonly IPersistStreams _persistence; + + /// + /// Initializes a new instance of the PerformanceCounterPersistenceEngine class. + /// + public PerformanceCounterPersistenceEngine(IPersistStreams persistence, string instanceName) + { + _persistence = persistence; + _counters = new PerformanceCounters(instanceName); + } + + /// + public void Initialize() + { + _persistence.Initialize(); + } + + /// + public IEnumerable GetFrom(string bucketId, string streamId, int minRevision, int maxRevision) + { + return _persistence.GetFrom(bucketId, streamId, minRevision, maxRevision); + } + + /// + public ICommit? Commit(CommitAttempt attempt) + { + Stopwatch clock = Stopwatch.StartNew(); + var commit = _persistence.Commit(attempt); + clock.Stop(); + _counters.CountCommit(attempt.Events.Count, clock.ElapsedMilliseconds); + return commit; + } + + /// + public Task GetFromAsync(string bucketId, string streamId, int minRevision, int maxRevision, IAsyncObserver observer, CancellationToken cancellationToken) + { + return _persistence.GetFromAsync(bucketId, streamId, minRevision, maxRevision, observer, cancellationToken); + } + + /// + public async Task CommitAsync(CommitAttempt attempt, CancellationToken cancellationToken) + { + Stopwatch clock = Stopwatch.StartNew(); + var commit = await _persistence.CommitAsync(attempt, cancellationToken).ConfigureAwait(false); + clock.Stop(); + _counters.CountCommit(attempt.Events.Count, clock.ElapsedMilliseconds); + return commit; + } + + /// + [Obsolete("DateTime is problematic in distributed systems. Use GetFromTo(Int64 fromCheckpointToken, Int64 toCheckpointToken) instead. This method will be removed in a later version.")] + public IEnumerable GetFromTo(string bucketId, DateTime startDate, DateTime endDate) + { + return _persistence.GetFromTo(bucketId, startDate, endDate); + } + + /// + [Obsolete("DateTime is problematic in distributed systems. Use GetFrom(Int64 checkpointToken) instead. This method will be removed in a later version.")] + public IEnumerable GetFrom(string bucketId, DateTime startDate) + { + return _persistence.GetFrom(bucketId, startDate); + } + + /// + public IEnumerable GetFrom(Int64 checkpointToken) + { + return _persistence.GetFrom(checkpointToken); + } + + /// + public Task GetFromAsync(Int64 checkpointToken, IAsyncObserver asyncObserver, CancellationToken cancellationToken) + { + return _persistence.GetFromAsync(checkpointToken, asyncObserver, cancellationToken); + } + + /// + public IEnumerable GetFromTo(Int64 fromCheckpointToken, Int64 toCheckpointToken) + { + return _persistence.GetFromTo(fromCheckpointToken, toCheckpointToken); + } + + /// + public Task GetFromToAsync(Int64 fromCheckpointToken, Int64 toCheckpointToken, IAsyncObserver asyncObserver, CancellationToken cancellationToken) + { + return _persistence.GetFromToAsync(fromCheckpointToken, toCheckpointToken, asyncObserver, cancellationToken); + } + + /// + public IEnumerable GetFrom(string bucketId, Int64 checkpointToken) + { + return _persistence.GetFrom(bucketId, checkpointToken); + } + + /// + public Task GetFromAsync(string bucketId, Int64 checkpointToken, IAsyncObserver asyncObserver, CancellationToken cancellationToken) + { + return _persistence.GetFromAsync(bucketId, checkpointToken, asyncObserver, cancellationToken); + } + + /// + public IEnumerable GetFromTo(string bucketId, Int64 fromCheckpointToken, Int64 toCheckpointToken) + { + return _persistence.GetFromTo(bucketId, fromCheckpointToken, toCheckpointToken); + } + + /// + public Task GetFromToAsync(string bucketId, long fromCheckpointToken, long toCheckpointToken, IAsyncObserver asyncObserver, CancellationToken cancellationToken) + { + return _persistence.GetFromToAsync(bucketId, fromCheckpointToken, toCheckpointToken, asyncObserver, cancellationToken); + } + + /// + public ISnapshot? GetSnapshot(string bucketId, string streamId, int maxRevision) + { + return _persistence.GetSnapshot(bucketId, streamId, maxRevision); + } + + /// + public bool AddSnapshot(ISnapshot snapshot) + { + bool result = _persistence.AddSnapshot(snapshot); + if (result) + { + _counters.CountSnapshot(); + } + + return result; + } + + /// + public virtual IEnumerable GetStreamsToSnapshot(string bucketId, int maxThreshold) + { + return _persistence.GetStreamsToSnapshot(bucketId, maxThreshold); + } + + /// + public Task GetSnapshotAsync(string bucketId, string streamId, int maxRevision, CancellationToken cancellationToken) + { + return _persistence.GetSnapshotAsync(bucketId, streamId, maxRevision, cancellationToken); + } + + /// + public async Task AddSnapshotAsync(ISnapshot snapshot, CancellationToken cancellationToken) + { + bool result = await _persistence.AddSnapshotAsync(snapshot, cancellationToken).ConfigureAwait(false); + if (result) + { + _counters.CountSnapshot(); + } + + return result; + } + + /// + public Task GetStreamsToSnapshotAsync(string bucketId, int maxThreshold, IAsyncObserver asyncObserver, CancellationToken cancellationToken) + { + return _persistence.GetStreamsToSnapshotAsync(bucketId, maxThreshold, asyncObserver, cancellationToken); + } + + /// + public virtual void Purge() + { + _persistence.Purge(); + } + + /// + public void Purge(string bucketId) + { + _persistence.Purge(bucketId); + } + + /// + public Task PurgeAsync(CancellationToken cancellationToken) + { + return _persistence.PurgeAsync(cancellationToken); + } + + /// + public Task PurgeAsync(string bucketId, CancellationToken cancellationToken) + { + return _persistence.PurgeAsync(bucketId, cancellationToken); + } + + /// + public void Drop() + { + _persistence.Drop(); + } + + /// + public void DeleteStream(string bucketId, string streamId) + { + _persistence.DeleteStream(bucketId, streamId); + } + + /// + public Task DeleteStreamAsync(string bucketId, string streamId, CancellationToken cancellationToken) + { + return _persistence.DeleteStreamAsync(bucketId, streamId, cancellationToken); + } + + /// + public bool IsDisposed + { + get { return _persistence.IsDisposed; } + } + + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Finalizes an instance of the PerformanceCounterPersistenceEngine class. + /// + ~PerformanceCounterPersistenceEngine() + { + Dispose(false); + } + + /// + /// Dispose the performance counter and the wrapped persistence engine. + /// + protected virtual void Dispose(bool disposing) + { + if (!disposing) + { + return; + } + + _counters.Dispose(); + _persistence.Dispose(); + } + + /// + /// Unwrap the performance counter and return the wrapped persistence engine. + /// + public IPersistStreams UnwrapPersistenceEngine() + { + return _persistence; + } + } +#endif +} \ No newline at end of file diff --git a/src/NEventStore/Diagnostics/PerformanceCounters.cs b/src/NEventStore/Diagnostics/PerformanceCounters.cs new file mode 100644 index 000000000..acd420332 --- /dev/null +++ b/src/NEventStore/Diagnostics/PerformanceCounters.cs @@ -0,0 +1,114 @@ +using System.Diagnostics; + +namespace NEventStore.Diagnostics +{ + // PerformanceCounters are not cross platform + +#if NET462 + internal class PerformanceCounters : IDisposable + { + private const string CategoryName = "NEventStore"; + private const string TotalCommitsName = "Total Commits"; + private const string CommitsRateName = "Commits/Sec"; + private const string AvgCommitDuration = "Average Commit Duration"; + private const string AvgCommitDurationBase = "Average Commit Duration Base"; + private const string TotalEventsName = "Total Events"; + private const string EventsRateName = "Events/Sec"; + private const string TotalSnapshotsName = "Total Snapshots"; + private const string SnapshotsRateName = "Snapshots/Sec"; + private readonly PerformanceCounter _avgCommitDuration; + private readonly PerformanceCounter _avgCommitDurationBase; + private readonly PerformanceCounter _commitsRate; + private readonly PerformanceCounter _eventsRate; + private readonly PerformanceCounter _snapshotsRate; + private readonly PerformanceCounter _totalCommits; + private readonly PerformanceCounter _totalEvents; + private readonly PerformanceCounter _totalSnapshots; + + static PerformanceCounters() + { + if (PerformanceCounterCategory.Exists(CategoryName)) + { + return; + } + + var counters = new CounterCreationDataCollection + { + new CounterCreationData(TotalCommitsName, "Total number of commits persisted", PerformanceCounterType.NumberOfItems32), + new CounterCreationData(CommitsRateName, "Rate of commits persisted per second", PerformanceCounterType.RateOfCountsPerSecond32), + new CounterCreationData(AvgCommitDuration, "Average duration for each commit", PerformanceCounterType.AverageTimer32), + new CounterCreationData(AvgCommitDurationBase, "Average duration base for each commit", PerformanceCounterType.AverageBase), + new CounterCreationData(TotalEventsName, "Total number of events persisted", PerformanceCounterType.NumberOfItems32), + new CounterCreationData(EventsRateName, "Rate of events persisted per second", PerformanceCounterType.RateOfCountsPerSecond32), + new CounterCreationData(TotalSnapshotsName, "Total number of snapshots persisted", PerformanceCounterType.NumberOfItems32), + new CounterCreationData(SnapshotsRateName, "Rate of snapshots persisted per second", PerformanceCounterType.RateOfCountsPerSecond32), + }; + + // TODO: add other useful counts such as: + // + // * Total Commit Bytes + // * Average Commit Bytes + // * Total Queries + // * Queries Per Second + // * Average Query Duration + // * Commits per Query (Total / average / per second) + // * Events per Query (Total / average / per second) + // + // Some of these will involve hooking into other parts of the NEventStore + + PerformanceCounterCategory.Create(CategoryName, "NEventStore Event-Sourcing Persistence", PerformanceCounterCategoryType.MultiInstance, counters); + } + + public PerformanceCounters(string instanceName) + { + _totalCommits = new PerformanceCounter(CategoryName, TotalCommitsName, instanceName, false); + _commitsRate = new PerformanceCounter(CategoryName, CommitsRateName, instanceName, false); + _avgCommitDuration = new PerformanceCounter(CategoryName, AvgCommitDuration, instanceName, false); + _avgCommitDurationBase = new PerformanceCounter(CategoryName, AvgCommitDurationBase, instanceName, false); + _totalEvents = new PerformanceCounter(CategoryName, TotalEventsName, instanceName, false); + _eventsRate = new PerformanceCounter(CategoryName, EventsRateName, instanceName, false); + _totalSnapshots = new PerformanceCounter(CategoryName, TotalSnapshotsName, instanceName, false); + _snapshotsRate = new PerformanceCounter(CategoryName, SnapshotsRateName, instanceName, false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + public void CountCommit(int eventsCount, long elapsedMilliseconds) + { + _totalCommits.Increment(); + _commitsRate.Increment(); + _avgCommitDuration.IncrementBy(elapsedMilliseconds); + _avgCommitDurationBase.Increment(); + _totalEvents.IncrementBy(eventsCount); + _eventsRate.IncrementBy(eventsCount); + } + + public void CountSnapshot() + { + _totalSnapshots.Increment(); + _snapshotsRate.Increment(); + } + + ~PerformanceCounters() + { + Dispose(false); + } + + protected virtual void Dispose(bool disposing) + { + _totalCommits.Dispose(); + _commitsRate.Dispose(); + _avgCommitDuration.Dispose(); + _avgCommitDurationBase.Dispose(); + _totalEvents.Dispose(); + _eventsRate.Dispose(); + _totalSnapshots.Dispose(); + _snapshotsRate.Dispose(); + } + } +#endif +} \ No newline at end of file diff --git a/src/NEventStore/DuplicateCommitException.cs b/src/NEventStore/DuplicateCommitException.cs new file mode 100644 index 000000000..58763d4af --- /dev/null +++ b/src/NEventStore/DuplicateCommitException.cs @@ -0,0 +1,44 @@ +namespace NEventStore +{ + using System; + using System.Runtime.Serialization; + + /// + /// Represents an attempt to commit the same information more than once. + /// + [Serializable] + public class DuplicateCommitException : Exception + { + /// + /// Initializes a new instance of the DuplicateCommitException class. + /// + public DuplicateCommitException() + {} + + /// + /// Initializes a new instance of the DuplicateCommitException class. + /// + /// The message that describes the error. + public DuplicateCommitException(string message) + : base(message) + {} + + /// + /// Initializes a new instance of the DuplicateCommitException class. + /// + /// The message that describes the error. + /// The message that is the cause of the current exception. + public DuplicateCommitException(string message, Exception innerException) + : base(message, innerException) + {} + + /// + /// Initializes a new instance of the DuplicateCommitException class. + /// + /// The SerializationInfo that holds the serialized object data of the exception being thrown. + /// The StreamingContext that contains contextual information about the source or destination. + protected DuplicateCommitException(SerializationInfo info, StreamingContext context) + : base(info, context) + {} + } +} \ No newline at end of file diff --git a/src/NEventStore/EventMessage.cs b/src/NEventStore/EventMessage.cs new file mode 100644 index 000000000..58ead0a32 --- /dev/null +++ b/src/NEventStore/EventMessage.cs @@ -0,0 +1,34 @@ +namespace NEventStore +{ + using System; + using System.Collections.Generic; + using System.Runtime.Serialization; + + /// + /// Represents a single element in a stream of events. + /// + [Serializable] + [DataContract] + public sealed class EventMessage + { + /// + /// Initializes a new instance of the EventMessage class. + /// + public EventMessage() + { + Headers = []; + } + + /// + /// Gets the metadata which provides additional, unstructured information about this message. + /// + [DataMember] + public Dictionary Headers { get; set; } + + /// + /// Gets or sets the actual event message body. + /// + [DataMember] + public object Body { get; set; } + } +} \ No newline at end of file diff --git a/src/NEventStore/EventUpconverterWireup.cs b/src/NEventStore/EventUpconverterWireup.cs new file mode 100644 index 000000000..baa6cfb22 --- /dev/null +++ b/src/NEventStore/EventUpconverterWireup.cs @@ -0,0 +1,121 @@ +using System.Reflection; +using Microsoft.Extensions.Logging; +using NEventStore.Conversion; +using NEventStore.Logging; + +namespace NEventStore +{ + /// + /// Represents the configuration for event upconverters. + /// + public class EventUpconverterWireup : Wireup + { + private static readonly ILogger Logger = LogFactory.BuildLogger(typeof(EventUpconverterWireup)); + private readonly List _assembliesToScan = new List(); + private readonly Dictionary> _registered = new Dictionary>(); + + /// + /// Initializes a new instance of the EventUpconverterWireup class. + /// + /// + public EventUpconverterWireup(Wireup wireup) : base(wireup) + { + if (Logger.IsEnabled(LogLevel.Debug)) + { + Logger.LogDebug(Messages.EventUpconverterRegistered); + } + + Container.Register(_ => + { + if (_registered.Count > 0) + { + return new EventUpconverterPipelineHook(_registered); + } + + if (_assembliesToScan.Count == 0) + { + _assembliesToScan.AddRange(GetAllAssemblies()); + } + + var converters = GetConverters(_assembliesToScan); + if (converters.Count > 0) + { + return new EventUpconverterPipelineHook(converters); + } + return null; + }); + } + + private static IEnumerable GetAllAssemblies() + { + return Assembly.GetCallingAssembly() + .GetReferencedAssemblies() + .Select(Assembly.Load) + .Concat(new[] { Assembly.GetCallingAssembly() }); + } + + private static Dictionary> GetConverters(IEnumerable toScan) + { + IEnumerable>> c = from a in toScan + from t in a.GetTypes() + where !t.IsAbstract + let i = t.GetInterface(typeof(IUpconvertEvents<,>).FullName) + where i != null + let sourceType = i.GetGenericArguments().First() + let convertMethod = i.GetMethods(BindingFlags.Public | BindingFlags.Instance).First() + let instance = Activator.CreateInstance(t) + select new KeyValuePair>( + sourceType, e => convertMethod.Invoke(instance, new[] { e })); + try + { + return c.ToDictionary(x => x.Key, x => x.Value); + } + catch (ArgumentException e) + { + throw new MultipleConvertersFoundException(e.Message, e); + } + } + + /// + /// Scans the specified assemblies for event upconverters. + /// + public virtual EventUpconverterWireup WithConvertersFrom(params Assembly[] assemblies) + { + if (Logger.IsEnabled(LogLevel.Debug)) + { + Logger.LogDebug(Messages.EventUpconvertersLoadedFrom, + string.Join(", ", assemblies.Select(a => $"{a.GetName().Name} {a.GetName().Version}"))); + } + _assembliesToScan.AddRange(assemblies); + return this; + } + + /// + /// Scans the assemblies containing the converters for event upconverters. + /// + public virtual EventUpconverterWireup WithConvertersFromAssemblyContaining(params Type[] converters) + { + IEnumerable assemblies = converters.Select(c => c.Assembly).Distinct(); + return this.WithConvertersFrom(assemblies.ToArray()); + } + + /// + /// Adds a converter to the pipeline. + /// + /// + public virtual EventUpconverterWireup AddConverter( + IUpconvertEvents converter) + where TSource : class + where TTarget : class + { + if (converter == null) + { + throw new ArgumentNullException(nameof(converter)); + } + + _registered[typeof(TSource)] = @event => converter.Convert((TSource)@event); + + return this; + } + } +} \ No newline at end of file diff --git a/src/NEventStore/EventUpconverterWireupExtensions.cs b/src/NEventStore/EventUpconverterWireupExtensions.cs new file mode 100644 index 000000000..68024e15a --- /dev/null +++ b/src/NEventStore/EventUpconverterWireupExtensions.cs @@ -0,0 +1,16 @@ +namespace NEventStore +{ + /// + /// Provides support for upconverting events during the commit phase. + /// + public static class EventUpconverterWireupExtensions + { + /// + /// Configures the event store to upconvert events during the commit phase. + /// + public static EventUpconverterWireup UsingEventUpconversion(this Wireup wireup) + { + return new EventUpconverterWireup(wireup); + } + } +} \ No newline at end of file diff --git a/src/NEventStore/ExtensionMethods.cs b/src/NEventStore/ExtensionMethods.cs new file mode 100644 index 000000000..b2dcebd11 --- /dev/null +++ b/src/NEventStore/ExtensionMethods.cs @@ -0,0 +1,21 @@ +using System.Globalization; + +namespace NEventStore +{ + /// + /// A set of common methods used through the NEventStore. + /// + public static class ExtensionMethods + { + /// + /// Formats the string provided using the values specified. + /// + /// The string to be formatted. + /// The values to be embedded into the string. + /// The formatted string. + public static string FormatWith(this string format, params object[] values) + { + return string.Format(CultureInfo.InvariantCulture, format ?? string.Empty, values); + } + } +} \ No newline at end of file diff --git a/src/NEventStore/Guard.cs b/src/NEventStore/Guard.cs new file mode 100644 index 000000000..afbf03429 --- /dev/null +++ b/src/NEventStore/Guard.cs @@ -0,0 +1,75 @@ +using System.Linq.Expressions; + +namespace NEventStore +{ + internal static class Guard + { + internal static void NotFalse(bool condition, Func createException) + { + if (!condition) + { + throw createException(); + } + } + + internal static void NotNullOrWhiteSpace(Expression> reference, string value) + { + if (string.IsNullOrWhiteSpace(value)) + { + throw new ArgumentException(GetParameterName(reference)); + } + } + + internal static void NotNull(Expression> reference, T value) + { + if (value == null) + { + throw new ArgumentNullException(GetParameterName(reference)); + } + } + + internal static void NotLessThanOrEqualTo(Expression> reference, T value, T compareTo) + where T: IComparable + { + NotNull(reference, value); + if (value.CompareTo(compareTo) <= 0) + { + throw new ArgumentOutOfRangeException("{0} has value {1} which is less than or equal to {2}".FormatWith(GetParameterName(reference), value, compareTo)); + } + } + + internal static void NotLessThan(Expression> reference, T value, T compareTo) + where T : IComparable + { + NotNull(reference, value); + if (value.CompareTo(compareTo) < 0) + { + throw new ArgumentOutOfRangeException("{0} has value {1} which is less than {2}".FormatWith(GetParameterName(reference), value, compareTo)); + } + } + + internal static void NotDefault(Expression> reference, T value) + where T : IComparable + { + NotNull(reference, value); + if (value.CompareTo(default(T)) == 0) + { + throw new ArgumentException("{0} has value {1} which cannot be equal to it's default value {2}".FormatWith(GetParameterName(reference), value, default(T)!)); + } + } + + internal static void NotEmpty(Expression>> reference, IEnumerable value) + { + NotNull(reference, value); + if (!value.Any()) + { + throw new ArgumentException("{0} cannot be empty".FormatWith(GetParameterName(reference), value, default(T)!)); + } + } + + private static string GetParameterName(LambdaExpression reference) + { + return ((MemberExpression) reference.Body).Member.Name; + } + } +} \ No newline at end of file diff --git a/src/NEventStore/Helpers/DateTimeService.cs b/src/NEventStore/Helpers/DateTimeService.cs new file mode 100644 index 000000000..3514c4682 --- /dev/null +++ b/src/NEventStore/Helpers/DateTimeService.cs @@ -0,0 +1,54 @@ +namespace NEventStore.Helpers +{ + /// + /// Provides a way to get the current date and time. + /// Useful for testing. + /// + public static class DateTimeService + { + private static Func _nowFunc = () => DateTime.Now; + private static Func _utcNowFunc = () => DateTime.UtcNow; + + /// + /// Gets the current date and time. + /// + public static DateTime Now + { + get + { + return _nowFunc(); + } + } + + /// + /// Gets the current date and time in UTC. + /// + public static DateTime UtcNow + { + get + { + return _utcNowFunc(); + } + } + + #region "test function" + + internal static DisposableAction Override(Func functor) + { + _nowFunc = functor; + _utcNowFunc = () => functor().ToUniversalTime(); + return new DisposableAction(() => + { + _nowFunc = () => DateTime.Now; + _utcNowFunc = () => DateTime.UtcNow; + }); + } + + internal static DisposableAction Override(DateTime overrideNowDate) + { + return Override(() => overrideNowDate); + } + + #endregion + } +} diff --git a/src/NEventStore/Helpers/DisposableAction.cs b/src/NEventStore/Helpers/DisposableAction.cs new file mode 100644 index 000000000..79c52d8f6 --- /dev/null +++ b/src/NEventStore/Helpers/DisposableAction.cs @@ -0,0 +1,22 @@ +namespace NEventStore.Helpers +{ + /// + /// Provides a way to execute an action when disposed. + /// + internal sealed class DisposableAction : IDisposable + { + private Action? _disposeAction; + + public DisposableAction(Action disposeAction) + { + _disposeAction = disposeAction; + } + + public void Dispose() + { + // Interlocked allows the continuation to be executed only once + var dispose = Interlocked.Exchange(ref _disposeAction, null); + dispose?.Invoke(); + } + } +} diff --git a/src/NEventStore/IAccessSnapshots.cs b/src/NEventStore/IAccessSnapshots.cs new file mode 100644 index 000000000..eedc4945e --- /dev/null +++ b/src/NEventStore/IAccessSnapshots.cs @@ -0,0 +1,43 @@ +using NEventStore.Persistence; + +namespace NEventStore +{ + /// + /// Indicates the ability to get or retrieve a snapshot for a given stream. + /// + /// + /// Instances of this class must be designed to be multi-thread safe such that they can be shared between threads. + /// + public interface IAccessSnapshots + { + /// + /// Gets the most recent snapshot which was taken on or before the revision indicated. + /// + /// The value which uniquely identifies bucket the stream belongs to. + /// The stream to be searched for a snapshot. + /// The maximum revision possible for the desired snapshot. + /// If found, it returns the snapshot; otherwise null is returned. + /// + /// + ISnapshot? GetSnapshot(string bucketId, string streamId, int maxRevision); + + /// + /// Adds the snapshot provided to the stream indicated. + /// + /// The snapshot to save. + /// If the snapshot was added, returns true; otherwise false. + /// + /// + bool AddSnapshot(ISnapshot snapshot); + + /// + /// Gets identifiers for all streams whose head and last snapshot revisions differ by at least the threshold specified. + /// + /// The value which uniquely identifies bucket the stream belongs to. + /// The maximum difference between the head and most recent snapshot revisions. + /// The streams for which the head and snapshot revisions differ by at least the threshold specified. + /// + /// + IEnumerable GetStreamsToSnapshot(string bucketId, int maxThreshold); + } +} \ No newline at end of file diff --git a/src/NEventStore/IAccessSnapshotsAsync.cs b/src/NEventStore/IAccessSnapshotsAsync.cs new file mode 100644 index 000000000..84c5f1111 --- /dev/null +++ b/src/NEventStore/IAccessSnapshotsAsync.cs @@ -0,0 +1,49 @@ +using NEventStore.Persistence; + +namespace NEventStore +{ + /// + /// Indicates the ability to get or retrieve a snapshot for a given stream. + /// + /// + /// Instances of this class must be designed to be multi-thread safe such that they can be shared between threads. + /// + public interface IAccessSnapshotsAsync + { + /// + /// Gets the most recent snapshot which was taken on or before the revision indicated. + /// + /// The value which uniquely identifies bucket the stream belongs to. + /// The stream to be searched for a snapshot. + /// The maximum revision possible for the desired snapshot. + /// The token to monitor for cancellation requests. + /// If found, it returns the snapshot; otherwise null is returned. + /// + /// + Task GetSnapshotAsync(string bucketId, string streamId, int maxRevision, CancellationToken cancellationToken = default); + + /// + /// Adds the snapshot provided to the stream indicated. + /// + /// The snapshot to save. + /// The token to monitor for cancellation requests. + /// If the snapshot was added, returns true; otherwise false. + /// + /// + Task AddSnapshotAsync(ISnapshot snapshot, CancellationToken cancellationToken = default); + + /// + /// Gets identifiers for all streams whose head and last snapshot revisions differ by at least the threshold specified. + /// + /// The value which uniquely identifies bucket the stream belongs to. + /// The maximum difference between the head and most recent snapshot revisions. + /// The observer to receive the streams. + /// The token to monitor for cancellation requests. + /// + /// + /// + /// In Observer.OnErrorAsync and Observer.OnCompletedAsync the checkpoint will always be 0. + /// + Task GetStreamsToSnapshotAsync(string bucketId, int maxThreshold, IAsyncObserver asyncObserver, CancellationToken cancellationToken = default); + } +} \ No newline at end of file diff --git a/src/NEventStore/IAsyncObserver.cs b/src/NEventStore/IAsyncObserver.cs new file mode 100644 index 000000000..c11b6b3e3 --- /dev/null +++ b/src/NEventStore/IAsyncObserver.cs @@ -0,0 +1,32 @@ +namespace NEventStore +{ + /// + /// Represents an async observer that can receive notifications of objects. + /// + public interface IAsyncObserver + { + /// + /// Provides the observer with new data. + /// + /// The current notification information. + /// the cancellation token of the original request. + /// + /// true = continue reading, false = stop reading + /// + Task OnNextAsync(T value, CancellationToken cancellationToken = default); + /// + /// Notifies the observer that the provider has experienced an error condition. + /// + /// The error that has occurred. + /// the cancellation token of the original request. + Task OnErrorAsync(Exception ex, CancellationToken cancellationToken = default); + /// + /// Notifies the observer that the provider has finished sending push-based notifications. + /// It can happen because: + /// - the source has completed sending push-based notifications successfully. + /// - the reading operation has been cancelled. + /// + /// the cancellation token of the original request. + Task OnCompletedAsync(CancellationToken cancellationToken = default); + } +} \ No newline at end of file diff --git a/src/NEventStore/ICommit.cs b/src/NEventStore/ICommit.cs new file mode 100644 index 000000000..04c731143 --- /dev/null +++ b/src/NEventStore/ICommit.cs @@ -0,0 +1,53 @@ +namespace NEventStore +{ + /// + /// Represents a series of events which have been fully committed as a single unit and which apply to the stream indicated. + /// + public interface ICommit + { + /// + /// Gets the value which identifies bucket to which the stream and the commit belongs. + /// + string BucketId { get; } + + /// + /// Gets the value which uniquely identifies the stream to which the commit belongs. + /// + string StreamId { get; } + + /// + /// Gets the value which indicates the revision of the most recent event in the stream to which this commit applies. + /// + int StreamRevision { get; } + + /// + /// Gets the value which uniquely identifies the commit within the stream. + /// + Guid CommitId { get; } + + /// + /// Gets the value which indicates the sequence (or position) in the stream to which this commit applies. + /// + int CommitSequence { get; } + + /// + /// Gets the point in time at which the commit was persisted. + /// + DateTime CommitStamp { get; } + + /// + /// Gets the metadata which provides additional, unstructured information about this commit. + /// + IDictionary Headers { get; } + + /// + /// Gets the collection of event messages to be committed as a single unit. + /// + ICollection Events { get; } + + /// + /// The checkpoint that represents the storage level order. + /// + Int64 CheckpointToken { get; } + } +} \ No newline at end of file diff --git a/src/NEventStore/ICommitEvents.cs b/src/NEventStore/ICommitEvents.cs new file mode 100644 index 000000000..69ff1fce0 --- /dev/null +++ b/src/NEventStore/ICommitEvents.cs @@ -0,0 +1,39 @@ +using NEventStore.Persistence; + +namespace NEventStore +{ + /// + /// Indicates the ability to commit events and access events to and from a given stream. + /// + /// + /// Instances of this class must be designed to be multi-thread safe such that they can be shared between threads. + /// + public interface ICommitEvents + { + /// + /// Gets the corresponding commits from the stream indicated starting at the revision specified until the + /// end of the stream sorted in ascending order--from oldest to newest. + /// + /// The value which uniquely identifies bucket the stream belongs to. + /// The stream from which the events will be read. + /// The minimum revision of the stream to be read. + /// The maximum revision of the stream to be read. + /// A series of committed events from the stream specified sorted in ascending order. + /// + /// + IEnumerable GetFrom(string bucketId, string streamId, int minRevision, int maxRevision); + + /// + /// Writes the to-be-committed events provided to the underlying persistence mechanism. + /// + /// The series of events and associated metadata to be committed. + /// + /// + /// + /// + /// This interface returns a nullable ICommit object because it's implemented by + /// that can return null if the pipeline hooks decide to abort the commit. + /// + ICommit? Commit(CommitAttempt attempt); + } +} \ No newline at end of file diff --git a/src/NEventStore/ICommitEventsAsync.cs b/src/NEventStore/ICommitEventsAsync.cs new file mode 100644 index 000000000..2fa4edca4 --- /dev/null +++ b/src/NEventStore/ICommitEventsAsync.cs @@ -0,0 +1,47 @@ +using NEventStore.Persistence; + +namespace NEventStore +{ + /// + /// Indicates the ability to commit events and access events to and from a given stream. + /// + /// + /// Instances of this class must be designed to be multi-thread safe such that they can be shared between threads. + /// + public interface ICommitEventsAsync + { + /// + /// Gets the corresponding commits (possibly using an async cursor) from the stream indicated + /// starting at the revision specified until the end of the stream sorted + /// in ascending order--from oldest to newest. + /// Each commit will be passed to an observer. + /// Reading operation will stop: + /// - when the maxRevision is reached. + /// - if CancellationToken is set. + /// - when there are no more commits to read. + /// + /// The value which uniquely identifies bucket the stream belongs to. + /// The stream from which the events will be read. + /// The minimum revision of the stream to be read. + /// The maximum revision of the stream to be read. + /// Observer to receive the commits. + /// The token to monitor for cancellation requests. + /// + /// + Task GetFromAsync(string bucketId, string streamId, int minRevision, int maxRevision, IAsyncObserver observer, CancellationToken cancellationToken = default); + + /// + /// Writes the to-be-committed events provided to the underlying persistence mechanism. + /// + /// The series of events and associated metadata to be committed. + /// The token to monitor for cancellation requests. + /// + /// + /// + /// + /// This interface returns a nullable ICommit object because it's implemented by + /// that can return null if the pipeline hooks decide to abort the commit. + /// + Task CommitAsync(CommitAttempt attempt, CancellationToken cancellationToken = default); + } +} \ No newline at end of file diff --git a/src/NEventStore/IEventStream.cs b/src/NEventStore/IEventStream.cs new file mode 100644 index 000000000..c4132e279 --- /dev/null +++ b/src/NEventStore/IEventStream.cs @@ -0,0 +1,87 @@ +using NEventStore.Persistence; + +namespace NEventStore +{ + /// + /// Indicates the ability to track a series of events and commit them to durable storage. + /// + /// + /// Instances of this class are single threaded and should not be shared between threads. + /// + public interface IEventStream : IDisposable + { + /// + /// Gets the value which identifies bucket to which the stream belongs. + /// + string BucketId { get; } + + /// + /// Gets the value which uniquely identifies the stream to which the stream belongs. + /// + string StreamId { get; } + + /// + /// Gets the value which indicates the most recent committed revision of event stream. + /// + int StreamRevision { get; } + + /// + /// Gets the value which indicates the most recent committed sequence identifier of the event stream. + /// + int CommitSequence { get; } + + /// + /// Gets the collection of events which have been successfully persisted to durable storage. + /// + ICollection CommittedEvents { get; } + + /// + /// Gets the collection of committed headers associated with the stream. + /// + IDictionary CommittedHeaders { get; } + + /// + /// Gets the collection of yet-to-be-committed events that have not yet been persisted to durable storage. + /// + ICollection UncommittedEvents { get; } + + /// + /// Gets the collection of yet-to-be-committed headers associated with the uncommitted events. + /// + IDictionary UncommittedHeaders { get; } + + /// + /// Adds the event messages provided to the session to be tracked. + /// + /// The event to be tracked. + void Add(EventMessage uncommittedEvent); + + /// + /// Commits the changes to durable storage. + /// + /// The value which uniquely identifies the commit. + /// The commit which has been persisted to durable storage. + /// + /// + /// + /// + ICommit? CommitChanges(Guid commitId); + + /// + /// Commits the changes to durable storage. + /// + /// The value which uniquely identifies the commit. + /// The token to monitor for cancellation requests. + /// The commit which has been persisted to durable storage. + /// + /// + /// + /// + Task CommitChangesAsync(Guid commitId, CancellationToken cancellationToken = default); + + /// + /// Clears the uncommitted changes. + /// + void ClearChanges(); + } +} \ No newline at end of file diff --git a/src/NEventStore/IPipelineHook.cs b/src/NEventStore/IPipelineHook.cs new file mode 100644 index 000000000..b85dd3a24 --- /dev/null +++ b/src/NEventStore/IPipelineHook.cs @@ -0,0 +1,44 @@ +namespace NEventStore +{ + /// + /// Provides the ability to hook into the pipeline of persisting a commit. + /// + /// + /// Instances of this class must be designed to be multi-thread safe such that they can be shared between threads. + /// + public interface IPipelineHook : IDisposable + { + /// + /// Hooks into the selection pipeline just prior to the commit being returned to the caller. + /// + /// The commit to be filtered. + /// If successful, returns a populated commit; otherwise returns null. + ICommit? SelectCommit(ICommit committed); + + /// + /// Hooks into the commit pipeline prior to persisting the commit to durable storage. + /// + /// The attempt to be committed. + /// If processing should continue, returns true; otherwise returns false. + bool PreCommit(CommitAttempt attempt); + + /// + /// Hooks into the commit pipeline just after the commit has been *successfully* committed to durable storage. + /// + /// The commit which has been persisted. + void PostCommit(ICommit committed); + + /// + /// Invoked when a bucket has been purged. If buckedId is null, then all buckets have been purged. + /// + /// The bucket Id that has been purged. Null when all buckets have been purged. + void OnPurge(string? bucketId); + + /// + /// Invoked when a stream has been deleted. + /// + /// The bucket Id from which the stream which has been deleted. + /// The stream Id of the stream which has been deleted. + void OnDeleteStream(string bucketId, string streamId); + } +} \ No newline at end of file diff --git a/src/NEventStore/IPipelineHookAsync.cs b/src/NEventStore/IPipelineHookAsync.cs new file mode 100644 index 000000000..601d273b1 --- /dev/null +++ b/src/NEventStore/IPipelineHookAsync.cs @@ -0,0 +1,49 @@ +namespace NEventStore +{ + /// + /// Provides the ability to hook into the pipeline of persisting a commit. + /// + /// + /// Instances of this class must be designed to be multi-thread safe such that they can be shared between threads. + /// + public interface IPipelineHookAsync : IDisposable + { + /// + /// Hooks into the selection pipeline just prior to the commit being returned to the caller. + /// + /// The commit to be filtered. + /// The token to monitor for cancellation requests. + /// If successful, returns a populated commit; otherwise returns null. + Task SelectCommitAsync(ICommit committed, CancellationToken cancellationToken); + + /// + /// Hooks into the commit pipeline prior to persisting the commit to durable storage. + /// + /// The attempt to be committed. + /// The token to monitor for cancellation requests. + /// If processing should continue, returns true; otherwise returns false. + Task PreCommitAsync(CommitAttempt attempt, CancellationToken cancellationToken); + + /// + /// Hooks into the commit pipeline just after the commit has been *successfully* committed to durable storage. + /// + /// The commit which has been persisted. + /// The token to monitor for cancellation requests. + Task PostCommitAsync(ICommit committed, CancellationToken cancellationToken); + + /// + /// Invoked when a bucket has been purged. If buckedId is null, then all buckets have been purged. + /// + /// The bucket Id that has been purged. Null when all buckets have been purged. + /// The token to monitor for cancellation requests. + Task OnPurgeAsync(string? bucketId, CancellationToken cancellationToken); + + /// + /// Invoked when a stream has been deleted. + /// + /// The bucket Id from which the stream which has been deleted. + /// The stream Id of the stream which has been deleted. + /// The token to monitor for cancellation requests. + Task OnDeleteStreamAsync(string bucketId, string streamId, CancellationToken cancellationToken); + } +} \ No newline at end of file diff --git a/src/NEventStore/ISnapshot.cs b/src/NEventStore/ISnapshot.cs new file mode 100644 index 000000000..178f0bc70 --- /dev/null +++ b/src/NEventStore/ISnapshot.cs @@ -0,0 +1,28 @@ +namespace NEventStore +{ + /// + /// Represents a materialized view of a stream at specific revision. + /// + public interface ISnapshot + { + /// + /// Gets the value which uniquely identifies the bucket to which the snapshot applies. + /// + string BucketId { get; } + + /// + /// Gets the value which uniquely identifies the stream to which the snapshot applies. + /// + string StreamId { get; } + + /// + /// Gets the position at which the snapshot applies. + /// + int StreamRevision { get; } + + /// + /// Gets the snapshot or materialized view of the stream at the revision indicated. + /// + object Payload { get; } + } +} \ No newline at end of file diff --git a/src/NEventStore/IStoreEvents.cs b/src/NEventStore/IStoreEvents.cs new file mode 100644 index 000000000..db4c27b5c --- /dev/null +++ b/src/NEventStore/IStoreEvents.cs @@ -0,0 +1,76 @@ +using NEventStore.Persistence; + +namespace NEventStore +{ + /// + /// Indicates the ability to store and retrieve a stream of events. + /// + /// + /// Instances of this class must be designed to be multi-thread safe such that they can be shared between threads. + /// + public interface IStoreEvents : IDisposable + { + /// + /// Gets a reference to the underlying persistence engine which allows direct access to persistence operations. + /// + IPersistStreams Advanced { get; } + + /// + /// Creates a new stream. + /// + /// The value which uniquely identifies bucket the stream belongs to. + /// The value which uniquely identifies the stream within the bucket to be created. + /// An empty stream. + IEventStream CreateStream(string bucketId, string streamId); + + /// + /// Reads the stream indicated from the minimum revision specified up to the maximum revision specified or creates + /// an empty stream if no commits are found and a minimum revision of zero is provided. + /// + /// The value which uniquely identifies bucket the stream belongs to. + /// The value which uniquely identifies the stream in the bucket from which the events will be read. + /// The minimum revision of the stream to be read. + /// The maximum revision of the stream to be read. + /// A series of committed events represented as a stream. + /// + /// + /// + IEventStream OpenStream(string bucketId, string streamId, int minRevision, int maxRevision); + + /// + /// Reads the stream indicated from the minimum revision specified up to the maximum revision specified or creates + /// an empty stream if no commits are found and a minimum revision of zero is provided. + /// + /// The value which uniquely identifies bucket the stream belongs to. + /// The value which uniquely identifies the stream in the bucket from which the events will be read. + /// The minimum revision of the stream to be read. + /// The maximum revision of the stream to be read. + /// The token to monitor for cancellation requests. + /// A series of committed events represented as a stream. + /// + /// + /// + Task OpenStreamAsync(string bucketId, string streamId, int minRevision, int maxRevision, CancellationToken cancellationToken = default); + + /// + /// Reads the stream indicated from the point of the snapshot forward until the maximum revision specified. + /// + /// The snapshot of the stream to be read. + /// The maximum revision of the stream to be read. + /// A series of committed events represented as a stream. + /// + /// + IEventStream OpenStream(ISnapshot snapshot, int maxRevision); + + /// + /// Reads the stream indicated from the point of the snapshot forward until the maximum revision specified. + /// + /// The snapshot of the stream to be read. + /// The maximum revision of the stream to be read. + /// The token to monitor for cancellation requests. + /// A series of committed events represented as a stream. + /// + /// + Task OpenStreamAsync(ISnapshot snapshot, int maxRevision, CancellationToken cancellationToken = default); + } +} \ No newline at end of file diff --git a/src/NEventStore/ImmutableCollection.cs b/src/NEventStore/ImmutableCollection.cs new file mode 100644 index 000000000..2f1cb05e8 --- /dev/null +++ b/src/NEventStore/ImmutableCollection.cs @@ -0,0 +1,71 @@ +using System.Collections; + +namespace NEventStore +{ + internal sealed class ImmutableCollection : ICollection, ICollection + { + private readonly ICollection _inner; + + public ImmutableCollection(ICollection inner) + { + _inner = inner; + } + + public object SyncRoot { get; } = new object(); + + public bool IsSynchronized + { + get { return false; } + } + + public void CopyTo(Array array, int index) + { + CopyTo(array.Cast().ToArray(), index); + } + + public int Count + { + get { return _inner.Count; } + } + + public bool IsReadOnly + { + get { return true; } + } + + public IEnumerator GetEnumerator() + { + return _inner.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public void Add(T item) + { + throw new NotSupportedException(Resources.ReadOnlyCollection); + } + + public bool Remove(T item) + { + throw new NotSupportedException(Resources.ReadOnlyCollection); + } + + public void Clear() + { + throw new NotSupportedException(Resources.ReadOnlyCollection); + } + + public bool Contains(T item) + { + return _inner.Contains(item); + } + + public void CopyTo(T[] array, int arrayIndex) + { + _inner.CopyTo(array, arrayIndex); + } + } +} \ No newline at end of file diff --git a/src/NEventStore/ImmutableDictionary.cs b/src/NEventStore/ImmutableDictionary.cs new file mode 100644 index 000000000..7133698bf --- /dev/null +++ b/src/NEventStore/ImmutableDictionary.cs @@ -0,0 +1,136 @@ +using System.Collections; + +namespace NEventStore +{ + /// + /// Represents an immutable dictionary. + /// + public sealed class ImmutableDictionary : IDictionary + { + private readonly IDictionary _inner; + + /// + /// Initializes a new instance of the ImmutableDictionary class. + /// + public ImmutableDictionary(IDictionary inner) + { + _inner = inner; + } + + /// + /// Gets the value associated with the specified key. + /// + /// + public TValue this[TKey key] { get => _inner[key]; set => throw new NotSupportedException(Resources.ReadOnlyCollection); } + + /// + /// Gets the keys in the dictionary. + /// + public ICollection Keys => _inner.Keys; + + /// + /// Gets the values in the dictionary. + /// + public ICollection Values => _inner.Values; + + /// + /// Gets the number of elements in the dictionary. + /// + public int Count => _inner.Count; + + /// + /// Gets a value indicating whether the dictionary is read-only. + /// + public bool IsReadOnly => true; + + /// + /// Adds an element with the provided key and value to the dictionary. + /// + /// + public void Add(TKey key, TValue value) + { + throw new NotSupportedException(Resources.ReadOnlyCollection); + } + + /// + /// Adds an item to the dictionary. + /// + /// + public void Add(KeyValuePair item) + { + throw new NotSupportedException(Resources.ReadOnlyCollection); + } + + /// + /// Removes all items from the dictionary. + /// + /// + public void Clear() + { + throw new NotSupportedException(Resources.ReadOnlyCollection); + } + + /// + /// Determines whether the dictionary contains a specific value. + /// + public bool Contains(KeyValuePair item) + { + return _inner.Contains(item); + } + + /// + /// Determines whether the dictionary contains a specific key. + /// + public bool ContainsKey(TKey key) + { + return _inner.ContainsKey(key); + } + + /// + /// Copies the elements of the dictionary to an array, starting at a particular array index. + /// + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { + _inner.CopyTo(array, arrayIndex); + } + + /// + /// Removes the element with the specified key from the dictionary. + /// + /// + public bool Remove(TKey key) + { + throw new NotSupportedException(Resources.ReadOnlyCollection); + } + + /// + /// Removes the first occurrence of a specific object from the dictionary. + /// + /// + public bool Remove(KeyValuePair item) + { + throw new NotSupportedException(Resources.ReadOnlyCollection); + } + + /// + /// Gets the value associated with the specified key. + /// + public bool TryGetValue(TKey key, out TValue value) + { + return _inner.TryGetValue(key, out value); + } + + /// + /// Returns an enumerator that iterates through the dictionary. + /// + public IEnumerator> GetEnumerator() + { + return _inner.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} \ No newline at end of file diff --git a/src/NEventStore/LambdaAsyncObserver.cs b/src/NEventStore/LambdaAsyncObserver.cs new file mode 100644 index 000000000..4dcff26ea --- /dev/null +++ b/src/NEventStore/LambdaAsyncObserver.cs @@ -0,0 +1,44 @@ +namespace NEventStore +{ + /// + /// Represents an async observer that can receive notifications of commits. + /// + public class LambdaAsyncObserver : IAsyncObserver + { + private readonly Func> _onNextAsync; + private readonly Func? _onErrorAsync; + private readonly Func? _onCompletedAsync; + + /// + /// Initializes a new instance of the LambdaAsyncObserver class. + /// + /// + public LambdaAsyncObserver( + Func> onNextAsync, + Func? onErrorAsync = null, + Func? onCompletedAsync = null) + { + _onNextAsync = onNextAsync ?? throw new ArgumentNullException(nameof(onNextAsync)); + _onErrorAsync = onErrorAsync; + _onCompletedAsync = onCompletedAsync; + } + + /// + public Task OnCompletedAsync(CancellationToken cancellationToken) + { + return _onCompletedAsync?.Invoke(cancellationToken) ?? Task.CompletedTask; + } + + /// + public Task OnErrorAsync(Exception ex, CancellationToken cancellationToken) + { + return _onErrorAsync?.Invoke(ex, cancellationToken) ?? Task.CompletedTask; + } + + /// + public Task OnNextAsync(T value, CancellationToken cancellationToken) + { + return _onNextAsync(value, cancellationToken); + } + } +} diff --git a/src/NEventStore/Logging/LogFactory.cs b/src/NEventStore/Logging/LogFactory.cs new file mode 100644 index 000000000..630e4965b --- /dev/null +++ b/src/NEventStore/Logging/LogFactory.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; + +namespace NEventStore.Logging +{ + /// + /// Provides the ability to get a new instance of the configured logger. + /// + public static class LogFactory + { + /// + /// Initializes static members of the LogFactory class. + /// + static LogFactory() + { + BuildLogger = _ => NullLogger.Instance; + } + + /// + /// Gets or sets the log builder of the configured logger. + /// This should be invoked to return a new logging instance. + /// + public static Func BuildLogger { get; set; } + } +} \ No newline at end of file diff --git a/src/NEventStore/LoggingWireupExtensions.cs b/src/NEventStore/LoggingWireupExtensions.cs new file mode 100644 index 000000000..e13c4ad80 --- /dev/null +++ b/src/NEventStore/LoggingWireupExtensions.cs @@ -0,0 +1,28 @@ +using Microsoft.Extensions.Logging; +using NEventStore.Logging; + +namespace NEventStore +{ + /// + /// Provides extension methods to configure logging. + /// + public static class LoggingWireupExtensions + { + /// + /// Configures NEventStore to use the specified logger factory. + /// + public static Wireup WithLoggerFactory(this Wireup wireup, ILoggerFactory loggerFactory) + { + return wireup.LogTo(type => loggerFactory.CreateLogger(type)); + } + + /// + /// Configures NEventStore to use the specified logger. + /// + public static Wireup LogTo(this Wireup wireup, Func logger) + { + LogFactory.BuildLogger = logger; + return wireup; + } + } +} \ No newline at end of file diff --git a/src/proj/EventStore.Wireup/Messages.Designer.cs b/src/NEventStore/Messages.Designer.cs similarity index 79% rename from src/proj/EventStore.Wireup/Messages.Designer.cs rename to src/NEventStore/Messages.Designer.cs index b3d268f6a..1bbad3627 100644 --- a/src/proj/EventStore.Wireup/Messages.Designer.cs +++ b/src/NEventStore/Messages.Designer.cs @@ -1,360 +1,396 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.261 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace EventStore { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Messages { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Messages() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("EventStore.Messages", typeof(Messages).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to Adding wireup registration callback.. - /// - internal static string AddingWireupCallback { - get { - return ResourceManager.GetString("AddingWireupCallback", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Adding wireup registration for an object instance of type '{0}'.. - /// - internal static string AddingWireupRegistration { - get { - return ResourceManager.GetString("AddingWireupRegistration", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Configuring the store to dispatch messages asynchronously.. - /// - internal static string AsyncDispatchSchedulerRegistered { - get { - return ResourceManager.GetString("AsyncDispatchSchedulerRegistered", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Attempting to resolve existing instance.. - /// - internal static string AttemptingToResolveInstance { - get { - return ResourceManager.GetString("AttemptingToResolveInstance", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Configuring SQL engine to auto-detect dialect.. - /// - internal static string AutoDetectDialect { - get { - return ResourceManager.GetString("AutoDetectDialect", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Building (and storing) new instance for later calls.. - /// - internal static string BuildingAndStoringNewInstance { - get { - return ResourceManager.GetString("BuildingAndStoringNewInstance", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Building the persistence engine.. - /// - internal static string BuildingEngine { - get { - return ResourceManager.GetString("BuildingEngine", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Building new instance.. - /// - internal static string BuildingNewInstance { - get { - return ResourceManager.GetString("BuildingNewInstance", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Configuring serializer to compress the serialized payload.. - /// - internal static string ConfiguringCompression { - get { - return ResourceManager.GetString("ConfiguringCompression", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Configuring serializer to encrypt the serialized payload.. - /// - internal static string ConfiguringEncryption { - get { - return ResourceManager.GetString("ConfiguringEncryption", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Configuring persistence engine to enlist in ambient transactions using TransactionScope.. - /// - internal static string ConfiguringEngineEnlistment { - get { - return ResourceManager.GetString("ConfiguringEngineEnlistment", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Configuring persistence engine to initialize.. - /// - internal static string ConfiguringEngineInitialization { - get { - return ResourceManager.GetString("ConfiguringEngineInitialization", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Configuring persistence engine to track performance. - /// - internal static string ConfiguringEnginePerformanceTracking { - get { - return ResourceManager.GetString("ConfiguringEnginePerformanceTracking", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Registration configured to resolve a new instance per call.. - /// - internal static string ConfiguringInstancePerCall { - get { - return ResourceManager.GetString("ConfiguringInstancePerCall", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Using SQL connection factory of type '{0}'.. - /// - internal static string ConnectionFactorySpecified { - get { - return ResourceManager.GetString("ConnectionFactorySpecified", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Registering SQL dialect of type '{0}'.. - /// - internal static string DialectSpecified { - get { - return ResourceManager.GetString("DialectSpecified", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Registering dispatcher of type '{0}'.. - /// - internal static string DispatcherRegistered { - get { - return ResourceManager.GetString("DispatcherRegistered", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Configuring the store to upconvert events when fetched.. - /// - internal static string EventUpconverterRegistered { - get { - return ResourceManager.GetString("EventUpconverterRegistered", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Will scan for event upconverters from the following assemblies: '{0}'. - /// - internal static string EventUpconvertersLoadedFrom { - get { - return ResourceManager.GetString("EventUpconvertersLoadedFrom", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Initializing the configured persistence engine.. - /// - internal static string InitializingEngine { - get { - return ResourceManager.GetString("InitializingEngine", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The instance provided cannot be null.. - /// - internal static string InstanceCannotBeNull { - get { - return ResourceManager.GetString("InstanceCannotBeNull", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Persistence engine configured to page every '{0}' records.. - /// - internal static string PagingSpecified { - get { - return ResourceManager.GetString("PagingSpecified", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Registering persistence engine of type '{0}'.. - /// - internal static string RegisteringPersistenceEngine { - get { - return ResourceManager.GetString("RegisteringPersistenceEngine", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Registering wireup instance for service of type '{0}'.. - /// - internal static string RegisteringServiceInstance { - get { - return ResourceManager.GetString("RegisteringServiceInstance", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Registering wireup resolver for service of type '{0}'.. - /// - internal static string RegisteringWireupCallback { - get { - return ResourceManager.GetString("RegisteringWireupCallback", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Resolving instance.. - /// - internal static string ResolvingInstance { - get { - return ResourceManager.GetString("ResolvingInstance", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Attempting to resolve instance for service of type '{0}'.. - /// - internal static string ResolvingService { - get { - return ResourceManager.GetString("ResolvingService", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Configuring the store to dispatch messages synchronously.. - /// - internal static string SyncDispatchSchedulerRegistered { - get { - return ResourceManager.GetString("SyncDispatchSchedulerRegistered", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Only the synchronous dispatcher can enlist in two-phase commits.. - /// - internal static string SynchronousDispatcherTwoPhaseCommits { - get { - return ResourceManager.GetString("SynchronousDispatcherTwoPhaseCommits", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The type provided must be registered as an interface rather than as a concrete type, e.g. "container.Register<IDispatchCommits>(instance);".. - /// - internal static string TypeMustBeInterface { - get { - return ResourceManager.GetString("TypeMustBeInterface", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Unable to resolve requested instance of type '{0}'.. - /// - internal static string UnableToResolve { - get { - return ResourceManager.GetString("UnableToResolve", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Wrapping serializer of type '{0}' in RijndaelSerializer.. - /// - internal static string WrappingSerializerEncryption { - get { - return ResourceManager.GetString("WrappingSerializerEncryption", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Wrapping serializer of type '{0}' in GZipSerializer.. - /// - internal static string WrappingSerializerGZip { - get { - return ResourceManager.GetString("WrappingSerializerGZip", resourceCulture); - } - } - } -} +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace NEventStore { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Messages { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Messages() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("NEventStore.Messages", typeof(Messages).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Adding wireup registration callback.. + /// + internal static string AddingWireupCallback { + get { + return ResourceManager.GetString("AddingWireupCallback", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Adding wireup registration for an object instance of type '{0}'.. + /// + internal static string AddingWireupRegistration { + get { + return ResourceManager.GetString("AddingWireupRegistration", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Attempting to resolve existing instance.. + /// + internal static string AttemptingToResolveInstance { + get { + return ResourceManager.GetString("AttemptingToResolveInstance", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Configuring SQL engine to auto-detect dialect.. + /// + internal static string AutoDetectDialect { + get { + return ResourceManager.GetString("AutoDetectDialect", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Building (and storing) new instance for later calls.. + /// + internal static string BuildingAndStoringNewInstance { + get { + return ResourceManager.GetString("BuildingAndStoringNewInstance", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Building the persistence engine.. + /// + internal static string BuildingEngine { + get { + return ResourceManager.GetString("BuildingEngine", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Building new instance.. + /// + internal static string BuildingNewInstance { + get { + return ResourceManager.GetString("BuildingNewInstance", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Head CommitSequence [{0}] greater or equal than Attempt BucketId [{1}] - CommitSequence [{2}] - StreamId {3} - StreamRevision {4} - Events Count {5}. + /// + internal static string ConcurrencyExceptionCommitSequence { + get { + return ResourceManager.GetString("ConcurrencyExceptionCommitSequence", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Head StreamRevision [{0}] greater or equal than Attempt BucketId [{1}] - StreamId {2} - StreamRevision {3} - Events Count {4}. + /// + internal static string ConcurrencyExceptionStreamRevision { + get { + return ResourceManager.GetString("ConcurrencyExceptionStreamRevision", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Configuring serializer to compress the serialized payload.. + /// + internal static string ConfiguringCompression { + get { + return ResourceManager.GetString("ConfiguringCompression", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Configuring serializer to encrypt the serialized payload.. + /// + internal static string ConfiguringEncryption { + get { + return ResourceManager.GetString("ConfiguringEncryption", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Configuring persistence engine to enlist in ambient transactions using TransactionScope.. + /// + internal static string ConfiguringEngineEnlistment { + get { + return ResourceManager.GetString("ConfiguringEngineEnlistment", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Configuring persistence engine to initialize.. + /// + internal static string ConfiguringEngineInitialization { + get { + return ResourceManager.GetString("ConfiguringEngineInitialization", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Configuring persistence engine to track performance. + /// + internal static string ConfiguringEnginePerformanceTracking { + get { + return ResourceManager.GetString("ConfiguringEnginePerformanceTracking", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Registration configured to resolve a new instance per call.. + /// + internal static string ConfiguringInstancePerCall { + get { + return ResourceManager.GetString("ConfiguringInstancePerCall", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Using SQL connection factory of type '{0}'.. + /// + internal static string ConnectionFactorySpecified { + get { + return ResourceManager.GetString("ConnectionFactorySpecified", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Registering SQL dialect of type '{0}'.. + /// + internal static string DialectSpecified { + get { + return ResourceManager.GetString("DialectSpecified", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Stream: {0} Bucket: {1} Duplicate commit id {2}.. + /// + internal static string DuplicateCommitIdException { + get { + return ResourceManager.GetString("DuplicateCommitIdException", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Configuring the store to upconvert events when fetched.. + /// + internal static string EventUpconverterRegistered { + get { + return ResourceManager.GetString("EventUpconverterRegistered", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Will scan for event upconverters from the following assemblies: '{0}'. + /// + internal static string EventUpconvertersLoadedFrom { + get { + return ResourceManager.GetString("EventUpconvertersLoadedFrom", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cannot only compare {0} with {1}.. + /// + internal static string FailedToCompareCheckpoint { + get { + return ResourceManager.GetString("FailedToCompareCheckpoint", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Initializing the configured persistence engine.. + /// + internal static string InitializingEngine { + get { + return ResourceManager.GetString("InitializingEngine", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The instance provided cannot be null.. + /// + internal static string InstanceCannotBeNull { + get { + return ResourceManager.GetString("InstanceCannotBeNull", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Persistence engine configured to page every '{0}' records.. + /// + internal static string PagingSpecified { + get { + return ResourceManager.GetString("PagingSpecified", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Registering persistence engine of type '{0}'.. + /// + internal static string RegisteringPersistenceEngine { + get { + return ResourceManager.GetString("RegisteringPersistenceEngine", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Registering wireup instance for service of type '{0}'.. + /// + internal static string RegisteringServiceInstance { + get { + return ResourceManager.GetString("RegisteringServiceInstance", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Registering wireup resolver for service of type '{0}'.. + /// + internal static string RegisteringWireupCallback { + get { + return ResourceManager.GetString("RegisteringWireupCallback", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Resolving instance.. + /// + internal static string ResolvingInstance { + get { + return ResourceManager.GetString("ResolvingInstance", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Attempting to resolve instance for service of type '{0}'.. + /// + internal static string ResolvingService { + get { + return ResourceManager.GetString("ResolvingService", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Head CommitSequence [{0}] lesser than Attempt BucketId [{1}] - CommitSequence [{2}] - StreamId {3} - StreamRevision {4} - Events Count {5}. + /// + internal static string StorageExceptionCommitSequence { + get { + return ResourceManager.GetString("StorageExceptionCommitSequence", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Stream EOF: head.StreamRevision [{0}] < attempt.StreamRevision [{1}] - attempt.Events.Count [{2}] - BucketId {3} - StreamId {4} - StreamRevision {5} . + /// + internal static string StorageExceptionEndOfStream { + get { + return ResourceManager.GetString("StorageExceptionEndOfStream", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Registering stream ID hasher of type '{0}'. + /// + internal static string StreamIdHasherSpecified { + get { + return ResourceManager.GetString("StreamIdHasherSpecified", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Stream not found: StreamId {0} bucket {1}.. + /// + internal static string StreamNotFoundException { + get { + return ResourceManager.GetString("StreamNotFoundException", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The type provided must be registered as an interface rather than as a concrete type, e.g. "container.Register<ISomeService>(instance);".. + /// + internal static string TypeMustBeInterface { + get { + return ResourceManager.GetString("TypeMustBeInterface", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unable to resolve requested instance of type '{0}'.. + /// + internal static string UnableToResolve { + get { + return ResourceManager.GetString("UnableToResolve", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Wrapping serializer of type '{0}' in RijndaelSerializer.. + /// + internal static string WrappingSerializerEncryption { + get { + return ResourceManager.GetString("WrappingSerializerEncryption", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Wrapping serializer of type '{0}' in GZipSerializer.. + /// + internal static string WrappingSerializerGZip { + get { + return ResourceManager.GetString("WrappingSerializerGZip", resourceCulture); + } + } + } +} diff --git a/src/proj/EventStore.Wireup/Messages.resx b/src/NEventStore/Messages.resx similarity index 84% rename from src/proj/EventStore.Wireup/Messages.resx rename to src/NEventStore/Messages.resx index 2f54a83d8..eef77689c 100644 --- a/src/proj/EventStore.Wireup/Messages.resx +++ b/src/NEventStore/Messages.resx @@ -1,219 +1,231 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - The instance provided cannot be null. - - - The type provided must be registered as an interface rather than as a concrete type, e.g. "container.Register<IDispatchCommits>(instance);". - - - Configuring the store to dispatch messages asynchronously. - - - Registering dispatcher of type '{0}'. - - - Adding wireup registration for an object instance of type '{0}'. - - - Adding wireup registration callback. - - - Registration configured to resolve a new instance per call. - - - Resolving instance. - - - Building new instance. - - - Attempting to resolve existing instance. - - - Building (and storing) new instance for later calls. - - - Registering wireup resolver for service of type '{0}'. - - - Registering wireup instance for service of type '{0}'. - - - Attempting to resolve instance for service of type '{0}'. - - - Unable to resolve requested instance of type '{0}'. - - - Registering persistence engine of type '{0}'. - - - Configuring persistence engine to initialize. - - - Configuring persistence engine to enlist in ambient transactions using TransactionScope. - - - Building the persistence engine. - - - Initializing the configured persistence engine. - - - Configuring serializer to compress the serialized payload. - - - Wrapping serializer of type '{0}' in GZipSerializer. - - - Configuring serializer to encrypt the serialized payload. - - - Wrapping serializer of type '{0}' in RijndaelSerializer. - - - Configuring SQL engine to auto-detect dialect. - - - Persistence engine configured to page every '{0}' records. - - - Registering SQL dialect of type '{0}'. - - - Using SQL connection factory of type '{0}'. - - - Configuring the store to dispatch messages synchronously. - - - Only the synchronous dispatcher can enlist in two-phase commits. - - - Configuring the store to upconvert events when fetched. - - - Will scan for event upconverters from the following assemblies: '{0}' - - - Configuring persistence engine to track performance - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The instance provided cannot be null. + + + The type provided must be registered as an interface rather than as a concrete type, e.g. "container.Register<ISomeService>(instance);". + + + Adding wireup registration for an object instance of type '{0}'. + + + Adding wireup registration callback. + + + Registration configured to resolve a new instance per call. + + + Resolving instance. + + + Building new instance. + + + Attempting to resolve existing instance. + + + Building (and storing) new instance for later calls. + + + Registering wireup resolver for service of type '{0}'. + + + Registering wireup instance for service of type '{0}'. + + + Attempting to resolve instance for service of type '{0}'. + + + Unable to resolve requested instance of type '{0}'. + + + Registering persistence engine of type '{0}'. + + + Configuring persistence engine to initialize. + + + Configuring persistence engine to enlist in ambient transactions using TransactionScope. + + + Building the persistence engine. + + + Initializing the configured persistence engine. + + + Configuring serializer to compress the serialized payload. + + + Wrapping serializer of type '{0}' in GZipSerializer. + + + Configuring serializer to encrypt the serialized payload. + + + Wrapping serializer of type '{0}' in RijndaelSerializer. + + + Configuring SQL engine to auto-detect dialect. + + + Persistence engine configured to page every '{0}' records. + + + Registering SQL dialect of type '{0}'. + + + Using SQL connection factory of type '{0}'. + + + Configuring the store to upconvert events when fetched. + + + Will scan for event upconverters from the following assemblies: '{0}' + + + Configuring persistence engine to track performance + + + Cannot only compare {0} with {1}. + + + Registering stream ID hasher of type '{0}' + + + Head CommitSequence [{0}] greater or equal than Attempt BucketId [{1}] - CommitSequence [{2}] - StreamId {3} - StreamRevision {4} - Events Count {5} + + + Head StreamRevision [{0}] greater or equal than Attempt BucketId [{1}] - StreamId {2} - StreamRevision {3} - Events Count {4} + + + Stream: {0} Bucket: {1} Duplicate commit id {2}. + + + Head CommitSequence [{0}] lesser than Attempt BucketId [{1}] - CommitSequence [{2}] - StreamId {3} - StreamRevision {4} - Events Count {5} + + + Stream EOF: head.StreamRevision [{0}] < attempt.StreamRevision [{1}] - attempt.Events.Count [{2}] - BucketId {3} - StreamId {4} - StreamRevision {5} + + + Stream not found: StreamId {0} bucket {1}. + \ No newline at end of file diff --git a/src/NEventStore/NEventStore.Core.csproj b/src/NEventStore/NEventStore.Core.csproj new file mode 100644 index 000000000..521e7d9b4 --- /dev/null +++ b/src/NEventStore/NEventStore.Core.csproj @@ -0,0 +1,95 @@ + + + netstandard2.0;net462 + false + NEventStore + NEventStore + false + TRACE;DEBUG + + + NEventStore + NEventStore + NEventStore Dev Team + http://neventstore.org + false + The purpose of the EventStore is to represent a series of events as a stream. NEventStore is a persistence agnostic event sourcing library for .NET. The primary use is most often associated with CQRS. + events, event sourcing, cqrs, storage, persistence, database + + True + true + true + snupkg + true + true + True + True + NEventStore Dev Team + icon.png + Readme.md + https://github.com/NEventStore/NEventStore + git + license.txt + True + True + latest-recommended + + + + + + + + + True + \ + + + True + \ + + + True + \ + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + True + True + Messages.resx + + + True + True + Resources.resx + + + True + True + Messages.resx + + + + + ResXFileCodeGenerator + Messages.Designer.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + + + ResXFileCodeGenerator + Messages.Designer.cs + + + \ No newline at end of file diff --git a/src/NEventStore/NanoContainer.cs b/src/NEventStore/NanoContainer.cs new file mode 100644 index 000000000..2d19ed3f0 --- /dev/null +++ b/src/NEventStore/NanoContainer.cs @@ -0,0 +1,175 @@ +using NEventStore.Logging; +using Microsoft.Extensions.Logging; + +namespace NEventStore +{ + /// + /// Represents a simple IoC container. + /// + public class NanoContainer + { + private static readonly ILogger Logger = LogFactory.BuildLogger(typeof(NanoContainer)); + + private readonly Dictionary _registrations = []; + + /// + /// Registers a service with the container. + /// + public virtual ContainerRegistration Register(Func resolve) + where TService : class + { + if (Logger.IsEnabled(LogLevel.Debug)) + { + Logger.LogDebug(Messages.RegisteringWireupCallback, typeof(TService)); + } + var registration = new ContainerRegistration(c => resolve(c)); + _registrations[typeof(TService)] = registration; + return registration; + } + + /// + /// Registers a service instance with the container. + /// + /// + /// + public virtual ContainerRegistration Register(TService instance) + { + if (Equals(instance, null)) + { + throw new ArgumentNullException(nameof(instance), Messages.InstanceCannotBeNull); + } + + if (!typeof(TService).IsValueType && !typeof(TService).IsInterface) + { + throw new ArgumentException(Messages.TypeMustBeInterface, nameof(instance)); + } + if (Logger.IsEnabled(LogLevel.Debug)) + { + Logger.LogDebug(Messages.RegisteringServiceInstance, typeof(TService)); + } + var registration = new ContainerRegistration(instance); + _registrations[typeof(TService)] = registration; + return registration; + } + + /// + /// Resolves a service from the container. + /// + public virtual TService? Resolve() + { + if (Logger.IsEnabled(LogLevel.Debug)) + { + Logger.LogDebug(Messages.ResolvingService, typeof(TService)); + } + + if (_registrations.TryGetValue(typeof(TService), out ContainerRegistration registration)) + { + var obj = registration.Resolve(this); + return obj != null ? (TService)obj : default; + } + + if (Logger.IsEnabled(LogLevel.Debug)) + { + Logger.LogDebug(Messages.UnableToResolve, typeof(TService)); + } + return default; + } + } + + /// + /// Represents a registration in the container. + /// + public class ContainerRegistration + { + private static readonly ILogger Logger = LogFactory.BuildLogger(typeof(ContainerRegistration)); + private readonly Func? _resolve; + private object? _instance; + private bool _instancePerCall; + + /// + /// Initializes a new instance of the ContainerRegistration class. + /// + public ContainerRegistration(Func resolve) + { + if (resolve is null) + { + throw new ArgumentNullException(nameof(resolve)); + } + + if (Logger.IsEnabled(LogLevel.Trace)) + { + Logger.LogTrace(Messages.AddingWireupCallback); + } + _resolve = resolve; + } + + /// + /// Initializes a new instance of the ContainerRegistration class. + /// + public ContainerRegistration(object instance) + { + if (instance is null) + { + throw new ArgumentNullException(nameof(instance)); + } + + if (Logger.IsEnabled(LogLevel.Trace)) + { + Logger.LogTrace(Messages.AddingWireupRegistration, instance.GetType()); + } + _instance = instance; + } + + /// + /// Configures the registration to be resolved once per call. + /// + public virtual ContainerRegistration InstancePerCall() + { + if (_resolve == null) + { + throw new InvalidOperationException("Cannot configure InstancePerCall() on instance registrations."); + } + if (Logger.IsEnabled(LogLevel.Trace)) + { + Logger.LogTrace(Messages.ConfiguringInstancePerCall); + } + _instancePerCall = true; + return this; + } + + /// + /// Resolves the registration. + /// + public virtual object? Resolve(NanoContainer container) + { + if (Logger.IsEnabled(LogLevel.Trace)) + { + Logger.LogTrace(Messages.ResolvingInstance); + } + if (_instancePerCall) + { + if (Logger.IsEnabled(LogLevel.Trace)) + { + Logger.LogTrace(Messages.BuildingNewInstance); + } + return _resolve!(container); + } + + if (Logger.IsEnabled(LogLevel.Trace)) + { + Logger.LogTrace(Messages.AttemptingToResolveInstance); + } + + if (_instance != null) + { + return _instance; + } + + if (Logger.IsEnabled(LogLevel.Trace)) + { + Logger.LogTrace(Messages.BuildingAndStoringNewInstance); + } + return _instance = _resolve!(container); + } + } +} \ No newline at end of file diff --git a/src/NEventStore/OptimisticEventStore.cs b/src/NEventStore/OptimisticEventStore.cs new file mode 100644 index 000000000..6a81ed99e --- /dev/null +++ b/src/NEventStore/OptimisticEventStore.cs @@ -0,0 +1,297 @@ +using Microsoft.Extensions.Logging; +using NEventStore.Logging; +using NEventStore.Persistence; + +namespace NEventStore +{ + /// + /// An implementation of a store that supports optimistic concurrency. + /// + public class OptimisticEventStore : IStoreEvents, ICommitEvents, ICommitEventsAsync + { + private static readonly ILogger Logger = LogFactory.BuildLogger(typeof(OptimisticEventStore)); + private readonly IPersistStreams _persistence; + private readonly IEnumerable _pipelineHooks; + private readonly IEnumerable _pipelineHooksAsync; + + /// + public virtual IPersistStreams Advanced { get => _persistence; } + + /// + /// Initializes a new instance of the OptimisticEventStore class. + /// + /// + public OptimisticEventStore(IPersistStreams persistence, IEnumerable? pipelineHooks, IEnumerable? pipelineHooksAsync) + { + if (persistence == null) + { + throw new ArgumentNullException(nameof(persistence)); + } + + _pipelineHooks = pipelineHooks ?? []; + _pipelineHooksAsync = pipelineHooksAsync ?? []; + if (_pipelineHooks.Any() || _pipelineHooksAsync.Any()) + { + _persistence = new PipelineHooksAwarePersistStreamsDecorator(persistence, _pipelineHooks, _pipelineHooksAsync); + } + else + { + _persistence = persistence; + } + } + + /// + public virtual IEnumerable GetFrom(string bucketId, string streamId, int minRevision, int maxRevision) + { + return _persistence.GetFrom(bucketId, streamId, minRevision, maxRevision); + } + + /// + /// + /// Pipeline hooks can prevent commits to be persisted by returning false from the PreCommit method. + /// If the pipeline hook prevents the commit from being persisted, the commit will not be persisted and null will be returned. + /// + public virtual ICommit? Commit(CommitAttempt attempt) + { + Guard.NotNull(() => attempt, attempt); + foreach (var hook in _pipelineHooks) + { + if (Logger.IsEnabled(LogLevel.Trace)) + { + Logger.LogTrace(Resources.InvokingPreCommitHooks, attempt.CommitId, hook.GetType()); + } + if (hook.PreCommit(attempt)) + { + continue; + } + + if (Logger.IsEnabled(LogLevel.Information)) + { + Logger.LogInformation(Resources.CommitRejectedByPipelineHook, hook.GetType(), attempt.CommitId); + } + return null; + } + foreach (var hook in _pipelineHooksAsync) + { + if (Logger.IsEnabled(LogLevel.Trace)) + { + Logger.LogTrace(Resources.InvokingPreCommitHooks, attempt.CommitId, hook.GetType()); + } + if (hook.PreCommitAsync(attempt, CancellationToken.None).GetAwaiter().GetResult()) + { + continue; + } + + if (Logger.IsEnabled(LogLevel.Information)) + { + Logger.LogInformation(Resources.CommitRejectedByPipelineHook, hook.GetType(), attempt.CommitId); + } + return null; + } + + if (Logger.IsEnabled(LogLevel.Trace)) + { + Logger.LogTrace(Resources.CommittingAttempt, attempt.CommitId, attempt.Events?.Count ?? 0); + } + var commit = _persistence.Commit(attempt); + + if (commit != null) + { + foreach (var hook in _pipelineHooks) + { + if (Logger.IsEnabled(LogLevel.Trace)) + { + Logger.LogTrace(Resources.InvokingPostCommitPipelineHooks, attempt.CommitId, hook.GetType()); + } + hook.PostCommit(commit); + } + foreach (var hook in _pipelineHooksAsync) + { + if (Logger.IsEnabled(LogLevel.Trace)) + { + Logger.LogTrace(Resources.InvokingPostCommitPipelineHooks, attempt.CommitId, hook.GetType()); + } + hook.PostCommitAsync(commit, CancellationToken.None).GetAwaiter().GetResult(); + } + } + return commit; + } + + /// + public virtual Task GetFromAsync(string bucketId, string streamId, int minRevision, int maxRevision, IAsyncObserver observer, CancellationToken cancellationToken) + { + return _persistence.GetFromAsync(bucketId, streamId, minRevision, maxRevision, observer, cancellationToken); + } + + /// + /// + /// Pipeline hooks can prevent commits to be persisted by returning false from the PreCommit method. + /// If the pipeline hook prevents the commit from being persisted, the commit will not be persisted and null will be returned. + /// + public async Task CommitAsync(CommitAttempt attempt, CancellationToken cancellationToken) + { + Guard.NotNull(() => attempt, attempt); + foreach (var hook in _pipelineHooks) + { + if (Logger.IsEnabled(LogLevel.Trace)) + { + Logger.LogTrace(Resources.InvokingPreCommitHooks, attempt.CommitId, hook.GetType()); + } + if (hook.PreCommit(attempt)) + { + continue; + } + + if (Logger.IsEnabled(LogLevel.Information)) + { + Logger.LogInformation(Resources.CommitRejectedByPipelineHook, hook.GetType(), attempt.CommitId); + } + return null; + } + foreach (var hook in _pipelineHooksAsync) + { + if (Logger.IsEnabled(LogLevel.Trace)) + { + Logger.LogTrace(Resources.InvokingPreCommitHooks, attempt.CommitId, hook.GetType()); + } + if (await hook.PreCommitAsync(attempt, cancellationToken).ConfigureAwait(false)) + { + continue; + } + + if (Logger.IsEnabled(LogLevel.Information)) + { + Logger.LogInformation(Resources.CommitRejectedByPipelineHook, hook.GetType(), attempt.CommitId); + } + return null; + } + + if (Logger.IsEnabled(LogLevel.Trace)) + { + Logger.LogTrace(Resources.CommittingAttempt, attempt.CommitId, attempt.Events?.Count ?? 0); + } + var commit = await _persistence.CommitAsync(attempt, cancellationToken).ConfigureAwait(false); + + if (commit != null) + { + foreach (var hook in _pipelineHooks) + { + if (Logger.IsEnabled(LogLevel.Trace)) + { + Logger.LogTrace(Resources.InvokingPostCommitPipelineHooks, attempt.CommitId, hook.GetType()); + } + hook.PostCommit(commit); + } + foreach (var hook in _pipelineHooksAsync) + { + if (Logger.IsEnabled(LogLevel.Trace)) + { + Logger.LogTrace(Resources.InvokingPostCommitPipelineHooks, attempt.CommitId, hook.GetType()); + } + await hook.PostCommitAsync(commit, cancellationToken).ConfigureAwait(false); + } + } + return commit; + } + + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + public virtual IEventStream CreateStream(string bucketId, string streamId) + { + if (Logger.IsEnabled(LogLevel.Debug)) + { + Logger.LogDebug(Resources.CreatingStream, streamId, bucketId); + } + return new OptimisticEventStream(bucketId, streamId, this, this); + } + + /// + public virtual IEventStream OpenStream(string bucketId, string streamId, int minRevision, int maxRevision) + { + maxRevision = maxRevision <= 0 ? int.MaxValue : maxRevision; + + if (Logger.IsEnabled(LogLevel.Trace)) + { + Logger.LogTrace(Resources.OpeningStreamAtRevision, streamId, bucketId, minRevision, maxRevision); + } + var stream = new OptimisticEventStream(bucketId, streamId, this, this); + stream.Initialize(minRevision, maxRevision); + return stream; + } + + /// + public async Task OpenStreamAsync(string bucketId, string streamId, int minRevision, int maxRevision, CancellationToken cancellationToken) + { + maxRevision = maxRevision <= 0 ? int.MaxValue : maxRevision; + + if (Logger.IsEnabled(LogLevel.Trace)) + { + Logger.LogTrace(Resources.OpeningStreamAtRevision, streamId, bucketId, minRevision, maxRevision); + } + var stream = new OptimisticEventStream(bucketId, streamId, this, this); + await stream.InitializeAsync(minRevision, maxRevision, cancellationToken).ConfigureAwait(false); + return stream; + } + + /// + public virtual IEventStream OpenStream(ISnapshot snapshot, int maxRevision) + { + if (snapshot == null) + { + throw new ArgumentNullException(nameof(snapshot)); + } + + if (Logger.IsEnabled(LogLevel.Trace)) + { + Logger.LogTrace(Resources.OpeningStreamWithSnapshot, snapshot.StreamId, snapshot.BucketId, snapshot.StreamRevision, maxRevision); + } + maxRevision = maxRevision <= 0 ? int.MaxValue : maxRevision; + var stream = new OptimisticEventStream(snapshot.BucketId, snapshot.StreamId, this, this); + stream.Initialize(snapshot, maxRevision); + return stream; + } + + /// + public async Task OpenStreamAsync(ISnapshot snapshot, int maxRevision, CancellationToken cancellationToken) + { + if (snapshot == null) + { + throw new ArgumentNullException(nameof(snapshot)); + } + + if (Logger.IsEnabled(LogLevel.Trace)) + { + Logger.LogTrace(Resources.OpeningStreamWithSnapshot, snapshot.StreamId, snapshot.BucketId, snapshot.StreamRevision, maxRevision); + } + maxRevision = maxRevision <= 0 ? int.MaxValue : maxRevision; + var stream = new OptimisticEventStream(snapshot.BucketId, snapshot.StreamId, this, this); + await stream.InitializeAsync(snapshot, maxRevision, cancellationToken).ConfigureAwait(false); + return stream; + } + + /// + protected virtual void Dispose(bool disposing) + { + if (!disposing) + { + return; + } + + if (Logger.IsEnabled(LogLevel.Information)) + { + Logger.LogInformation(Resources.ShuttingDownStore); + } + _persistence.Dispose(); + foreach (var hook in _pipelineHooks) + { + hook.Dispose(); + } + } + } +} \ No newline at end of file diff --git a/src/NEventStore/OptimisticEventStream.cs b/src/NEventStore/OptimisticEventStream.cs new file mode 100644 index 000000000..a6de10733 --- /dev/null +++ b/src/NEventStore/OptimisticEventStream.cs @@ -0,0 +1,481 @@ +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using Microsoft.Extensions.Logging; +using NEventStore.Logging; + +namespace NEventStore +{ + /// + /// Represents a stream of events that can be committed and appended to. + /// + [SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix", + Justification = "This behaves like a stream--not a .NET 'Stream' object, but a stream nonetheless.")] + public sealed class OptimisticEventStream : IEventStream + { + private static readonly ILogger Logger = LogFactory.BuildLogger(typeof(OptimisticEventStream)); + private readonly ICollection _committed = new LinkedList(); + private readonly ImmutableCollection _committedImmutableWrapper; + private readonly Dictionary _committedHeaders = []; + private readonly ImmutableDictionary _committedHeadersImmutableWrapper; + private readonly ICollection _events = new LinkedList(); + private readonly ImmutableCollection _eventsImmutableWrapper; + private readonly HashSet _identifiers = []; + private readonly ICommitEvents _persistence; + private readonly ICommitEventsAsync _persistenceAsync; + private bool _disposed; + // a stream is considered partial if we haven't read all the events in a commit + private bool _isPartialStream; + + /// + public string BucketId { get; } + /// + public string StreamId { get; } + /// + public int StreamRevision { get; private set; } + /// + public int CommitSequence { get; private set; } + /// + public ICollection CommittedEvents { get => _committedImmutableWrapper; } + /// + public IDictionary CommittedHeaders { get => _committedHeadersImmutableWrapper; } + /// + public ICollection UncommittedEvents { get => _eventsImmutableWrapper; } + /// + public IDictionary UncommittedHeaders { get; } = new Dictionary(); + + /// + /// Create a new instance of the OptimisticEventStream class. + /// + public OptimisticEventStream(string bucketId, string streamId, ICommitEvents persistence, ICommitEventsAsync persistenceAsync) + { + if (string.IsNullOrWhiteSpace(bucketId)) + { + throw new ArgumentException($"'{nameof(bucketId)}' cannot be null or whitespace.", nameof(bucketId)); + } + + if (string.IsNullOrWhiteSpace(streamId)) + { + throw new ArgumentException($"'{nameof(streamId)}' cannot be null or whitespace.", nameof(streamId)); + } + + BucketId = bucketId; + StreamId = streamId; + _persistence = persistence ?? throw new ArgumentNullException(nameof(persistence)); + _persistenceAsync = persistenceAsync ?? throw new ArgumentNullException(nameof(persistenceAsync)); + _committedImmutableWrapper = new ImmutableCollection(_committed); + _eventsImmutableWrapper = new ImmutableCollection(_events); + _committedHeadersImmutableWrapper = new ImmutableDictionary(_committedHeaders); + } + + private void EnsureStreamIsNew() + { + if (_committed.Count > 0 || _events.Count > 0) + { + throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Cannot call Initialize on a stream already used: Bucked: {0}, Stream: {1}, Committed Events: {2}, New Events: {3}", BucketId, StreamId, _committed.Count, _events.Count)); + } + } + + /// + /// Initializes a new instance of the OptimisticEventStream class. + /// + /// + /// + public void Initialize(int minRevision, int maxRevision) + { + EnsureStreamIsNew(); + IEnumerable commits = _persistence.GetFrom(BucketId, StreamId, minRevision, maxRevision); + PopulateStream(minRevision, maxRevision, commits); + + if (minRevision > 0 && _committed.Count == 0) + { + throw new StreamNotFoundException(String.Format(CultureInfo.InvariantCulture, Messages.StreamNotFoundException, StreamId, BucketId)); + } + } + + /// + /// Initializes a new instance of the OptimisticEventStream class. + /// + /// + /// + public async Task InitializeAsync(int minRevision, int maxRevision, CancellationToken cancellationToken) + { + EnsureStreamIsNew(); + _isPartialStream = false; + var observer = new LambdaAsyncObserver( + onNextAsync: (commit, _) => + { + InnerPopulateStream(minRevision, maxRevision, commit); + return Task.FromResult(true); + }); + await _persistenceAsync.GetFromAsync(BucketId, StreamId, minRevision, maxRevision, observer, cancellationToken).ConfigureAwait(false); + + if (minRevision > 0 && _committed.Count == 0) + { + throw new StreamNotFoundException(String.Format(CultureInfo.InvariantCulture, Messages.StreamNotFoundException, StreamId, BucketId)); + } + } + + private void EnsureSnapshotIsForThisStream(ISnapshot snapshot) + { + if (BucketId != snapshot.BucketId || StreamId != snapshot.StreamId) + { + throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "The snapshot is for a different stream. Stream BucketId: {0}, StreamId: {1}; Snapshot BucketId: {2}, StreamId: {3}", BucketId, StreamId, snapshot.BucketId, snapshot.StreamId)); + } + } + + /// + /// Initializes a new instance of the OptimisticEventStream class. + /// + /// + /// + public void Initialize(ISnapshot snapshot, int maxRevision) + { + if (snapshot is null) + { + throw new ArgumentNullException(nameof(snapshot)); + } + EnsureSnapshotIsForThisStream(snapshot); + EnsureStreamIsNew(); + IEnumerable commits = _persistence.GetFrom(snapshot.BucketId, snapshot.StreamId, snapshot.StreamRevision, maxRevision); + PopulateStream(snapshot.StreamRevision + 1, maxRevision, commits); + StreamRevision = snapshot.StreamRevision + _committed.Count; + } + + /// + /// Initializes a new instance of the OptimisticEventStream class. + /// + /// + /// + public async Task InitializeAsync(ISnapshot snapshot, int maxRevision, CancellationToken cancellationToken) + { + if (snapshot is null) + { + throw new ArgumentNullException(nameof(snapshot)); + } + EnsureSnapshotIsForThisStream(snapshot); + EnsureStreamIsNew(); + int minRevision = snapshot.StreamRevision + 1; + _isPartialStream = false; + var observer = new LambdaAsyncObserver( + onNextAsync: (commit, _) => + { + InnerPopulateStream(minRevision, maxRevision, commit); + return Task.FromResult(true); + }); + await _persistenceAsync.GetFromAsync(snapshot.BucketId, snapshot.StreamId, snapshot.StreamRevision, maxRevision, observer, cancellationToken).ConfigureAwait(false); + StreamRevision = snapshot.StreamRevision + _committed.Count; + } + + /// + public void Add(EventMessage uncommittedEvent) + { + if (uncommittedEvent == null) + { + throw new ArgumentNullException(nameof(uncommittedEvent)); + } + + if (uncommittedEvent.Body == null) + { + throw new ArgumentException(nameof(uncommittedEvent.Body)); + } + + if (Logger.IsEnabled(LogLevel.Trace)) + { + Logger.LogTrace(Resources.AppendingUncommittedToStream, uncommittedEvent.Body.GetType(), StreamId, BucketId); + } + _events.Add(uncommittedEvent); + } + + /// + public ICommit? CommitChanges(Guid commitId) + { + if (Logger.IsEnabled(LogLevel.Trace)) + { + Logger.LogTrace(Resources.AttemptingToCommitChanges, StreamId, BucketId); + } + + if (_isPartialStream) + { + if (Logger.IsEnabled(LogLevel.Debug)) + { + Logger.LogDebug(Resources.CannotAddCommitsToPartiallyLoadedStream, StreamId, BucketId, StreamRevision); + } + + RefreshStreamAfterConcurrencyException(); + + throw new ConcurrencyException(string.Format( + CultureInfo.InvariantCulture, + Resources.CannotAddCommitsToPartiallyLoadedStream, + StreamId, + BucketId, + StreamRevision + )); + } + + if (_identifiers.Contains(commitId)) + { + throw new DuplicateCommitException(String.Format(CultureInfo.InvariantCulture, Messages.DuplicateCommitIdException, StreamId, BucketId, commitId)); + } + + if (!HasChanges()) + { + return null; + } + + try + { + return PersistChanges(commitId); + } + catch (ConcurrencyException cex) + { + if (Logger.IsEnabled(LogLevel.Debug)) + { + Logger.LogDebug(Resources.UnderlyingStreamHasChanged, StreamId, BucketId, cex.Message); + } + + RefreshStreamAfterConcurrencyException(); + + throw; + } + } + + /// + public async Task CommitChangesAsync(Guid commitId, CancellationToken cancellationToken) + { + if (Logger.IsEnabled(LogLevel.Trace)) + { + Logger.LogTrace(Resources.AttemptingToCommitChanges, StreamId, BucketId); + } + + if (_isPartialStream) + { + if (Logger.IsEnabled(LogLevel.Debug)) + { + Logger.LogDebug(Resources.CannotAddCommitsToPartiallyLoadedStream, StreamId, BucketId, StreamRevision); + } + + await RefreshStreamAfterConcurrencyExceptionAsync(cancellationToken).ConfigureAwait(false); + + throw new ConcurrencyException(string.Format( + CultureInfo.InvariantCulture, + Resources.CannotAddCommitsToPartiallyLoadedStream, + StreamId, + BucketId, + StreamRevision + )); + } + + if (_identifiers.Contains(commitId)) + { + throw new DuplicateCommitException(String.Format(CultureInfo.InvariantCulture, Messages.DuplicateCommitIdException, StreamId, BucketId, commitId)); + } + + if (!HasChanges()) + { + return null; + } + + try + { + return await PersistChangesAsync(commitId, cancellationToken).ConfigureAwait(false); + } + catch (ConcurrencyException cex) + { + if (Logger.IsEnabled(LogLevel.Debug)) + { + Logger.LogDebug(Resources.UnderlyingStreamHasChanged, StreamId, BucketId, cex.Message); + } + + await RefreshStreamAfterConcurrencyExceptionAsync(cancellationToken).ConfigureAwait(false); + + throw; + } + } + + private void RefreshStreamAfterConcurrencyException() + { + int refreshFromRevision = StreamRevision + 1; + IEnumerable commits = _persistence.GetFrom(BucketId, StreamId, refreshFromRevision, int.MaxValue); + PopulateStream(refreshFromRevision, int.MaxValue, commits); + } + + private Task RefreshStreamAfterConcurrencyExceptionAsync(CancellationToken cancellationToken) + { + int refreshFromRevision = StreamRevision + 1; + const int maxRevision = int.MaxValue; + _isPartialStream = false; + var observer = new LambdaAsyncObserver( + onNextAsync: (commit, _) => + { + InnerPopulateStream(refreshFromRevision, maxRevision, commit); + return Task.FromResult(true); + }); + return _persistenceAsync.GetFromAsync(BucketId, StreamId, refreshFromRevision, maxRevision, observer, cancellationToken); + } + + /// + public void ClearChanges() + { + if (Logger.IsEnabled(LogLevel.Trace)) + { + Logger.LogTrace(Resources.ClearingUncommittedChanges, StreamId, BucketId); + } + _events.Clear(); + UncommittedHeaders.Clear(); + } + + private void PopulateStream(int minRevision, int maxRevision, ICommit commit) + { + _isPartialStream = false; + InnerPopulateStream(minRevision, maxRevision, commit); + } + + private void PopulateStream(int minRevision, int maxRevision, IEnumerable commits) + { + _isPartialStream = false; + foreach (var commit in commits ?? []) + { + InnerPopulateStream(minRevision, maxRevision, commit); + } + } + + private void InnerPopulateStream(int minRevision, int maxRevision, ICommit commit) + { + _identifiers.Add(commit.CommitId); + + int currentRevision = commit.StreamRevision - commit.Events.Count + 1; + // just in case the persistence returned more commits than it should be + if (currentRevision > maxRevision) + { + _isPartialStream = true; + if (Logger.IsEnabled(LogLevel.Debug)) + { + Logger.LogDebug(Resources.IgnoringBeyondRevision, commit.CommitId, StreamId, maxRevision); + } + return; + } + + if (Logger.IsEnabled(LogLevel.Trace)) + { + Logger.LogTrace(Resources.AddingCommitsToStream, commit.CommitId, commit.Events.Count, StreamId, BucketId); + } + + CommitSequence = commit.CommitSequence; + + CopyToCommittedHeaders(commit); + CopyToEvents(minRevision, maxRevision, currentRevision, commit); + } + + private void CopyToCommittedHeaders(ICommit commit) + { + foreach (var key in commit.Headers.Keys) + { + _committedHeaders[key] = commit.Headers[key]; + } + } + + private void CopyToEvents(int minRevision, int maxRevision, int currentRevision, ICommit commit) + { + foreach (var @event in commit.Events) + { + if (currentRevision > maxRevision) + { + _isPartialStream = true; + if (Logger.IsEnabled(LogLevel.Debug)) + { + Logger.LogDebug(Resources.IgnoringBeyondRevision, commit.CommitId, StreamId, maxRevision); + } + break; + } + + if (currentRevision++ < minRevision) + { + if (Logger.IsEnabled(LogLevel.Debug)) + { + Logger.LogDebug(Resources.IgnoringBeforeRevision, commit.CommitId, StreamId, maxRevision); + } + continue; + } + + _committed.Add(@event); + StreamRevision = currentRevision - 1; + } + } + + private bool HasChanges() + { + if (_disposed) + { + throw new ObjectDisposedException(Resources.AlreadyDisposed); + } + + if (_events.Count > 0) + { + return true; + } + + if (Logger.IsEnabled(LogLevel.Information)) + { + Logger.LogInformation(Resources.NoChangesToCommit, StreamId, BucketId); + } + return false; + } + + private ICommit? PersistChanges(Guid commitId) + { + CommitAttempt attempt = BuildCommitAttempt(commitId); + + if (Logger.IsEnabled(LogLevel.Debug)) + { + Logger.LogDebug(Resources.PersistingCommit, commitId, StreamId, BucketId, attempt.Events?.Count ?? 0); + } + var commit = _persistence.Commit(attempt); + if (commit != null) + { + PopulateStream(StreamRevision + 1, attempt.StreamRevision, commit); + ClearChanges(); + } + return commit; + } + + private async Task PersistChangesAsync(Guid commitId, CancellationToken cancellationToken) + { + CommitAttempt attempt = BuildCommitAttempt(commitId); + + if (Logger.IsEnabled(LogLevel.Debug)) + { + Logger.LogDebug(Resources.PersistingCommit, commitId, StreamId, BucketId, attempt.Events?.Count ?? 0); + } + var commit = await _persistenceAsync.CommitAsync(attempt, cancellationToken).ConfigureAwait(false); + if (commit != null) + { + PopulateStream(StreamRevision + 1, attempt.StreamRevision, commit); + ClearChanges(); + } + return commit; + } + + private CommitAttempt BuildCommitAttempt(Guid commitId) + { + if (Logger.IsEnabled(LogLevel.Trace)) + { + Logger.LogTrace(Resources.BuildingCommitAttempt, commitId, StreamId, BucketId); + } + return new CommitAttempt( + BucketId, + StreamId, + StreamRevision + _events.Count, + commitId, + CommitSequence + 1, + SystemTime.UtcNow, + UncommittedHeaders.ToDictionary(x => x.Key, x => x.Value), + _events.ToArray()); // check this for performance: pre-allocate the array size. + } + + /// + public void Dispose() + { + _disposed = true; + } + } +} \ No newline at end of file diff --git a/src/NEventStore/OptimisticPipelineHook.cs b/src/NEventStore/OptimisticPipelineHook.cs new file mode 100644 index 000000000..75338a36a --- /dev/null +++ b/src/NEventStore/OptimisticPipelineHook.cs @@ -0,0 +1,303 @@ +using Microsoft.Extensions.Logging; +using NEventStore.Logging; +using NEventStore.Persistence; +using System.Globalization; + +namespace NEventStore +{ + /// + /// Tracks the heads of streams to reduce latency by avoiding roundtrips to storage. + /// + public class OptimisticPipelineHook : PipelineHookBase + { + internal const int MaxStreamsToTrack = 100; + private static readonly ILogger Logger = LogFactory.BuildLogger(typeof(OptimisticPipelineHook)); + private readonly Dictionary _heads = []; //TODO use concurrent collections + private readonly LinkedList _maxItemsToTrack = new(); + private readonly int _maxStreamsToTrack; + + /// + /// Initializes a new instance of the OptimisticPipelineHook class. + /// + public OptimisticPipelineHook() + : this(MaxStreamsToTrack) + { } + + /// + /// Initializes a new instance of the OptimisticPipelineHook class. + /// + public OptimisticPipelineHook(int maxStreamsToTrack) + { + if (Logger.IsEnabled(LogLevel.Debug)) + { + Logger.LogDebug(Resources.TrackingStreams, maxStreamsToTrack); + } + _maxStreamsToTrack = maxStreamsToTrack; + } + + /// + public override ICommit? SelectCommit(ICommit committed) + { + Track(committed); + return committed; + } + + /// + public override bool PreCommit(CommitAttempt attempt) + { + if (Logger.IsEnabled(LogLevel.Trace)) + { + Logger.LogTrace(Resources.OptimisticConcurrencyCheck, attempt.StreamId); + } + + ICommit head = GetStreamHead(GetHeadKey(attempt)); + if (head == null) + { + return true; + } + + if (head.CommitSequence >= attempt.CommitSequence) + { + throw new ConcurrencyException(String.Format( + CultureInfo.InvariantCulture, + Messages.ConcurrencyExceptionCommitSequence, + head.CommitSequence, + attempt.BucketId, + attempt.CommitSequence, + attempt.StreamId, + attempt.StreamRevision, + attempt.Events.Count + )); + } + + if (head.StreamRevision >= attempt.StreamRevision) + { + throw new ConcurrencyException(String.Format( + CultureInfo.InvariantCulture, + Messages.ConcurrencyExceptionStreamRevision, + head.StreamRevision, + attempt.BucketId, + attempt.StreamId, + attempt.StreamRevision, + attempt.Events.Count + )); + } + + if (head.CommitSequence < attempt.CommitSequence - 1) + { + throw new StorageException(String.Format( + CultureInfo.InvariantCulture, + Messages.StorageExceptionCommitSequence, + head.CommitSequence, + attempt.BucketId, + attempt.CommitSequence, + attempt.StreamId, + attempt.StreamRevision, + attempt.Events.Count + )); // beyond the end of the stream + } + + if (head.StreamRevision < attempt.StreamRevision - attempt.Events.Count) + { + throw new StorageException(String.Format( + CultureInfo.InvariantCulture, + Messages.StorageExceptionEndOfStream, + head.StreamRevision, + attempt.StreamRevision, + attempt.Events.Count, + attempt.BucketId, + attempt.StreamId, + attempt.StreamRevision + )); // beyond the end of the stream + } + + if (Logger.IsEnabled(LogLevel.Trace)) + { + Logger.LogTrace(Resources.NoConflicts, attempt.StreamId, attempt.BucketId); + } + return true; + } + + /// + public override void PostCommit(ICommit committed) + { + Track(committed); + } + + /// + public override void OnPurge(string? bucketId) + { + lock (_maxItemsToTrack) + { + if (bucketId == null) + { + _heads.Clear(); + _maxItemsToTrack.Clear(); + return; + } + HeadKey[] headsInBucket = _heads.Keys.Where(k => k.BucketId == bucketId).ToArray(); + foreach (var head in headsInBucket) + { + RemoveHead(head); + } + } + } + + /// + public override void OnDeleteStream(string bucketId, string streamId) + { + lock (_maxItemsToTrack) + { + RemoveHead(new HeadKey(bucketId, streamId)); + } + } + + /// + protected override void Dispose(bool disposing) + { + _heads.Clear(); + _maxItemsToTrack.Clear(); + base.Dispose(disposing); + } + + /// + public void Track(ICommit committed) + { + if (committed == null) + { + return; + } + + lock (_maxItemsToTrack) + { + UpdateStreamHead(committed); + TrackUpToCapacity(committed); + } + } + + private void UpdateStreamHead(ICommit committed) + { + HeadKey headKey = GetHeadKey(committed); + ICommit head = GetStreamHead(headKey); + if (AlreadyTracked(head)) + { + _maxItemsToTrack.Remove(headKey); + } + + head ??= committed; + head = head.StreamRevision > committed.StreamRevision ? head : committed; + + _heads[headKey] = head; + } + + private void RemoveHead(HeadKey head) + { + _heads.Remove(head); + LinkedListNode node = _maxItemsToTrack.Find(head); // There should only be ever one or none + if (node != null) + { + _maxItemsToTrack.Remove(node); + } + } + + private static bool AlreadyTracked(ICommit head) + { + return head != null; + } + + private void TrackUpToCapacity(ICommit committed) + { + if (Logger.IsEnabled(LogLevel.Trace)) + { + Logger.LogTrace(Resources.TrackingCommit, committed.CommitSequence, committed.StreamId, committed.BucketId); + } + _maxItemsToTrack.AddFirst(GetHeadKey(committed)); + if (_maxItemsToTrack.Count <= _maxStreamsToTrack) + { + return; + } + + HeadKey expired = _maxItemsToTrack.Last.Value; + if (Logger.IsEnabled(LogLevel.Trace)) + { + Logger.LogTrace(Resources.NoLongerTrackingStream, expired.StreamId, expired.BucketId); + } + + _heads.Remove(expired); + _maxItemsToTrack.RemoveLast(); + } + + /// + public bool Contains(ICommit attempt) + { + return GetStreamHead(GetHeadKey(attempt)) != null; + } + + private ICommit GetStreamHead(HeadKey headKey) + { + lock (_maxItemsToTrack) + { + _heads.TryGetValue(headKey, out ICommit head); + return head; + } + } + + private static HeadKey GetHeadKey(ICommit commit) + { + return new HeadKey(commit.BucketId, commit.StreamId); + } + + private static HeadKey GetHeadKey(CommitAttempt commitAttempt) + { + return new HeadKey(commitAttempt.BucketId, commitAttempt.StreamId); + } + + private sealed class HeadKey : IEquatable + { + public string BucketId { get; } + + public string StreamId { get; } + + public HeadKey(string bucketId, string streamId) + { + BucketId = bucketId; + StreamId = streamId; + } + + public bool Equals(HeadKey other) + { + if (other is null) + { + return false; + } + if (ReferenceEquals(this, other)) + { + return true; + } + return String.Equals(BucketId, other.BucketId, StringComparison.Ordinal) + && String.Equals(StreamId, other.StreamId, StringComparison.Ordinal); + } + + public override bool Equals(object obj) + { + if (obj is null) + { + return false; + } + if (ReferenceEquals(this, obj)) + { + return true; + } + return obj is HeadKey headKey && Equals(headKey); + } + + public override int GetHashCode() + { + unchecked + { + return (BucketId.GetHashCode() * 397) ^ StreamId.GetHashCode(); + } + } + } + } +} \ No newline at end of file diff --git a/src/NEventStore/Persistence/Commit.cs b/src/NEventStore/Persistence/Commit.cs new file mode 100644 index 000000000..ee7ab08e6 --- /dev/null +++ b/src/NEventStore/Persistence/Commit.cs @@ -0,0 +1,67 @@ +#pragma warning disable RCS1170 // Use read-only auto-implemented property. + +namespace NEventStore.Persistence +{ + /// + /// Represents a commit to the event store. + /// + public class Commit : ICommit + { + /// + /// Initializes a new instance of the Commit class. + /// + public Commit( + string bucketId, + string streamId, + int streamRevision, + Guid commitId, + int commitSequence, + DateTime commitStamp, + Int64 checkpointToken, + IDictionary? headers, + ICollection? events) + { + BucketId = bucketId; + StreamId = streamId; + StreamRevision = streamRevision; + CommitId = commitId; + CommitSequence = commitSequence; + CommitStamp = commitStamp; + CheckpointToken = checkpointToken; + Headers = headers ?? new Dictionary(); + Events = events ?? Array.Empty(); + //Events = events == null ? + // new ReadOnlyCollection(new List()) : + // new ReadOnlyCollection(new List(events)); + } + + /// + public string BucketId { get; private set; } + + /// + public string StreamId { get; private set; } + + /// + public int StreamRevision { get; private set; } + + /// + public Guid CommitId { get; private set; } + + /// + public int CommitSequence { get; private set; } + + /// + public DateTime CommitStamp { get; private set; } + + /// + public IDictionary Headers { get; private set; } + + /// + public ICollection Events { get; private set; } + + /// + public Int64 CheckpointToken { get; private set; } + } +} + +#pragma warning restore RCS1170 // Use read-only auto-implemented property. \ No newline at end of file diff --git a/src/NEventStore/Persistence/IPersistStreams.cs b/src/NEventStore/Persistence/IPersistStreams.cs new file mode 100644 index 000000000..a8d3b93c6 --- /dev/null +++ b/src/NEventStore/Persistence/IPersistStreams.cs @@ -0,0 +1,37 @@ +namespace NEventStore.Persistence +{ + /// + /// Indicates the ability to adapt the underlying persistence infrastructure to behave like a stream of events. + /// + /// + /// Instances of this class must be designed to be multi-thread safe such that they can be shared between threads. + /// + public interface IPersistStreams : IDisposable + , IPersistStreamsSync + , IPersistStreamsAsync + { + /// + /// Gets a value indicating whether this instance has been disposed of. + /// + bool IsDisposed { get; } + + /// + /// Initializes and prepares the storage for use, if not already performed. + /// + /// + /// Store initialization will be synchronous and should be completed before the method returns. + /// This is to ensure that the storage is ready for use before the built storage instance is registered in a container. + /// Another option will be: remove the Wireup.InitializeStorageEngine() method and let the user call Initialize() or InitializeAsync() explicitly. + /// + /// + /// + void Initialize(); + + /// + /// Completely DESTROYS the contents and schema (if applicable) containing ANY and ALL streams that have been + /// successfully persisted. + /// Use with caution. + /// + void Drop(); + } +} \ No newline at end of file diff --git a/src/NEventStore/Persistence/IPersistStreamsAsync.cs b/src/NEventStore/Persistence/IPersistStreamsAsync.cs new file mode 100644 index 000000000..9cef3cceb --- /dev/null +++ b/src/NEventStore/Persistence/IPersistStreamsAsync.cs @@ -0,0 +1,80 @@ +namespace NEventStore.Persistence +{ + /// + /// Asynchronous Interface: Indicates the ability to adapt the underlying persistence infrastructure to behave like a stream of events. + /// + /// + /// Instances of this class must be designed to be multi-thread safe such that they can be shared between threads. + /// + public interface IPersistStreamsAsync : ICommitEventsAsync, IAccessSnapshotsAsync + { + /// + /// Gets all commits (from all the buckets) after the specified checkpoint (excluded). Use 0 to get from the beginning. + /// + /// The checkpoint token: all the commits after this one will be returned. + /// The observer to receive the commits. + /// The token to monitor for cancellation requests. + /// An enumerable of Commits. + /// + /// + Task GetFromAsync(Int64 checkpointToken, IAsyncObserver asyncObserver, CancellationToken cancellationToken = default); + + /// + /// Gets all commits (from all the buckets) after the specified checkpoint token (excluded) up to the specified end checkpoint token (included). + /// + /// The checkpoint token: all the commits after this one will be returned + /// The checkpoint token: all the commits tp to this one (included) will be returned + /// The observer to receive the commits. + /// The token to monitor for cancellation requests. + /// All commits that have occurred on or after the specified checkpoint token up to the specified end checkpoint token. + /// + /// + Task GetFromToAsync(Int64 fromCheckpointToken, Int64 toCheckpointToken, IAsyncObserver asyncObserver, CancellationToken cancellationToken = default); + + /// + /// Gets all commits after the specified checkpoint (excluded) for a specific bucket. Use 0 to get from the beginning. + /// + /// The value which uniquely identifies bucket the stream belongs to. + /// The checkpoint token: all the commits after this one will be returned + /// The observer to receive the commits. + /// The token to monitor for cancellation requests. + /// An enumerable of Commits. + /// + /// + Task GetFromAsync(string bucketId, Int64 checkpointToken, IAsyncObserver asyncObserver, CancellationToken cancellationToken = default); + + /// + /// Gets all commits after the specified checkpoint token (excluded) up to the specified end checkpoint token (included). + /// + /// The value which uniquely identifies bucket the stream belongs to. + /// The checkpoint token: all the commits after this one will be returned + /// The checkpoint token: all the commits tp to this one (included) will be returned + /// The observer to receive the commits. + /// The token to monitor for cancellation requests. + /// All commits that have occurred on or after the specified checkpoint token up to the specified end checkpoint token. + /// + /// + Task GetFromToAsync(string bucketId, Int64 fromCheckpointToken, Int64 toCheckpointToken, IAsyncObserver asyncObserver, CancellationToken cancellationToken = default); + + /// + /// Completely DESTROYS the contents of ANY and ALL streams that have been successfully persisted. + /// Use with caution. + /// + Task PurgeAsync(CancellationToken cancellationToken = default); + + /// + /// Completely DESTROYS the contents of ANY and ALL streams that have been successfully persisted + /// in the specified bucket. + /// Use with caution. + /// + Task PurgeAsync(string bucketId, CancellationToken cancellationToken = default); + + /// + /// Deletes a stream. + /// + /// The bucket Id from which the stream is to be deleted. + /// The stream Id of the stream that is to be deleted. + /// The token to monitor for cancellation requests. + Task DeleteStreamAsync(string bucketId, string streamId, CancellationToken cancellationToken = default); + } +} \ No newline at end of file diff --git a/src/NEventStore/Persistence/IPersistStreamsSync.cs b/src/NEventStore/Persistence/IPersistStreamsSync.cs new file mode 100644 index 000000000..b6f504ded --- /dev/null +++ b/src/NEventStore/Persistence/IPersistStreamsSync.cs @@ -0,0 +1,94 @@ +namespace NEventStore.Persistence +{ + /// + /// Synchronous Interface: Indicates the ability to adapt the underlying persistence infrastructure to behave like a stream of events. + /// + /// + /// Instances of this class must be designed to be multi-thread safe such that they can be shared between threads. + /// + public interface IPersistStreamsSync : ICommitEvents, IAccessSnapshots + { + /// + /// Gets all commits on or after the specified starting time. + /// + /// The value which uniquely identifies bucket the stream belongs to. + /// The point in time at which to start. + /// All commits that have occurred on or after the specified starting time. + /// + /// + [Obsolete("DateTime is problematic in distributed systems. Use GetFrom(Int64 checkpointToken) instead. This method will be removed in a later version.")] + IEnumerable GetFrom(string bucketId, DateTime startDate); + + /// + /// Gets all commits on or after the specified starting time and before the specified end time. + /// + /// The value which uniquely identifies bucket the stream belongs to. + /// The point in time at which to start. + /// The point in time at which to end. + /// All commits that have occurred on or after the specified starting time and before the end time. + /// + /// + [Obsolete("DateTime is problematic in distributed systems. Use GetFromTo(Int64 fromCheckpointToken, Int64 toCheckpointToken) instead. This method will be removed in a later version.")] + IEnumerable GetFromTo(string bucketId, DateTime startDate, DateTime endDate); + + /// + /// Gets all commits (from all the buckets) after the specified checkpoint (excluded). Use 0 to get from the beginning. + /// + /// The checkpoint token: all the commits after this one will be returned. + /// An enumerable of Commits. + /// + /// + IEnumerable GetFrom(Int64 checkpointToken); + + /// + /// Gets all commits (from all the buckets) after the specified checkpoint token (excluded) up to the specified end checkpoint token (included). + /// + /// The checkpoint token: all the commits after this one will be returned + /// The checkpoint token: all the commits tp to this one (included) will be returned + /// All commits that have occurred on or after the specified checkpoint token up to the specified end checkpoint token. + /// + /// + IEnumerable GetFromTo(Int64 fromCheckpointToken, Int64 toCheckpointToken); + + /// + /// Gets all commits after the specified checkpoint (excluded) for a specific bucket. Use 0 to get from the beginning. + /// + /// The value which uniquely identifies bucket the stream belongs to. + /// The checkpoint token: all the commits after this one will be returned + /// An enumerable of Commits. + /// + /// + IEnumerable GetFrom(string bucketId, Int64 checkpointToken); + + /// + /// Gets all commits after the specified checkpoint token (excluded) up to the specified end checkpoint token (included). + /// + /// The value which uniquely identifies bucket the stream belongs to. + /// The checkpoint token: all the commits after this one will be returned + /// The checkpoint token: all the commits tp to this one (included) will be returned + /// All commits that have occurred on or after the specified checkpoint token up to the specified end checkpoint token. + /// + /// + IEnumerable GetFromTo(string bucketId, Int64 fromCheckpointToken, Int64 toCheckpointToken); + + /// + /// Completely DESTROYS the contents of ANY and ALL streams that have been successfully persisted. + /// Use with caution. + /// + void Purge(); + + /// + /// Completely DESTROYS the contents of ANY and ALL streams that have been successfully persisted + /// in the specified bucket. + /// Use with caution. + /// + void Purge(string bucketId); + + /// + /// Deletes a stream. + /// + /// The bucket Id from which the stream is to be deleted. + /// The stream Id of the stream that is to be deleted. + void DeleteStream(string bucketId, string streamId); + } +} \ No newline at end of file diff --git a/src/NEventStore/Persistence/IPersistenceFactory.cs b/src/NEventStore/Persistence/IPersistenceFactory.cs new file mode 100644 index 000000000..eb4eff4b1 --- /dev/null +++ b/src/NEventStore/Persistence/IPersistenceFactory.cs @@ -0,0 +1,17 @@ +namespace NEventStore.Persistence +{ + /// + /// Indicates the ability to build a ready-to-use persistence engine. + /// + /// + /// Instances of this class must be designed to be multi-thread safe such that they can be shared between threads. + /// + public interface IPersistenceFactory + { + /// + /// Builds a persistence engine. + /// + /// A ready-to-use persistence engine. + IPersistStreams Build(); + } +} \ No newline at end of file diff --git a/src/NEventStore/Persistence/IStreamHead.cs b/src/NEventStore/Persistence/IStreamHead.cs new file mode 100644 index 000000000..c70a329ed --- /dev/null +++ b/src/NEventStore/Persistence/IStreamHead.cs @@ -0,0 +1,28 @@ +namespace NEventStore.Persistence +{ + /// + /// Indicates the most recent information representing the head of a given stream. + /// + public interface IStreamHead + { + /// + /// Gets the value which uniquely identifies the stream where the last snapshot exceeds the allowed threshold. + /// + string BucketId { get; } + + /// + /// Gets the value which uniquely identifies the stream where the last snapshot exceeds the allowed threshold. + /// + string StreamId { get; } + + /// + /// Gets the value which indicates the revision, length, or number of events committed to the stream. + /// + int HeadRevision { get; } + + /// + /// Gets the value which indicates the revision at which the last snapshot was taken. + /// + int SnapshotRevision { get; } + } +} \ No newline at end of file diff --git a/src/NEventStore/Persistence/InMemory/InMemoryPersistenceEngine.cs b/src/NEventStore/Persistence/InMemory/InMemoryPersistenceEngine.cs new file mode 100644 index 000000000..fbfe3fef9 --- /dev/null +++ b/src/NEventStore/Persistence/InMemory/InMemoryPersistenceEngine.cs @@ -0,0 +1,720 @@ +using System.Collections.Concurrent; +using System.Globalization; +using Microsoft.Extensions.Logging; +using NEventStore.Logging; + +namespace NEventStore.Persistence.InMemory +{ + /// + /// Represents an in-memory persistence engine. + /// + public class InMemoryPersistenceEngine : IPersistStreams + { + private static readonly ILogger Logger = LogFactory.BuildLogger(typeof(InMemoryPersistenceEngine)); + private readonly ConcurrentDictionary _buckets = new(); + private bool _disposed; + private int _checkpoint; + + private Bucket this[string bucketId] + { + get { return _buckets.GetOrAdd(bucketId, _ => new Bucket()); } + } + + /// + /// Disposes the engine. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Initializes the engine. + /// + public void Initialize() + { + if (Logger.IsEnabled(LogLevel.Information)) + { + Logger.LogInformation(Resources.InitializingEngine); + } + } + + /// + [Obsolete("DateTime is problematic in distributed systems. Use GetFrom(Int64 checkpointToken) instead. This method will be removed in a later version.")] + public IEnumerable GetFrom(string bucketId, DateTime startDate) + { + ThrowWhenDisposed(); + if (Logger.IsEnabled(LogLevel.Debug)) + { + Logger.LogDebug(Resources.GettingAllCommitsFromTime, bucketId, startDate); + } + return this[bucketId].GetFrom(startDate); + } + + /// + [Obsolete("DateTime is problematic in distributed systems. Use GetFromTo(Int64 fromCheckpointToken, Int64 toCheckpointToken) instead. This method will be removed in a later version.")] + public IEnumerable GetFromTo(string bucketId, DateTime startDate, DateTime endDate) + { + ThrowWhenDisposed(); + if (Logger.IsEnabled(LogLevel.Debug)) + { + Logger.LogDebug(Resources.GettingAllCommitsFromToTime, startDate, endDate); + } + return this[bucketId].GetFromTo(startDate, endDate); + } + + /// + public IEnumerable GetFrom(Int64 checkpointToken) + { + ThrowWhenDisposed(); + if (Logger.IsEnabled(LogLevel.Debug)) + { + Logger.LogDebug(Resources.GettingAllCommitsFromCheckpoint, checkpointToken); + } + return _buckets + .Values + .SelectMany(b => b.GetCommits()) + .Where(c => c.CheckpointToken.CompareTo(checkpointToken) > 0) + .OrderBy(c => c.CheckpointToken) + .ToArray(); + } + + /// + public Task GetFromAsync(Int64 checkpointToken, IAsyncObserver asyncObserver, CancellationToken cancellationToken) + { + return ObserveDataStream(() => GetFrom(checkpointToken), asyncObserver, cancellationToken); + } + + /// + public IEnumerable GetFromTo(Int64 fromCheckpointToken, Int64 toCheckpointToken) + { + ThrowWhenDisposed(); + if (Logger.IsEnabled(LogLevel.Debug)) + { + Logger.LogDebug(Resources.GettingCommitsFromToCheckpoint, fromCheckpointToken, toCheckpointToken); + } + return _buckets + .Values + .SelectMany(b => b.GetCommits()) + .Where(c => c.CheckpointToken.CompareTo(fromCheckpointToken) > 0 && c.CheckpointToken.CompareTo(toCheckpointToken) <= 0) + .OrderBy(c => c.CheckpointToken) + .ToArray(); + } + + /// + public Task GetFromToAsync(Int64 fromCheckpointToken, Int64 toCheckpointToken, IAsyncObserver asyncObserver, CancellationToken cancellationToken) + { + return ObserveDataStream(() => GetFromTo(fromCheckpointToken, toCheckpointToken), asyncObserver, cancellationToken); + } + + /// + public IEnumerable GetFrom(string bucketId, Int64 checkpointToken) + { + ThrowWhenDisposed(); + if (Logger.IsEnabled(LogLevel.Debug)) + { + Logger.LogDebug(Resources.GettingAllCommitsFromBucketAndCheckpoint, bucketId, checkpointToken); + } + return this[bucketId].GetFrom(checkpointToken); + } + + /// + public Task GetFromAsync(string bucketId, Int64 checkpointToken, IAsyncObserver asyncObserver, CancellationToken cancellationToken) + { + return ObserveDataStream(() => GetFrom(bucketId, checkpointToken), asyncObserver, cancellationToken); + } + + /// + public IEnumerable GetFromTo(string bucketId, Int64 fromCheckpointToken, Int64 toCheckpointToken) + { + ThrowWhenDisposed(); + if (Logger.IsEnabled(LogLevel.Debug)) + { + Logger.LogDebug(Resources.GettingCommitsFromBucketAndFromToCheckpoint, bucketId, fromCheckpointToken, toCheckpointToken); + } + return this[bucketId].GetFromTo(fromCheckpointToken, toCheckpointToken); + } + + /// + public Task GetFromToAsync(string bucketId, long fromCheckpointToken, long toCheckpointToken, IAsyncObserver asyncObserver, CancellationToken cancellationToken) + { + return ObserveDataStream(() => GetFromTo(bucketId, fromCheckpointToken, toCheckpointToken), asyncObserver, cancellationToken); + } + + /// + public IEnumerable GetFrom(string bucketId, string streamId, int minRevision, int maxRevision) + { + ThrowWhenDisposed(); + if (Logger.IsEnabled(LogLevel.Debug)) + { + Logger.LogDebug(Resources.GettingAllCommitsFromRevision, streamId, bucketId, minRevision, maxRevision); + } + return this[bucketId].GetFrom(streamId, minRevision, maxRevision); + } + + /// + public ICommit? Commit(CommitAttempt attempt) + { + ThrowWhenDisposed(); + if (Logger.IsEnabled(LogLevel.Debug)) + { + Logger.LogDebug(Resources.AttemptingToCommit, attempt.CommitId, attempt.StreamId, attempt.BucketId, attempt.CommitSequence); + } + return this[attempt.BucketId].Commit(attempt, Interlocked.Increment(ref _checkpoint)); + } + + /// + public Task GetFromAsync(string bucketId, string streamId, int minRevision, int maxRevision, IAsyncObserver observer, CancellationToken cancellationToken) + { + return ObserveDataStream(() => GetFrom(bucketId, streamId, minRevision, maxRevision), observer, cancellationToken); + } + + private static async Task ObserveDataStream(Func> dataProvider, IAsyncObserver observer, CancellationToken cancellationToken) + { + try + { + var data = dataProvider(); + if (data?.Any() == true) + { + foreach (var commit in data) + { + if (cancellationToken.IsCancellationRequested) + { + throw new OperationCanceledException("Operation Cancellation Requested"); + } + var goOn = await observer.OnNextAsync(commit, cancellationToken).ConfigureAwait(false); + if (!goOn) + { + break; + } + if (cancellationToken.IsCancellationRequested) + { + throw new OperationCanceledException("Operation Cancellation Requested"); + } + } + } + await observer.OnCompletedAsync(cancellationToken).ConfigureAwait(false); + } + catch (Exception ex) + { + await observer.OnErrorAsync(ex, cancellationToken).ConfigureAwait(false); + } + } + + /// + public Task CommitAsync(CommitAttempt attempt, CancellationToken cancellationToken) + { + return Task.FromResult(Commit(attempt)); + } + + /// + public ISnapshot? GetSnapshot(string bucketId, string streamId, int maxRevision) + { + ThrowWhenDisposed(); + if (Logger.IsEnabled(LogLevel.Debug)) + { + Logger.LogDebug(Resources.GettingSnapshotForStream, bucketId, streamId, maxRevision); + } + return this[bucketId].GetSnapshot(streamId, maxRevision); + } + + /// + public bool AddSnapshot(ISnapshot snapshot) + { + ThrowWhenDisposed(); + if (Logger.IsEnabled(LogLevel.Debug)) + { + Logger.LogDebug(Resources.AddingSnapshot, snapshot.BucketId, snapshot.StreamId, snapshot.StreamRevision); + } + return this[snapshot.BucketId].AddSnapshot(snapshot); + } + + /// + public IEnumerable GetStreamsToSnapshot(string bucketId, int maxThreshold) + { + ThrowWhenDisposed(); + if (Logger.IsEnabled(LogLevel.Debug)) + { + Logger.LogDebug(Resources.GettingStreamsToSnapshot, bucketId, maxThreshold); + } + return this[bucketId].GetStreamsToSnapshot(maxThreshold); + } + + /// + public Task GetSnapshotAsync(string bucketId, string streamId, int maxRevision, CancellationToken cancellationToken) + { + ThrowWhenDisposed(); + if (Logger.IsEnabled(LogLevel.Debug)) + { + Logger.LogDebug(Resources.GettingSnapshotForStream, bucketId, streamId, maxRevision); + } + return Task.FromResult(this[bucketId].GetSnapshot(streamId, maxRevision)); + } + + /// + public Task AddSnapshotAsync(ISnapshot snapshot, CancellationToken cancellationToken) + { + ThrowWhenDisposed(); + if (Logger.IsEnabled(LogLevel.Debug)) + { + Logger.LogDebug(Resources.AddingSnapshot, snapshot.BucketId, snapshot.StreamId, snapshot.StreamRevision); + } + return Task.FromResult(this[snapshot.BucketId].AddSnapshot(snapshot)); + } + + /// + public async Task GetStreamsToSnapshotAsync(string bucketId, int maxThreshold, IAsyncObserver asyncObserver, CancellationToken cancellationToken) + { + try + { + var data = GetStreamsToSnapshot(bucketId, maxThreshold); + if (data?.Any() == true) + { + foreach (var commit in data) + { + if (cancellationToken.IsCancellationRequested) + { + throw new OperationCanceledException("Operation Cancellation Requested"); + } + var goOn = await asyncObserver.OnNextAsync(commit, cancellationToken).ConfigureAwait(false); + if (!goOn) + { + break; + } + if (cancellationToken.IsCancellationRequested) + { + throw new OperationCanceledException("Operation Cancellation Requested"); + } + } + } + await asyncObserver.OnCompletedAsync(cancellationToken).ConfigureAwait(false); + } + catch (Exception ex) + { + await asyncObserver.OnErrorAsync(ex, cancellationToken).ConfigureAwait(false); + } + } + + /// + public void Purge() + { + ThrowWhenDisposed(); + if (Logger.IsEnabled(LogLevel.Warning)) + { + Logger.LogWarning(Resources.PurgingStore); + } + + foreach (var bucket in _buckets.Values) + { + bucket.Purge(); + } + } + + /// + public void Purge(string bucketId) + { + _buckets.TryRemove(bucketId, out var _); + } + + /// + public Task PurgeAsync(CancellationToken cancellationToken) + { + Purge(); + return Task.CompletedTask; + } + + /// + public Task PurgeAsync(string bucketId, CancellationToken cancellationToken) + { + Purge(bucketId); + return Task.CompletedTask; + } + + /// + public void Drop() + { + _buckets.Clear(); + } + + /// + public void DeleteStream(string bucketId, string streamId) + { + if (Logger.IsEnabled(LogLevel.Warning)) + { + Logger.LogWarning(Resources.DeletingStream, streamId, bucketId); + } + if (!_buckets.TryGetValue(bucketId, out Bucket bucket)) + { + return; + } + bucket.DeleteStream(streamId); + } + + /// + public Task DeleteStreamAsync(string bucketId, string streamId, CancellationToken cancellationToken) + { + DeleteStream(bucketId, streamId); + return Task.CompletedTask; + } + + /// + public bool IsDisposed + { + get { return _disposed; } + } + +#pragma warning disable RCS1163 // Unused parameter. +#pragma warning disable IDE0060 // Remove unused parameter + private void Dispose(bool disposing) +#pragma warning restore IDE0060 // Remove unused parameter +#pragma warning restore RCS1163 // Unused parameter. + { + _disposed = true; + if (Logger.IsEnabled(LogLevel.Information)) + { + Logger.LogInformation(Resources.DisposingEngine); + } + } + + private void ThrowWhenDisposed() + { + if (!_disposed) + { + return; + } + if (Logger.IsEnabled(LogLevel.Warning)) + { + Logger.LogWarning(Resources.AlreadyDisposed); + } + throw new ObjectDisposedException(Resources.AlreadyDisposed); + } + + private class InMemoryCommit : Commit + { + public InMemoryCommit( + string bucketId, + string streamId, + int streamRevision, + Guid commitId, + int commitSequence, + DateTime commitStamp, + Int64 checkpointToken, + IDictionary headers, + ICollection events) + : base(bucketId, streamId, streamRevision, commitId, commitSequence, commitStamp, checkpointToken, headers, events) + { } + } + + private class IdentityForConcurrencyConflictDetection + { + protected bool Equals(IdentityForConcurrencyConflictDetection other) + { + return string.Equals(this.streamId, other.streamId, StringComparison.Ordinal) + && string.Equals(this.bucketId, other.bucketId, StringComparison.Ordinal) + && this.commitSequence == other.commitSequence; + } + + public override bool Equals(object obj) + { + if (obj is null) + { + return false; + } + if (ReferenceEquals(this, obj)) + { + return true; + } + if (obj.GetType() != this.GetType()) + { + return false; + } + return Equals((IdentityForConcurrencyConflictDetection)obj); + } + + public override int GetHashCode() + { + unchecked + { + int hashCode = this.streamId.GetHashCode(); + hashCode = (hashCode * 397) ^ this.bucketId.GetHashCode(); + return (hashCode * 397) ^ this.commitSequence; + } + } + + private readonly int commitSequence; + + private readonly string bucketId; + + private readonly string streamId; + + public IdentityForConcurrencyConflictDetection(CommitAttempt commitAttempt) + { + bucketId = commitAttempt.BucketId; + streamId = commitAttempt.StreamId; + commitSequence = commitAttempt.CommitSequence; + } + + public IdentityForConcurrencyConflictDetection(Commit commit) + { + bucketId = commit.BucketId; + streamId = commit.StreamId; + commitSequence = commit.CommitSequence; + } + } + + private class IdentityForDuplicationDetection + { + protected bool Equals(IdentityForDuplicationDetection other) + { + return string.Equals(this.streamId, other.streamId, StringComparison.Ordinal) + && string.Equals(this.bucketId, other.bucketId, StringComparison.Ordinal) + && this.commitId.Equals(other.commitId); + } + + public override bool Equals(object obj) + { + if (obj is null) + { + return false; + } + if (ReferenceEquals(this, obj)) + { + return true; + } + if (obj.GetType() != this.GetType()) + { + return false; + } + return Equals((IdentityForDuplicationDetection)obj); + } + + public override int GetHashCode() + { + unchecked + { + int hashCode = this.streamId.GetHashCode(); + hashCode = (hashCode * 397) ^ this.bucketId.GetHashCode(); + return (hashCode * 397) ^ this.commitId.GetHashCode(); + } + } + + private readonly Guid commitId; + + private readonly string bucketId; + + private readonly string streamId; + + public IdentityForDuplicationDetection(CommitAttempt commitAttempt) + { + bucketId = commitAttempt.BucketId; + streamId = commitAttempt.StreamId; + commitId = commitAttempt.CommitId; + } + + public IdentityForDuplicationDetection(Commit commit) + { + bucketId = commit.BucketId; + streamId = commit.StreamId; + commitId = commit.CommitId; + } + } + + private class Bucket + { + private readonly List _commits = []; + private readonly HashSet _potentialDuplicates = []; + private readonly HashSet _potentialConflicts = []; + + public IEnumerable GetCommits() + { + lock (_commits) + { + return _commits.ToArray(); + } + } + + private readonly ICollection _heads = new LinkedList(); + private readonly ICollection _snapshots = new LinkedList(); + private readonly Dictionary _stamps = []; + + public IEnumerable GetFrom(string streamId, int minRevision, int maxRevision) + { + lock (_commits) + { + return _commits + .Where(x => x.StreamId == streamId && x.StreamRevision >= minRevision && (x.StreamRevision - x.Events.Count + 1) <= maxRevision) + .OrderBy(c => c.CommitSequence) + .ToArray(); + } + } + + public IEnumerable GetFrom(DateTime start) + { + Guid commitId = _stamps.Where(x => x.Value >= start).Select(x => x.Key).FirstOrDefault(); + if (commitId == Guid.Empty) + { + return []; + } + + InMemoryCommit startingCommit = _commits.FirstOrDefault(x => x.CommitId == commitId); + return _commits.Skip(_commits.IndexOf(startingCommit)); + } + + public IEnumerable GetFrom(Int64 checkpoint) + { + InMemoryCommit startingCommit = _commits.FirstOrDefault(x => x.CheckpointToken.CompareTo(checkpoint) == 0); + return _commits.Skip(_commits.IndexOf(startingCommit) + 1 /* GetFrom => after the checkpoint*/); + } + + public IEnumerable GetFromTo(Int64 from, Int64 to) + { + InMemoryCommit startingCommit = _commits.FirstOrDefault(x => x.CheckpointToken.CompareTo(from) == 0); + return _commits.Skip(_commits.IndexOf(startingCommit) + 1 /* GetFrom => after the checkpoint*/) + .TakeWhile(c => c.CheckpointToken <= to); + } + + public IEnumerable GetFromTo(DateTime start, DateTime end) + { + IEnumerable selectedCommitIds = _stamps.Where(x => x.Value >= start && x.Value < end).Select(x => x.Key).ToArray(); + Guid firstCommitId = selectedCommitIds.FirstOrDefault(); + Guid lastCommitId = selectedCommitIds.LastOrDefault(); + if (lastCommitId == Guid.Empty && lastCommitId == Guid.Empty) + { + return []; + } + InMemoryCommit startingCommit = _commits.FirstOrDefault(x => x.CommitId == firstCommitId); + InMemoryCommit endingCommit = _commits.FirstOrDefault(x => x.CommitId == lastCommitId); + int startingCommitIndex = (startingCommit == null) ? 0 : _commits.IndexOf(startingCommit); + int endingCommitIndex = (endingCommit == null) ? _commits.Count - 1 : _commits.IndexOf(endingCommit); + int numberToTake = endingCommitIndex - startingCommitIndex + 1; + + return _commits.Skip(startingCommitIndex).Take(numberToTake); + } + + public Commit Commit(CommitAttempt attempt, Int64 checkpoint) + { + lock (_commits) + { + DetectDuplicate(attempt); + var commit = new InMemoryCommit(attempt.BucketId, + attempt.StreamId, + attempt.StreamRevision, + attempt.CommitId, + attempt.CommitSequence, + attempt.CommitStamp, + checkpoint, + attempt.Headers, + attempt.Events); + if (_potentialConflicts.Contains(new IdentityForConcurrencyConflictDetection(commit))) + { + throw new ConcurrencyException(); + } + _stamps[commit.CommitId] = commit.CommitStamp; + _commits.Add(commit); + _potentialDuplicates.Add(new IdentityForDuplicationDetection(commit)); + _potentialConflicts.Add(new IdentityForConcurrencyConflictDetection(commit)); + IStreamHead head = _heads.FirstOrDefault(x => x.StreamId == commit.StreamId); + _heads.Remove(head); + if (Logger.IsEnabled(LogLevel.Debug)) + { + Logger.LogDebug(Resources.UpdatingStreamHead, commit.StreamId, commit.BucketId); + } + int snapshotRevision = head?.SnapshotRevision ?? 0; + _heads.Add(new StreamHead(commit.BucketId, commit.StreamId, commit.StreamRevision, snapshotRevision)); + return commit; + } + } + + private void DetectDuplicate(CommitAttempt attempt) + { + if (_potentialDuplicates.Contains(new IdentityForDuplicationDetection(attempt))) + { + throw new DuplicateCommitException(String.Format( + CultureInfo.InvariantCulture, + Messages.DuplicateCommitIdException, attempt.StreamId, attempt.BucketId, attempt.CommitId)); + } + } + + public IEnumerable GetStreamsToSnapshot(int maxThreshold) + { + lock (_commits) + { + return _heads + .Where(x => x.HeadRevision >= x.SnapshotRevision + maxThreshold) + .Select(stream => new StreamHead(stream.BucketId, stream.StreamId, stream.HeadRevision, stream.SnapshotRevision)); + } + } + + public ISnapshot? GetSnapshot(string streamId, int maxRevision) + { + lock (_commits) + { + return _snapshots + .Where(x => x.StreamId == streamId && x.StreamRevision <= maxRevision) + .OrderByDescending(x => x.StreamRevision) + .FirstOrDefault(); + } + } + + public bool AddSnapshot(ISnapshot snapshot) + { + lock (_commits) + { + IStreamHead currentHead = _heads.FirstOrDefault(h => h.StreamId == snapshot.StreamId); + if (currentHead == null) + { + return false; + } + + // if the snapshot is already there do NOT add it (follow the SQL implementation) + // and the original GetSnapshot behavior which was to return the first one that was + // added to the collection + if (_snapshots.Any(s => s.StreamId == snapshot.StreamId && s.StreamRevision == snapshot.StreamRevision)) + { + return false; + } + + _snapshots.Add(snapshot); + _heads.Remove(currentHead); + _heads.Add(new StreamHead(currentHead.BucketId, currentHead.StreamId, currentHead.HeadRevision, snapshot.StreamRevision)); + } + return true; + } + + public void Purge() + { + lock (_commits) + { + _commits.Clear(); + _snapshots.Clear(); + _heads.Clear(); + _potentialConflicts.Clear(); + _potentialDuplicates.Clear(); + } + } + + public void DeleteStream(string streamId) + { + lock (_commits) + { + InMemoryCommit[] commits = _commits.Where(c => c.StreamId == streamId).ToArray(); + foreach (var commit in commits) + { + _commits.Remove(commit); + } + ISnapshot[] snapshots = _snapshots.Where(s => s.StreamId == streamId).ToArray(); + foreach (var snapshot in snapshots) + { + _snapshots.Remove(snapshot); + } + IStreamHead streamHead = _heads.SingleOrDefault(s => s.StreamId == streamId); + if (streamHead != null) + { + _heads.Remove(streamHead); + } + } + } + } + } +} \ No newline at end of file diff --git a/src/NEventStore/Persistence/InMemory/InMemoryPersistenceFactory.cs b/src/NEventStore/Persistence/InMemory/InMemoryPersistenceFactory.cs new file mode 100644 index 000000000..54e1d3d53 --- /dev/null +++ b/src/NEventStore/Persistence/InMemory/InMemoryPersistenceFactory.cs @@ -0,0 +1,16 @@ +namespace NEventStore.Persistence.InMemory +{ + /// + /// Represents a factory for creating in-memory persistence engines. + /// + public class InMemoryPersistenceFactory : IPersistenceFactory + { + /// + /// Builds a new in-memory persistence engine. + /// + public virtual IPersistStreams Build() + { + return new InMemoryPersistenceEngine(); + } + } +} \ No newline at end of file diff --git a/src/NEventStore/Persistence/PersistStreamsExtensions.cs b/src/NEventStore/Persistence/PersistStreamsExtensions.cs new file mode 100644 index 000000000..1e6233805 --- /dev/null +++ b/src/NEventStore/Persistence/PersistStreamsExtensions.cs @@ -0,0 +1,58 @@ +namespace NEventStore.Persistence +{ + /// + /// Provides a set of extension methods for the interface. + /// + public static class PersistStreamsExtensions + { + /// + /// Deletes a stream from the default bucket. + /// + /// The IPersistStreams instance. + /// The stream id to be deleted. + public static void DeleteStream(this IPersistStreams persistStreams, string streamId) + { + persistStreams.DeleteStream(Bucket.Default, streamId); + } + + /// + /// Deletes a stream from the default bucket. + /// + /// The IPersistStreams instance. + /// The stream id to be deleted. + /// The token to monitor for cancellation requests. + public static Task DeleteStreamAsync(this IPersistStreams persistStreams, string streamId, CancellationToken cancellationToken = default) + { + return persistStreams.DeleteStreamAsync(Bucket.Default, streamId, cancellationToken); + } + + /// + /// Returns a single commit from any bucket. + /// Wrapper for the function in order to + /// return a single commit. + /// + /// The IPersistStreams instance. + /// The checkpoint token that mark the commit to read. + /// A single commit. + public static ICommit GetCommit(this IPersistStreams persistStreams, Int64 checkpointToken) + { + return persistStreams.GetFromTo(checkpointToken - 1, checkpointToken).SingleOrDefault(); + } + + /// + /// Returns a single commit from any bucket. + /// Wrapper for the function in order to + /// return a single commit. + /// + /// The IPersistStreams instance. + /// The checkpoint token that mark the commit to read. + /// The token to monitor for cancellation requests. + /// A single commit. + public static async Task GetCommitAsync(this IPersistStreams persistStreams, Int64 checkpointToken, CancellationToken cancellationToken = default) + { + var observer = new CommitStreamObserver(); + await persistStreams.GetFromToAsync(checkpointToken - 1, checkpointToken, observer, cancellationToken).ConfigureAwait(false); + return observer.Commits.SingleOrDefault(); + } + } +} \ No newline at end of file diff --git a/src/NEventStore/Persistence/PipelineHooksAwarePersistStreamsDecorator.cs b/src/NEventStore/Persistence/PipelineHooksAwarePersistStreamsDecorator.cs new file mode 100644 index 000000000..113f1491c --- /dev/null +++ b/src/NEventStore/Persistence/PipelineHooksAwarePersistStreamsDecorator.cs @@ -0,0 +1,390 @@ +using Microsoft.Extensions.Logging; +using NEventStore.Logging; + +namespace NEventStore.Persistence +{ + /// + /// Represents a persistence decorator that allows for hooks to be injected into the pipeline. + /// + public sealed class PipelineHooksAwarePersistStreamsDecorator : IPersistStreams + { + private static readonly ILogger Logger = LogFactory.BuildLogger(typeof(PipelineHooksAwarePersistStreamsDecorator)); + private readonly IPersistStreams _original; + private readonly IEnumerable _pipelineHooks; + private readonly IEnumerable _pipelineHooksAsync; + + /// + /// Initializes a new instance of the PipelineHooksAwarePersistStreamsDecorator class. + /// + /// + public PipelineHooksAwarePersistStreamsDecorator(IPersistStreams original, IEnumerable pipelineHooks, IEnumerable pipelineHooksAsync) + { + _original = original ?? throw new ArgumentNullException(nameof(original)); + _pipelineHooks = pipelineHooks ?? throw new ArgumentNullException(nameof(pipelineHooks)); + _pipelineHooksAsync = pipelineHooksAsync ?? throw new ArgumentNullException(nameof(pipelineHooksAsync)); + } + + /// + public void Dispose() + { + _original.Dispose(); + } + + /// + [Obsolete("DateTime is problematic in distributed systems. Use GetFrom(Int64 checkpointToken) instead. This method will be removed in a later version.")] + public IEnumerable GetFrom(string bucketId, DateTime startDate) + { + return ExecuteSelectCommitsHooks(_original.GetFrom(bucketId, startDate)); + } + + /// + [Obsolete("DateTime is problematic in distributed systems. Use GetFromTo(Int64 fromCheckpointToken, Int64 toCheckpointToken) instead. This method will be removed in a later version.")] + public IEnumerable GetFromTo(string bucketId, DateTime startDate, DateTime endDate) + { + return ExecuteSelectCommitsHooks(_original.GetFromTo(bucketId, startDate, endDate)); + } + + /// + public IEnumerable GetFrom(Int64 checkpointToken) + { + return ExecuteSelectCommitsHooks(_original.GetFrom(checkpointToken)); + } + + /// + public Task GetFromAsync(Int64 checkpointToken, IAsyncObserver asyncObserver, CancellationToken cancellationToken) + { + var pipelineHookObserver = new PipelineHookObserver(_pipelineHooks, _pipelineHooksAsync, asyncObserver); + return _original.GetFromAsync(checkpointToken, pipelineHookObserver, cancellationToken); + } + + /// + public IEnumerable GetFromTo(Int64 fromCheckpointToken, Int64 toCheckpointToken) + { + return ExecuteSelectCommitsHooks(_original.GetFromTo(fromCheckpointToken, toCheckpointToken)); + } + + /// + public Task GetFromToAsync(Int64 fromCheckpointToken, Int64 toCheckpointToken, IAsyncObserver asyncObserver, CancellationToken cancellationToken) + { + var pipelineHookObserver = new PipelineHookObserver(_pipelineHooks, _pipelineHooksAsync, asyncObserver); + return _original.GetFromToAsync(fromCheckpointToken, toCheckpointToken, pipelineHookObserver, cancellationToken); + } + + /// + public IEnumerable GetFrom(string bucketId, Int64 checkpointToken) + { + return ExecuteSelectCommitsHooks(_original.GetFrom(bucketId, checkpointToken)); + } + + /// + public Task GetFromAsync(string bucketId, Int64 checkpointToken, IAsyncObserver asyncObserver, CancellationToken cancellationToken) + { + var pipelineHookObserver = new PipelineHookObserver(_pipelineHooks, _pipelineHooksAsync, asyncObserver); + return _original.GetFromAsync(bucketId, checkpointToken, pipelineHookObserver, cancellationToken); + } + + /// + public IEnumerable GetFromTo(string bucketId, Int64 fromCheckpointToken, Int64 toCheckpointToken) + { + return ExecuteSelectCommitsHooks(_original.GetFromTo(bucketId, fromCheckpointToken, toCheckpointToken)); + } + + /// + public Task GetFromToAsync(string bucketId, long fromCheckpointToken, long toCheckpointToken, IAsyncObserver asyncObserver, CancellationToken cancellationToken) + { + var pipelineHookObserver = new PipelineHookObserver(_pipelineHooks, _pipelineHooksAsync, asyncObserver); + return _original.GetFromToAsync(bucketId, fromCheckpointToken, toCheckpointToken, pipelineHookObserver, cancellationToken); + } + + /// + public IEnumerable GetFrom(string bucketId, string streamId, int minRevision, int maxRevision) + { + return ExecuteSelectCommitsHooks(_original.GetFrom(bucketId, streamId, minRevision, maxRevision)); + } + + /// + public ICommit? Commit(CommitAttempt attempt) + { + return _original.Commit(attempt); + } + + /// + public Task GetFromAsync(string bucketId, string streamId, int minRevision, int maxRevision, IAsyncObserver observer, CancellationToken cancellationToken) + { + var pipelineHookObserver = new PipelineHookObserver(_pipelineHooks, _pipelineHooksAsync, observer); + return _original.GetFromAsync(bucketId, streamId, minRevision, maxRevision, pipelineHookObserver, cancellationToken); + } + + /// + public Task CommitAsync(CommitAttempt attempt, CancellationToken cancellationToken) + { + return _original.CommitAsync(attempt, cancellationToken); + } + + /// + public ISnapshot? GetSnapshot(string bucketId, string streamId, int maxRevision) + { + return _original.GetSnapshot(bucketId, streamId, maxRevision); + } + + /// + public bool AddSnapshot(ISnapshot snapshot) + { + return _original.AddSnapshot(snapshot); + } + + /// + public IEnumerable GetStreamsToSnapshot(string bucketId, int maxThreshold) + { + return _original.GetStreamsToSnapshot(bucketId, maxThreshold); + } + + /// + public Task GetSnapshotAsync(string bucketId, string streamId, int maxRevision, CancellationToken cancellationToken) + { + return _original.GetSnapshotAsync(bucketId, streamId, maxRevision, cancellationToken); + } + + /// + public Task AddSnapshotAsync(ISnapshot snapshot, CancellationToken cancellationToken) + { + return _original.AddSnapshotAsync(snapshot, cancellationToken); + } + + /// + public Task GetStreamsToSnapshotAsync(string bucketId, int maxThreshold, IAsyncObserver asyncObserver, CancellationToken cancellationToken) + { + return _original.GetStreamsToSnapshotAsync(bucketId, maxThreshold, asyncObserver, cancellationToken); + } + + /// + public void Initialize() + { + _original.Initialize(); + } + + /// + public void Purge() + { + _original.Purge(); + foreach (var pipelineHook in _pipelineHooks) + { + pipelineHook.OnPurge(); + } + foreach (var pipelineHook in _pipelineHooksAsync) + { + pipelineHook.OnPurgeAsync(CancellationToken.None).GetAwaiter().GetResult(); + } + } + + /// + public void Purge(string bucketId) + { + _original.Purge(bucketId); + foreach (var pipelineHook in _pipelineHooks) + { + pipelineHook.OnPurge(bucketId); + } + foreach (var pipelineHook in _pipelineHooksAsync) + { + pipelineHook.OnPurgeAsync(bucketId, CancellationToken.None).GetAwaiter().GetResult(); + } + } + + /// + public async Task PurgeAsync(CancellationToken cancellationToken) + { + await _original.PurgeAsync(cancellationToken).ConfigureAwait(false); + foreach (var pipelineHook in _pipelineHooks) + { + pipelineHook.OnPurge(); + } + foreach (var pipelineHook in _pipelineHooksAsync) + { + await pipelineHook.OnPurgeAsync(cancellationToken).ConfigureAwait(false); + } + } + + /// + public async Task PurgeAsync(string bucketId, CancellationToken cancellationToken) + { + await _original.PurgeAsync(bucketId, cancellationToken).ConfigureAwait(false); + foreach (var pipelineHook in _pipelineHooks) + { + pipelineHook.OnPurge(bucketId); + } + foreach (var pipelineHook in _pipelineHooksAsync) + { + await pipelineHook.OnPurgeAsync(bucketId, cancellationToken).ConfigureAwait(false); + } + } + + /// + public void Drop() + { + _original.Drop(); + } + + /// + public void DeleteStream(string bucketId, string streamId) + { + _original.DeleteStream(bucketId, streamId); + foreach (var pipelineHook in _pipelineHooks) + { + pipelineHook.OnDeleteStream(bucketId, streamId); + } + foreach (var pipelineHook in _pipelineHooksAsync) + { + pipelineHook.OnDeleteStreamAsync(bucketId, streamId, CancellationToken.None).GetAwaiter().GetResult(); + } + } + + /// + public async Task DeleteStreamAsync(string bucketId, string streamId, CancellationToken cancellationToken) + { + await _original.DeleteStreamAsync(bucketId, streamId, cancellationToken).ConfigureAwait(false); + foreach (var pipelineHook in _pipelineHooks) + { + pipelineHook.OnDeleteStream(bucketId, streamId); + } + foreach (var pipelineHook in _pipelineHooksAsync) + { + await pipelineHook.OnDeleteStreamAsync(bucketId, streamId, cancellationToken).ConfigureAwait(false); + } + } + + /// + public bool IsDisposed + { + get { return _original.IsDisposed; } + } + + private IEnumerable ExecuteSelectCommitsHooks(IEnumerable commits) + { + foreach (var commit in commits) + { + ICommit? filtered = commit; + foreach (var hook in _pipelineHooks) + { + filtered = hook.SelectCommit(filtered); + if (filtered == null) + { + if (Logger.IsEnabled(LogLevel.Information)) + { + Logger.LogInformation(Resources.PipelineHookSkippedCommit, hook.GetType(), commit.CommitId); + } + break; + } + } + if (filtered != null) + { + foreach (var hook in _pipelineHooksAsync) + { + filtered = hook.SelectCommitAsync(filtered, CancellationToken.None).GetAwaiter().GetResult(); + if (filtered == null) + { + if (Logger.IsEnabled(LogLevel.Information)) + { + Logger.LogInformation(Resources.PipelineHookSkippedCommit, hook.GetType(), commit.CommitId); + } + break; + } + } + } + if (filtered == null) + { + if (Logger.IsEnabled(LogLevel.Information)) + { + Logger.LogInformation(Resources.PipelineHookFilteredCommit); + } + } + else + { + yield return filtered; + } + } + } + + internal class PipelineHookObserver : IAsyncObserver + { + private readonly IEnumerable _pipelineHooks; + private readonly IEnumerable _pipelineHooksAsync; + private readonly IAsyncObserver _observer; + + public PipelineHookObserver( + IEnumerable pipelineHooks, + IEnumerable pipelineHooksAsync, + IAsyncObserver observer + ) + { + _pipelineHooks = pipelineHooks; + _pipelineHooksAsync = pipelineHooksAsync; + _observer = observer; + } + + public Task OnCompletedAsync(CancellationToken cancellationToken) + { + return _observer.OnCompletedAsync(cancellationToken); + } + + public Task OnErrorAsync(Exception error, CancellationToken cancellationToken) + { + return _observer.OnErrorAsync(error, cancellationToken); + } + + public async Task OnNextAsync(ICommit value, CancellationToken cancellationToken) + { + var commit = await ExecuteSelectCommitHooksAsync(value, cancellationToken).ConfigureAwait(false); + if (commit != null) + { + return await _observer.OnNextAsync(commit, cancellationToken).ConfigureAwait(false); + } + return true; + } + + private async Task ExecuteSelectCommitHooksAsync(ICommit commit, CancellationToken cancellationToken) + { + ICommit? filtered = commit; + foreach (var hook in _pipelineHooks) + { + filtered = hook.SelectCommit(filtered); + if (filtered == null) + { + if (Logger.IsEnabled(LogLevel.Information)) + { + Logger.LogInformation(Resources.PipelineHookSkippedCommit, hook.GetType(), commit.CommitId); + } + break; + } + } + if (filtered != null) + { + foreach (var hook in _pipelineHooksAsync) + { + filtered = await hook.SelectCommitAsync(filtered, cancellationToken).ConfigureAwait(false); + if (filtered == null) + { + if (Logger.IsEnabled(LogLevel.Information)) + { + Logger.LogInformation(Resources.PipelineHookSkippedCommit, hook.GetType(), commit.CommitId); + } + break; + } + } + } + if (filtered == null) + { + if (Logger.IsEnabled(LogLevel.Information)) + { + Logger.LogInformation(Resources.PipelineHookFilteredCommit); + } + return null; + } + else + { + return filtered; + } + } + } + } +} \ No newline at end of file diff --git a/src/NEventStore/Persistence/StorageException.cs b/src/NEventStore/Persistence/StorageException.cs new file mode 100644 index 000000000..da555ed42 --- /dev/null +++ b/src/NEventStore/Persistence/StorageException.cs @@ -0,0 +1,43 @@ +using System.Runtime.Serialization; + +namespace NEventStore.Persistence +{ + /// + /// Represents a general failure of the storage engine or persistence infrastructure. + /// + [Serializable] + public class StorageException : Exception + { + /// + /// Initializes a new instance of the StorageException class. + /// + public StorageException() + { } + + /// + /// Initializes a new instance of the StorageException class. + /// + /// The message that describes the error. + public StorageException(string message) + : base(message) + { } + + /// + /// Initializes a new instance of the StorageException class. + /// + /// The message that describes the error. + /// The message that is the cause of the current exception. + public StorageException(string message, Exception innerException) + : base(message, innerException) + { } + + /// + /// Initializes a new instance of the StorageException class. + /// + /// The SerializationInfo that holds the serialized object data of the exception being thrown. + /// The StreamingContext that contains contextual information about the source or destination. + protected StorageException(SerializationInfo info, StreamingContext context) + : base(info, context) + { } + } +} \ No newline at end of file diff --git a/src/NEventStore/Persistence/StorageUnavailableException.cs b/src/NEventStore/Persistence/StorageUnavailableException.cs new file mode 100644 index 000000000..483d6668c --- /dev/null +++ b/src/NEventStore/Persistence/StorageUnavailableException.cs @@ -0,0 +1,43 @@ +using System.Runtime.Serialization; + +namespace NEventStore.Persistence +{ + /// + /// Indicates that the underlying persistence medium is unavailable or offline. + /// + [Serializable] + public class StorageUnavailableException : StorageException + { + /// + /// Initializes a new instance of the StorageUnavailableException class. + /// + public StorageUnavailableException() + {} + + /// + /// Initializes a new instance of the StorageUnavailableException class. + /// + /// The message that describes the error. + public StorageUnavailableException(string message) + : base(message) + {} + + /// + /// Initializes a new instance of the StorageUnavailableException class. + /// + /// The message that describes the error. + /// The message that is the cause of the current exception. + public StorageUnavailableException(string message, Exception innerException) + : base(message, innerException) + {} + + /// + /// Initializes a new instance of the StorageUnavailableException class. + /// + /// The SerializationInfo that holds the serialized object data of the exception being thrown. + /// The StreamingContext that contains contextual information about the source or destination. + protected StorageUnavailableException(SerializationInfo info, StreamingContext context) + : base(info, context) + {} + } +} \ No newline at end of file diff --git a/src/NEventStore/Persistence/StreamHead.cs b/src/NEventStore/Persistence/StreamHead.cs new file mode 100644 index 000000000..01204f83d --- /dev/null +++ b/src/NEventStore/Persistence/StreamHead.cs @@ -0,0 +1,72 @@ +#pragma warning disable RCS1170 // Use read-only auto-implemented property. + +namespace NEventStore.Persistence +{ + /// + /// Indicates the most recent information representing the head of a given stream. + /// + public class StreamHead : IStreamHead + { + /// + /// Initializes a new instance of the StreamHead class. + /// + /// The value which uniquely identifies bucket the stream belongs to. + /// The value which uniquely identifies the stream in the bucket where the last snapshot exceeds the allowed threshold. + /// The value which indicates the revision, length, or number of events committed to the stream. + /// The value which indicates the revision at which the last snapshot was taken. + public StreamHead(string bucketId, string streamId, int headRevision, int snapshotRevision) + { + BucketId = bucketId; + StreamId = streamId; + HeadRevision = headRevision; + SnapshotRevision = snapshotRevision; + } + + /// + /// Stream Head Equality Comparer + /// + public static IEqualityComparer StreamIdBucketIdComparer { get; } = new StreamHeadEqualityComparer(); + + /// + /// Gets the value which uniquely identifies the stream where the last snapshot exceeds the allowed threshold. + /// + public string BucketId { get; private set; } + + /// + /// Gets the value which uniquely identifies the stream where the last snapshot exceeds the allowed threshold. + /// + public string StreamId { get; private set; } + + /// + /// Gets the value which indicates the revision, length, or number of events committed to the stream. + /// + public int HeadRevision { get; private set; } + + /// + /// Gets the value which indicates the revision at which the last snapshot was taken. + /// + public int SnapshotRevision { get; private set; } + + /// + /// Determines whether the specified object is equal to the current object. + /// + /// The object to compare with the current object. + /// If the two objects are equal, returns true; otherwise false. + public override bool Equals(object obj) + { + return obj is StreamHead commit + && commit.StreamId == StreamId; + } + + /// + /// Returns the hash code for this instance. + /// + /// The hash code for this instance. + public override int GetHashCode() + { + return StreamId.GetHashCode(); + } + } +} + +#pragma warning restore RCS1170 // Use read-only auto-implemented property. \ No newline at end of file diff --git a/src/NEventStore/Persistence/StreamHeadEqualityComparer.cs b/src/NEventStore/Persistence/StreamHeadEqualityComparer.cs new file mode 100644 index 000000000..b533c638d --- /dev/null +++ b/src/NEventStore/Persistence/StreamHeadEqualityComparer.cs @@ -0,0 +1,31 @@ +namespace NEventStore.Persistence { + /// + /// Represents a strategy for comparing stream heads. + /// + public sealed class StreamHeadEqualityComparer : IEqualityComparer { + /// + public bool Equals(IStreamHead x, IStreamHead y) { + if (ReferenceEquals(x, y)) { + return true; + } + if (x is null) { + return false; + } + if (y is null) { + return false; + } + if (x.GetType() != y.GetType()) { + return false; + } + return string.Equals(x.StreamId, y.StreamId, StringComparison.Ordinal) + && string.Equals(x.BucketId, y.BucketId, StringComparison.Ordinal); + } + + /// + public int GetHashCode(IStreamHead obj) { + unchecked { + return ((obj.StreamId?.GetHashCode() ?? 0) * 397) ^ (obj.BucketId?.GetHashCode() ?? 0); + } + } + } +} \ No newline at end of file diff --git a/src/NEventStore/PersistenceWireup.cs b/src/NEventStore/PersistenceWireup.cs new file mode 100644 index 000000000..4988d20cf --- /dev/null +++ b/src/NEventStore/PersistenceWireup.cs @@ -0,0 +1,144 @@ +using Microsoft.Extensions.Logging; +using NEventStore.Diagnostics; +using NEventStore.Logging; +using NEventStore.Persistence; +using NEventStore.Serialization; + +namespace NEventStore +{ + /// + /// Represents the persistence wireup. + /// + public class PersistenceWireup : Wireup + { + private static readonly ILogger Logger = LogFactory.BuildLogger(typeof(PersistenceWireup)); + private bool _initialize; +#if NET462 + private bool _tracking; + private string? _trackingInstanceName; +#endif + + /// + /// Initializes a new instance of the PersistenceWireup class. + /// + public PersistenceWireup(Wireup inner) + : base(inner) + { +#pragma warning disable S125 // Sections of code should not be commented out + /* EnlistInAmbientTransaction: Will be moved to the specific Persistence driver or completely removed letting the clients handle that + Container.Register(TransactionScopeOption.Suppress); + */ +#pragma warning restore S125 // Sections of code should not be commented out + } + + /// + /// Configures the persistence engine to use the specified serializer. + /// + public virtual PersistenceWireup WithPersistence(IPersistStreams instance) + { + if (Logger.IsEnabled(LogLevel.Information)) + { + Logger.LogInformation(Messages.RegisteringPersistenceEngine, instance.GetType()); + } + Register(instance); + return this; + } + + /// + /// Configures the persistence engine to use the specified serializer. + /// + protected virtual SerializationWireup WithSerializer(ISerialize serializer) + { + return new SerializationWireup(this, serializer); + } + + /// + /// Initializes the storage engine. + /// + public virtual PersistenceWireup InitializeStorageEngine() + { + if (Logger.IsEnabled(LogLevel.Information)) + { + Logger.LogInformation(Messages.ConfiguringEngineInitialization); + } + _initialize = true; + return this; + } + +#if NET462 + /// + /// Configures the persistence engine to track performance counters. + /// + /// + public virtual PersistenceWireup TrackPerformanceInstance(string instanceName) + { + _tracking = true; + _trackingInstanceName = instanceName + ?? throw new ArgumentNullException(nameof(instanceName), Messages.InstanceCannotBeNull); + + if (Logger.IsEnabled(LogLevel.Information)) + { + Logger.LogInformation(Messages.ConfiguringEnginePerformanceTracking); + } + return this; + } +#endif + +#pragma warning disable S125 // Sections of code should not be commented out + /* EnlistInAmbientTransaction: Will be moved to the specific Persistence driver or completely removed letting the clients handle that + /// + /// Enables two-phase commit. + /// By default NEventStore will suppress surrounding TransactionScopes + /// (All the Persistence drivers that support transactions will create a + /// private nested TransactionScope with for each operation) + /// so that all of its operations run in a dedicated, separate transaction. + /// This option changes the behavior so that NEventStore enlists in a surrounding TransactionScope, + /// if there is any (All the Persistence drivers that support transactions will create a + /// private nested TransactionScope with for each operation). + /// + /// + /// Enabling the two-phase commit will also disable the + /// that provide some additionl concurrency checks to avoid useless roundtrips to the databases. + /// + /// + public virtual PersistenceWireup EnlistInAmbientTransaction() + { + if (Logger.IsInfoEnabled) Logger.Info(Messages.ConfiguringEngineEnlistment); + Container.Register(TransactionScopeOption.Required); + return this; + } + */ +#pragma warning restore S125 // Sections of code should not be commented out + + /// + /// Builds the persistence engine. + /// + public override IStoreEvents Build() + { + if (Logger.IsEnabled(LogLevel.Information)) + { + Logger.LogInformation(Messages.BuildingEngine); + } + + var engine = Container.Resolve() + ?? throw new InvalidOperationException("IPersistStreams not registered"); + + if (_initialize) + { + if (Logger.IsEnabled(LogLevel.Debug)) + { + Logger.LogDebug(Messages.InitializingEngine); + } + engine.Initialize(); + } + +#if NET462 + if (_tracking) + { + Container.Register(new PerformanceCounterPersistenceEngine(engine, _trackingInstanceName!)); + } +#endif + return base.Build(); + } + } +} \ No newline at end of file diff --git a/src/NEventStore/PersistenceWireupExtensions.cs b/src/NEventStore/PersistenceWireupExtensions.cs new file mode 100644 index 000000000..3a17e4342 --- /dev/null +++ b/src/NEventStore/PersistenceWireupExtensions.cs @@ -0,0 +1,29 @@ +using NEventStore.Logging; +using Microsoft.Extensions.Logging; +using NEventStore.Persistence; +using NEventStore.Persistence.InMemory; + +namespace NEventStore +{ + /// + /// Persistence wireup extensions. + /// + public static class PersistenceWireupExtensions + { + private static readonly ILogger Logger = LogFactory.BuildLogger(typeof(OptimisticPipelineHook)); + + /// + /// Configures the persistence engine to use the in-memory persistence engine. + /// + public static PersistenceWireup UsingInMemoryPersistence(this Wireup wireup) + { + if (Logger.IsEnabled(LogLevel.Information)) + { + Logger.LogInformation(Resources.WireupSetPersistenceEngine, "InMemoryPersistenceEngine"); + } + wireup.Register(new InMemoryPersistenceEngine()); + + return new PersistenceWireup(wireup); + } + } +} \ No newline at end of file diff --git a/src/NEventStore/PipelineHookAsyncBase.cs b/src/NEventStore/PipelineHookAsyncBase.cs new file mode 100644 index 000000000..1e33f80b5 --- /dev/null +++ b/src/NEventStore/PipelineHookAsyncBase.cs @@ -0,0 +1,52 @@ + +namespace NEventStore +{ + /// + /// Provides a base implementation of the interface. + /// + public abstract class PipelineHookAsyncBase : IPipelineHookAsync + { + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + protected virtual void Dispose(bool disposing) + { + // Cleanup + } + + /// + public virtual Task OnDeleteStreamAsync(string bucketId, string streamId, CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + + /// + public virtual Task OnPurgeAsync(string? bucketId, CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + + /// + public virtual Task PostCommitAsync(ICommit committed, CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + + /// + public virtual Task PreCommitAsync(CommitAttempt attempt, CancellationToken cancellationToken) + { + return Task.FromResult(true); + } + + /// + public virtual Task SelectCommitAsync(ICommit committed, CancellationToken cancellationToken) + { + return Task.FromResult(committed); + } + } +} \ No newline at end of file diff --git a/src/NEventStore/PipelineHookBase.cs b/src/NEventStore/PipelineHookBase.cs new file mode 100644 index 000000000..350746c0f --- /dev/null +++ b/src/NEventStore/PipelineHookBase.cs @@ -0,0 +1,46 @@ + +namespace NEventStore +{ + /// + /// Provides a base implementation of the interface. + /// + public abstract class PipelineHookBase : IPipelineHook + { + /// + public virtual ICommit? SelectCommit(ICommit committed) + { + return committed; + } + + /// + public virtual bool PreCommit(CommitAttempt attempt) + { + return true; + } + + /// + public virtual void PostCommit(ICommit committed) + { } + + /// + public virtual void OnPurge(string? bucketId) + { } + + /// + public virtual void OnDeleteStream(string bucketId, string streamId) + { } + + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + protected virtual void Dispose(bool disposing) + { + // Cleanup + } + } +} \ No newline at end of file diff --git a/src/NEventStore/PipelineHookExtensions.cs b/src/NEventStore/PipelineHookExtensions.cs new file mode 100644 index 000000000..9f351fd31 --- /dev/null +++ b/src/NEventStore/PipelineHookExtensions.cs @@ -0,0 +1,27 @@ +namespace NEventStore +{ + /// + /// Provides extension methods for . + /// + public static class PipelineHookExtensions + { + /// + /// Invoked when all buckets have been purged. + /// + /// The pipeline hook. + public static void OnPurge(this IPipelineHook pipelineHook) + { + pipelineHook.OnPurge(null); + } + + /// + /// Invoked when all buckets have been purged. + /// + /// The pipeline hook. + /// The token to monitor for cancellation requests. + public static Task OnPurgeAsync(this IPipelineHookAsync pipelineHook, CancellationToken cancellationToken) + { + return pipelineHook.OnPurgeAsync(null, cancellationToken); + } + } +} \ No newline at end of file diff --git a/src/proj/EventStore.Wireup/Properties/AssemblyInfo.cs b/src/NEventStore/Properties/ProjectAssemblyInfo.cs similarity index 50% rename from src/proj/EventStore.Wireup/Properties/AssemblyInfo.cs rename to src/NEventStore/Properties/ProjectAssemblyInfo.cs index 2be49936c..94ee3b119 100644 --- a/src/proj/EventStore.Wireup/Properties/AssemblyInfo.cs +++ b/src/NEventStore/Properties/ProjectAssemblyInfo.cs @@ -1,6 +1,6 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -[assembly: AssemblyTitle("EventStore.Wireup")] -[assembly: AssemblyDescription("")] -[assembly: Guid("da6ec2a6-c59c-4bbe-97ae-e047d5e17e07")] \ No newline at end of file +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("NEventStore")] +[assembly: AssemblyDescription("")] +[assembly: Guid("9cb4668f-d7b2-4ad9-8f9b-2af9903d2db2")] \ No newline at end of file diff --git a/src/proj/EventStore.Core/Resources.Designer.cs b/src/NEventStore/Resources.Designer.cs similarity index 76% rename from src/proj/EventStore.Core/Resources.Designer.cs rename to src/NEventStore/Resources.Designer.cs index b0fb50b0e..53e1afe31 100644 --- a/src/proj/EventStore.Core/Resources.Designer.cs +++ b/src/NEventStore/Resources.Designer.cs @@ -1,576 +1,531 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.237 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace EventStore { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Resources() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("EventStore.Resources", typeof(Resources).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to Adding commit '{0} with {1} events to stream '{2}'.. - /// - internal static string AddingCommitsToStream { - get { - return ResourceManager.GetString("AddingCommitsToStream", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Adding a snapshot for stream '{0}' at revision '{1}'.. - /// - internal static string AddingSnapshot { - get { - return ResourceManager.GetString("AddingSnapshot", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The object has already been disposed.. - /// - internal static string AlreadyDisposed { - get { - return ResourceManager.GetString("AlreadyDisposed", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Appending uncommitted event to stream '{0}'. - /// - internal static string AppendingUncommittedToStream { - get { - return ResourceManager.GetString("AppendingUncommittedToStream", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Attempting to append commit '{0}' to stream '{1}' at position '{2}'.. - /// - internal static string AttemptingToCommit { - get { - return ResourceManager.GetString("AttemptingToCommit", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Attempting to commit all changes on stream '{0}' to the underlying store.. - /// - internal static string AttemptingToCommitChanges { - get { - return ResourceManager.GetString("AttemptingToCommitChanges", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Building a commit attempt '{0}' on stream '{1}'.. - /// - internal static string BuildingCommitAttempt { - get { - return ResourceManager.GetString("BuildingCommitAttempt", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Clearing all uncommitted changes on stream '{0}'.. - /// - internal static string ClearingUncommittedChanges { - get { - return ResourceManager.GetString("ClearingUncommittedChanges", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Commit attempt failed one or more integrity checks.. - /// - internal static string CommitAttemptFailedIntegrityChecks { - get { - return ResourceManager.GetString("CommitAttemptFailedIntegrityChecks", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Pipeline hook of type '{0}' rejected attempt '{1}'.. - /// - internal static string CommitRejectedByPipelineHook { - get { - return ResourceManager.GetString("CommitRejectedByPipelineHook", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The commit must be uniquely identified.. - /// - internal static string CommitsMustBeUniquelyIdentified { - get { - return ResourceManager.GetString("CommitsMustBeUniquelyIdentified", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Committing attempt '{0}' which contains {1} events to the underlying persistence engine.. - /// - internal static string CommittingAttempt { - get { - return ResourceManager.GetString("CommittingAttempt", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Converting an Event from '{0}' to '{1}'.. - /// - internal static string ConvertingEvent { - get { - return ResourceManager.GetString("ConvertingEvent", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Creating stream '{0}'.. - /// - internal static string CreatingStream { - get { - return ResourceManager.GetString("CreatingStream", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Dispatching message to /dev/null.. - /// - internal static string DispatchingToDevNull { - get { - return ResourceManager.GetString("DispatchingToDevNull", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Disposing engine.. - /// - internal static string DisposingEngine { - get { - return ResourceManager.GetString("DisposingEngine", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Getting all commits for stream '{0}' between '{1}' and '{2}'.. - /// - internal static string GettingAllCommitsFromRevision { - get { - return ResourceManager.GetString("GettingAllCommitsFromRevision", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Getting all commits from '{0}' forward.. - /// - internal static string GettingAllCommitsFromTime { - get { - return ResourceManager.GetString("GettingAllCommitsFromTime", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Getting all commits from '{0}' to '{1}'.. - /// - internal static string GettingAllCommitsFromToTime { - get { - return ResourceManager.GetString("GettingAllCommitsFromToTime", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Getting the most recent snapshot for stream '{0}' on/since revision '{1}'.. - /// - internal static string GettingSnapshotForStream { - get { - return ResourceManager.GetString("GettingSnapshotForStream", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Getting the set of all streams to be snapshot which exceed {0} revisions without a snapshot.. - /// - internal static string GettingStreamsToSnapshot { - get { - return ResourceManager.GetString("GettingStreamsToSnapshot", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Getting undispatched commits from persistence engine.. - /// - internal static string GettingUndispatchedCommits { - get { - return ResourceManager.GetString("GettingUndispatchedCommits", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Ignoring some events on commit '{0}' of stream '{1}' because they starting before revision {2}.. - /// - internal static string IgnoringBeforeRevision { - get { - return ResourceManager.GetString("IgnoringBeforeRevision", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Ignoring some events on commit '{0}' of stream '{1}' because they go beyond revision {2}.. - /// - internal static string IgnoringBeyondRevision { - get { - return ResourceManager.GetString("IgnoringBeyondRevision", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Initializing engine.. - /// - internal static string InitializingEngine { - get { - return ResourceManager.GetString("InitializingEngine", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Initializing persistence engine.. - /// - internal static string InitializingPersistence { - get { - return ResourceManager.GetString("InitializingPersistence", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Pushing commit '{0}' to post-commit hook of type '{1}'.. - /// - internal static string InvokingPostCommitPipelineHooks { - get { - return ResourceManager.GetString("InvokingPostCommitPipelineHooks", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Pushing commit '{0}' to pre-commit hook of type '{1}'.. - /// - internal static string InvokingPreCommitHooks { - get { - return ResourceManager.GetString("InvokingPreCommitHooks", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Marking commit '{0}' as dispatched.. - /// - internal static string MarkingAsDispatched { - get { - return ResourceManager.GetString("MarkingAsDispatched", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Marking commit '{0}' as dispatched.. - /// - internal static string MarkingCommitAsDispatched { - get { - return ResourceManager.GetString("MarkingCommitAsDispatched", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to There are no outstanding changes to be committed stream '{0}'.. - /// - internal static string NoChangesToCommit { - get { - return ResourceManager.GetString("NoChangesToCommit", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to No other commits have been discovered that conflict for stream '{0}'.. - /// - internal static string NoConflicts { - get { - return ResourceManager.GetString("NoConflicts", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Purging all commits on stream '{0}' from tracking.. - /// - internal static string NoLongerTrackingStream { - get { - return ResourceManager.GetString("NoLongerTrackingStream", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The stream revision must be a positive number.. - /// - internal static string NonPositiveRevisionNumber { - get { - return ResourceManager.GetString("NonPositiveRevisionNumber", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The commit sequence must be a positive number.. - /// - internal static string NonPositiveSequenceNumber { - get { - return ResourceManager.GetString("NonPositiveSequenceNumber", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Opening stream '{0}' between revisions {1} and {2}.. - /// - internal static string OpeningStreamAtRevision { - get { - return ResourceManager.GetString("OpeningStreamAtRevision", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Opening stream '{0}' with snapshot at {1} up to revision {2}.. - /// - internal static string OpeningStreamWithSnapshot { - get { - return ResourceManager.GetString("OpeningStreamWithSnapshot", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Verifying that no other commits have succeed on the stream '{0}'.. - /// - internal static string OptimisticConcurrencyCheck { - get { - return ResourceManager.GetString("OptimisticConcurrencyCheck", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Pushing attempt '{0}' on stream '{1}' to the underlying store.. - /// - internal static string PersistingCommit { - get { - return ResourceManager.GetString("PersistingCommit", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to One or more pipeline hooks filtered out the commit.. - /// - internal static string PipelineHookFilteredCommit { - get { - return ResourceManager.GetString("PipelineHookFilteredCommit", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Pipeline hook of type '{0}' skipped over commit '{1}'.. - /// - internal static string PipelineHookSkippedCommit { - get { - return ResourceManager.GetString("PipelineHookSkippedCommit", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Purging all data from storage.. - /// - internal static string PurgingStore { - get { - return ResourceManager.GetString("PurgingStore", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The collection is read only and cannot be modified.. - /// - internal static string ReadOnlyCollection { - get { - return ResourceManager.GetString("ReadOnlyCollection", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Retrieving all {0} undispatched commits.. - /// - internal static string RetrievingUndispatchedCommits { - get { - return ResourceManager.GetString("RetrievingUndispatchedCommits", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The stream revision must always be greater than or equal to the commit sequence.. - /// - internal static string RevisionTooSmall { - get { - return ResourceManager.GetString("RevisionTooSmall", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Scheduling commit '{0}' for delivery.. - /// - internal static string SchedulingDelivery { - get { - return ResourceManager.GetString("SchedulingDelivery", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Scheduling commit '{0}' to be dispatched.. - /// - internal static string SchedulingDispatch { - get { - return ResourceManager.GetString("SchedulingDispatch", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Shutting down dispatcher.. - /// - internal static string ShuttingDownDispatcher { - get { - return ResourceManager.GetString("ShuttingDownDispatcher", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Shutting down dispatch scheduler.. - /// - internal static string ShuttingDownDispatchScheduler { - get { - return ResourceManager.GetString("ShuttingDownDispatchScheduler", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Shutting down event store.. - /// - internal static string ShuttingDownStore { - get { - return ResourceManager.GetString("ShuttingDownStore", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Starting dispatch scheduler.. - /// - internal static string StartingDispatchScheduler { - get { - return ResourceManager.GetString("StartingDispatchScheduler", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Tracking commit {0} on stream '{1}'.. - /// - internal static string TrackingCommit { - get { - return ResourceManager.GetString("TrackingCommit", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Tracking up to {0} streams.. - /// - internal static string TrackingStreams { - get { - return ResourceManager.GetString("TrackingStreams", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Configured dispatcher of type '{0}' was unable to dispatch commit '{1}'.. - /// - internal static string UnableToDispatch { - get { - return ResourceManager.GetString("UnableToDispatch", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Unable to mark commit '{0}' as dispatched, the underlying storage has already been disposed. - /// - internal static string UnableToMarkDispatched { - get { - return ResourceManager.GetString("UnableToMarkDispatched", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The underlying stream '{0}' has changed since the last known commit, refreshing the stream.. - /// - internal static string UnderlyingStreamHasChanged { - get { - return ResourceManager.GetString("UnderlyingStreamHasChanged", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Updating stream head for stream '{0}'.. - /// - internal static string UpdatingStreamHead { - get { - return ResourceManager.GetString("UpdatingStreamHead", resourceCulture); - } - } - } -} +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace NEventStore { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("NEventStore.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Adding commit '{0}' with {1} events to stream '{2}' bucket'{3}'.. + /// + internal static string AddingCommitsToStream { + get { + return ResourceManager.GetString("AddingCommitsToStream", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Adding a snapshot for bucket '{0}' stream '{1}' at revision '{2}'.. + /// + internal static string AddingSnapshot { + get { + return ResourceManager.GetString("AddingSnapshot", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The object has already been disposed.. + /// + internal static string AlreadyDisposed { + get { + return ResourceManager.GetString("AlreadyDisposed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Appending uncommitted event '{0}' to stream '{1}' bucket '{2}'. + /// + internal static string AppendingUncommittedToStream { + get { + return ResourceManager.GetString("AppendingUncommittedToStream", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Attempting to append commit '{0}' to stream '{1}' bucket '{2}' at position '{3}'.. + /// + internal static string AttemptingToCommit { + get { + return ResourceManager.GetString("AttemptingToCommit", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Attempting to commit all changes on stream '{0}' bucket '{1}' to the underlying store.. + /// + internal static string AttemptingToCommitChanges { + get { + return ResourceManager.GetString("AttemptingToCommitChanges", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Building a commit attempt '{0}' on stream '{1}' bucket '{2}'.. + /// + internal static string BuildingCommitAttempt { + get { + return ResourceManager.GetString("BuildingCommitAttempt", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The stream '{0}' bucket '{1}' was partially loaded up to revision {1}. Cannot append commits to a partially loaded stream, refreshing the stream.. + /// + internal static string CannotAddCommitsToPartiallyLoadedStream { + get { + return ResourceManager.GetString("CannotAddCommitsToPartiallyLoadedStream", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Clearing all uncommitted changes on stream '{0}' bucket '{1}'.. + /// + internal static string ClearingUncommittedChanges { + get { + return ResourceManager.GetString("ClearingUncommittedChanges", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Pipeline hook of type '{0}' rejected attempt '{1}'.. + /// + internal static string CommitRejectedByPipelineHook { + get { + return ResourceManager.GetString("CommitRejectedByPipelineHook", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Committing attempt '{0}' which contains {1} events to the underlying persistence engine.. + /// + internal static string CommittingAttempt { + get { + return ResourceManager.GetString("CommittingAttempt", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Converting an Event from '{0}' to '{1}'.. + /// + internal static string ConvertingEvent { + get { + return ResourceManager.GetString("ConvertingEvent", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Creating stream '{0}' in bucket '{1}'.. + /// + internal static string CreatingStream { + get { + return ResourceManager.GetString("CreatingStream", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Deleting stream '{0}' from bucket '{1}'.. + /// + internal static string DeletingStream { + get { + return ResourceManager.GetString("DeletingStream", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Disposing engine.. + /// + internal static string DisposingEngine { + get { + return ResourceManager.GetString("DisposingEngine", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Getting all commits from bucket '{0}' since checkpoint '{1}' (excluded).. + /// + internal static string GettingAllCommitsFromBucketAndCheckpoint { + get { + return ResourceManager.GetString("GettingAllCommitsFromBucketAndCheckpoint", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Getting all commits since checkpoint '{0}' (excluded).. + /// + internal static string GettingAllCommitsFromCheckpoint { + get { + return ResourceManager.GetString("GettingAllCommitsFromCheckpoint", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Getting all commits for stream '{0}' bucket '{1}' between '{2}' and '{3}'.. + /// + internal static string GettingAllCommitsFromRevision { + get { + return ResourceManager.GetString("GettingAllCommitsFromRevision", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Getting all commits from bucket '{0}' from '{1}' forward.. + /// + internal static string GettingAllCommitsFromTime { + get { + return ResourceManager.GetString("GettingAllCommitsFromTime", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Getting all commits from bucket '{0}' from '{1}' to '{2}'.. + /// + internal static string GettingAllCommitsFromToTime { + get { + return ResourceManager.GetString("GettingAllCommitsFromToTime", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Getting all commits from bucket '{0}' from checkpoint '{1}' (excluded) up to '{2}' (included).. + /// + internal static string GettingCommitsFromBucketAndFromToCheckpoint { + get { + return ResourceManager.GetString("GettingCommitsFromBucketAndFromToCheckpoint", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Getting all commits from checkpoint '{0}' (excluded) up to '{1}' (included).. + /// + internal static string GettingCommitsFromToCheckpoint { + get { + return ResourceManager.GetString("GettingCommitsFromToCheckpoint", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Getting the most recent snapshot from bucket '{0}' for stream '{1}' on/since revision '{2}'.. + /// + internal static string GettingSnapshotForStream { + get { + return ResourceManager.GetString("GettingSnapshotForStream", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Getting the set of all streams to be snapshot from bucket '{0}' which exceed {1} revisions without a snapshot.. + /// + internal static string GettingStreamsToSnapshot { + get { + return ResourceManager.GetString("GettingStreamsToSnapshot", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Ignoring some events on commit '{0}' of stream '{1}' because they starting before revision {2}.. + /// + internal static string IgnoringBeforeRevision { + get { + return ResourceManager.GetString("IgnoringBeforeRevision", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Ignoring some events on commit '{0}' of stream '{1}' because they go beyond revision {2}.. + /// + internal static string IgnoringBeyondRevision { + get { + return ResourceManager.GetString("IgnoringBeyondRevision", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Initializing engine.. + /// + internal static string InitializingEngine { + get { + return ResourceManager.GetString("InitializingEngine", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Initializing persistence engine.. + /// + internal static string InitializingPersistence { + get { + return ResourceManager.GetString("InitializingPersistence", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Pushing commit '{0}' to post-commit hook of type '{1}'.. + /// + internal static string InvokingPostCommitPipelineHooks { + get { + return ResourceManager.GetString("InvokingPostCommitPipelineHooks", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Pushing commit '{0}' to pre-commit hook of type '{1}'.. + /// + internal static string InvokingPreCommitHooks { + get { + return ResourceManager.GetString("InvokingPreCommitHooks", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to There are no outstanding changes to be committed stream '{0}' bucket '{1}'.. + /// + internal static string NoChangesToCommit { + get { + return ResourceManager.GetString("NoChangesToCommit", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to No other commits have been discovered that conflict for stream '{0}' bucket '{1}'.. + /// + internal static string NoConflicts { + get { + return ResourceManager.GetString("NoConflicts", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Purging all commits on stream '{0}' bucket '{1}' from tracking.. + /// + internal static string NoLongerTrackingStream { + get { + return ResourceManager.GetString("NoLongerTrackingStream", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The stream revision must be a positive number.. + /// + internal static string NonPositiveRevisionNumber { + get { + return ResourceManager.GetString("NonPositiveRevisionNumber", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The commit sequence must be a positive number.. + /// + internal static string NonPositiveSequenceNumber { + get { + return ResourceManager.GetString("NonPositiveSequenceNumber", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Opening stream '{0}' from bucket '{1}' between revisions {2} and {3}.. + /// + internal static string OpeningStreamAtRevision { + get { + return ResourceManager.GetString("OpeningStreamAtRevision", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Opening stream '{0}' from bucket '{1}' with snapshot at {2} up to revision {3}.. + /// + internal static string OpeningStreamWithSnapshot { + get { + return ResourceManager.GetString("OpeningStreamWithSnapshot", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Verifying that no other commits have succeed on the stream '{0}'.. + /// + internal static string OptimisticConcurrencyCheck { + get { + return ResourceManager.GetString("OptimisticConcurrencyCheck", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Pushing attempt '{0}' on stream '{1}' bucket '{2}' with '{3}' events to the underlying store.. + /// + internal static string PersistingCommit { + get { + return ResourceManager.GetString("PersistingCommit", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to One or more pipeline hooks filtered out the commit.. + /// + internal static string PipelineHookFilteredCommit { + get { + return ResourceManager.GetString("PipelineHookFilteredCommit", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Pipeline hook of type '{0}' skipped over commit '{1}'.. + /// + internal static string PipelineHookSkippedCommit { + get { + return ResourceManager.GetString("PipelineHookSkippedCommit", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Purging all data from storage.. + /// + internal static string PurgingStore { + get { + return ResourceManager.GetString("PurgingStore", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The collection is read only and cannot be modified.. + /// + internal static string ReadOnlyCollection { + get { + return ResourceManager.GetString("ReadOnlyCollection", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The stream revision must always be greater than or equal to the commit sequence.. + /// + internal static string RevisionTooSmall { + get { + return ResourceManager.GetString("RevisionTooSmall", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Shutting down event store.. + /// + internal static string ShuttingDownStore { + get { + return ResourceManager.GetString("ShuttingDownStore", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Tracking commit {0} on stream '{1}' bucket '{2}'.. + /// + internal static string TrackingCommit { + get { + return ResourceManager.GetString("TrackingCommit", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Tracking up to {0} streams.. + /// + internal static string TrackingStreams { + get { + return ResourceManager.GetString("TrackingStreams", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The underlying stream '{0}' bucket '{1}' has changed since the last known commit, refreshing the stream. Exception Message: {2}. + /// + internal static string UnderlyingStreamHasChanged { + get { + return ResourceManager.GetString("UnderlyingStreamHasChanged", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Updating stream head for stream '{0}' bucket '{1}'.. + /// + internal static string UpdatingStreamHead { + get { + return ResourceManager.GetString("UpdatingStreamHead", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Hook into pipeline with hooks: {0}. + /// + internal static string WireupHookIntoPipeline { + get { + return ResourceManager.GetString("WireupHookIntoPipeline", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Configured Persistence Engine: {0}. + /// + internal static string WireupSetPersistenceEngine { + get { + return ResourceManager.GetString("WireupSetPersistenceEngine", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Configured serializer: {0}. + /// + internal static string WireupSetSerializer { + get { + return ResourceManager.GetString("WireupSetSerializer", resourceCulture); + } + } + } +} diff --git a/src/proj/EventStore.Core/Resources.resx b/src/NEventStore/Resources.resx similarity index 74% rename from src/proj/EventStore.Core/Resources.resx rename to src/NEventStore/Resources.resx index 76a1caab1..4e39441c4 100644 --- a/src/proj/EventStore.Core/Resources.resx +++ b/src/NEventStore/Resources.resx @@ -1,291 +1,276 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - The collection is read only and cannot be modified. - - - The object has already been disposed. - - - The commit must be uniquely identified. - - - The commit sequence must be a positive number. - - - The stream revision must be a positive number. - - - The stream revision must always be greater than or equal to the commit sequence. - - - Starting dispatch scheduler. - - - Initializing persistence engine. - - - Getting undispatched commits from persistence engine. - - - Scheduling commit '{0}' for delivery. - - - Scheduling commit '{0}' to be dispatched. - - - Marking commit '{0}' as dispatched. - - - Disposing engine. - - - Initializing engine. - - - Getting all commits for stream '{0}' between '{1}' and '{2}'. - - - Getting all commits from '{0}' forward. - - - Getting all commits from '{0}' to '{1}'. - - - Attempting to append commit '{0}' to stream '{1}' at position '{2}'. - - - Updating stream head for stream '{0}'. - - - Retrieving all {0} undispatched commits. - - - Marking commit '{0}' as dispatched. - - - Getting the set of all streams to be snapshot which exceed {0} revisions without a snapshot. - - - Getting the most recent snapshot for stream '{0}' on/since revision '{1}'. - - - Adding a snapshot for stream '{0}' at revision '{1}'. - - - Purging all data from storage. - - - Tracking up to {0} streams. - - - Verifying that no other commits have succeed on the stream '{0}'. - - - No other commits have been discovered that conflict for stream '{0}'. - - - Tracking commit {0} on stream '{1}'. - - - Purging all commits on stream '{0}' from tracking. - - - Creating stream '{0}'. - - - Opening stream '{0}' between revisions {1} and {2}. - - - Opening stream '{0}' with snapshot at {1} up to revision {2}. - - - Pipeline hook of type '{0}' skipped over commit '{1}'. - - - One or more pipeline hooks filtered out the commit. - - - Commit attempt failed one or more integrity checks. - - - Pushing commit '{0}' to pre-commit hook of type '{1}'. - - - Pipeline hook of type '{0}' rejected attempt '{1}'. - - - Committing attempt '{0}' which contains {1} events to the underlying persistence engine. - - - Pushing commit '{0}' to post-commit hook of type '{1}'. - - - Adding commit '{0} with {1} events to stream '{2}'. - - - Ignoring some events on commit '{0}' of stream '{1}' because they go beyond revision {2}. - - - Ignoring some events on commit '{0}' of stream '{1}' because they starting before revision {2}. - - - Appending uncommitted event to stream '{0}' - - - Attempting to commit all changes on stream '{0}' to the underlying store. - - - There are no outstanding changes to be committed stream '{0}'. - - - The underlying stream '{0}' has changed since the last known commit, refreshing the stream. - - - Pushing attempt '{0}' on stream '{1}' to the underlying store. - - - Building a commit attempt '{0}' on stream '{1}'. - - - Clearing all uncommitted changes on stream '{0}'. - - - Shutting down event store. - - - Shutting down dispatch scheduler. - - - Shutting down dispatcher. - - - Dispatching message to /dev/null. - - - Configured dispatcher of type '{0}' was unable to dispatch commit '{1}'. - - - Unable to mark commit '{0}' as dispatched, the underlying storage has already been disposed - - - Converting an Event from '{0}' to '{1}'. - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The collection is read only and cannot be modified. + + + The object has already been disposed. + + + The commit sequence must be a positive number. + + + The stream revision must be a positive number. + + + The stream revision must always be greater than or equal to the commit sequence. + + + Initializing persistence engine. + + + Disposing engine. + + + Initializing engine. + + + Getting all commits for stream '{0}' bucket '{1}' between '{2}' and '{3}'. + + + Getting all commits from bucket '{0}' from '{1}' forward. + + + Getting all commits from bucket '{0}' from '{1}' to '{2}'. + + + Attempting to append commit '{0}' to stream '{1}' bucket '{2}' at position '{3}'. + + + Updating stream head for stream '{0}' bucket '{1}'. + + + Getting the set of all streams to be snapshot from bucket '{0}' which exceed {1} revisions without a snapshot. + + + Getting the most recent snapshot from bucket '{0}' for stream '{1}' on/since revision '{2}'. + + + Adding a snapshot for bucket '{0}' stream '{1}' at revision '{2}'. + + + Purging all data from storage. + + + Tracking up to {0} streams. + + + Verifying that no other commits have succeed on the stream '{0}'. + + + No other commits have been discovered that conflict for stream '{0}' bucket '{1}'. + + + Tracking commit {0} on stream '{1}' bucket '{2}'. + + + Purging all commits on stream '{0}' bucket '{1}' from tracking. + + + Creating stream '{0}' in bucket '{1}'. + + + Opening stream '{0}' from bucket '{1}' between revisions {2} and {3}. + + + Opening stream '{0}' from bucket '{1}' with snapshot at {2} up to revision {3}. + + + Pipeline hook of type '{0}' skipped over commit '{1}'. + + + One or more pipeline hooks filtered out the commit. + + + Pushing commit '{0}' to pre-commit hook of type '{1}'. + + + Pipeline hook of type '{0}' rejected attempt '{1}'. + + + Committing attempt '{0}' which contains {1} events to the underlying persistence engine. + + + Pushing commit '{0}' to post-commit hook of type '{1}'. + + + Adding commit '{0}' with {1} events to stream '{2}' bucket'{3}'. + + + Ignoring some events on commit '{0}' of stream '{1}' because they go beyond revision {2}. + + + Ignoring some events on commit '{0}' of stream '{1}' because they starting before revision {2}. + + + Appending uncommitted event '{0}' to stream '{1}' bucket '{2}' + + + Attempting to commit all changes on stream '{0}' bucket '{1}' to the underlying store. + + + There are no outstanding changes to be committed stream '{0}' bucket '{1}'. + + + The underlying stream '{0}' bucket '{1}' has changed since the last known commit, refreshing the stream. Exception Message: {2} + + + Pushing attempt '{0}' on stream '{1}' bucket '{2}' with '{3}' events to the underlying store. + + + Building a commit attempt '{0}' on stream '{1}' bucket '{2}'. + + + Clearing all uncommitted changes on stream '{0}' bucket '{1}'. + + + Shutting down event store. + + + Converting an Event from '{0}' to '{1}'. + + + Getting all commits since checkpoint '{0}' (excluded). + + + Getting all commits from bucket '{0}' since checkpoint '{1}' (excluded). + + + Deleting stream '{0}' from bucket '{1}'. + + + Hook into pipeline with hooks: {0} + + + Configured Persistence Engine: {0} + + + Configured serializer: {0} + + + The stream '{0}' bucket '{1}' was partially loaded up to revision {1}. Cannot append commits to a partially loaded stream, refreshing the stream. + + + Getting all commits from bucket '{0}' from checkpoint '{1}' (excluded) up to '{2}' (included). + + + Getting all commits from checkpoint '{0}' (excluded) up to '{1}' (included). + \ No newline at end of file diff --git a/src/NEventStore/Serialization/ByteStreamDocumentSerializer.cs b/src/NEventStore/Serialization/ByteStreamDocumentSerializer.cs new file mode 100644 index 000000000..e001e03fc --- /dev/null +++ b/src/NEventStore/Serialization/ByteStreamDocumentSerializer.cs @@ -0,0 +1,76 @@ +using Microsoft.Extensions.Logging; +using NEventStore.Logging; + +namespace NEventStore.Serialization +{ + /// + /// A document serializer that uses a serializer to serialize and deserialize objects to and from byte arrays. + /// + public class ByteStreamDocumentSerializer : IDocumentSerializer + { + private static readonly ILogger Logger = LogFactory.BuildLogger(typeof(ByteStreamDocumentSerializer)); + private readonly ISerialize _serializer; + + /// + /// Initializes a new instance of the ByteStreamDocumentSerializer class. + /// + public ByteStreamDocumentSerializer(ISerialize serializer) + { + _serializer = serializer; + } + + /// + /// Serializes the object graph in a byte array + public object Serialize(T graph) where T : notnull + { + if (graph == null) + { + throw new ArgumentNullException(nameof(graph)); + } + if (Logger.IsEnabled(LogLevel.Trace)) + { + Logger.LogTrace(Messages.SerializingGraph, typeof(T)); + } + return _serializer.Serialize(graph); + } + + /// + /// + /// Accepts a byte array (in the form of a byte array or a base64 encoded string) + /// and deserialize it to an object graph. + /// + public T? Deserialize(object document) + { + var bytes = (FromBase64(document as string) ?? document as byte[]) + ?? throw new NotSupportedException("document must be byte[] or a string representing base64 encoded byte[]"); + + if (Logger.IsEnabled(LogLevel.Trace)) + { + Logger.LogTrace(Messages.DeserializingStream, typeof(T)); + } + return _serializer.Deserialize(bytes); + } + + private static byte[]? FromBase64(string? value) + { + if (string.IsNullOrEmpty(value)) + { + return null; + } + + if (Logger.IsEnabled(LogLevel.Trace)) + { + Logger.LogTrace(Messages.InspectingTextStream); + } + + try + { + return Convert.FromBase64String(value); + } + catch (FormatException) + { + return null; + } + } + } +} \ No newline at end of file diff --git a/src/NEventStore/Serialization/DocumentObjectSerializer.cs b/src/NEventStore/Serialization/DocumentObjectSerializer.cs new file mode 100644 index 000000000..d050849fe --- /dev/null +++ b/src/NEventStore/Serialization/DocumentObjectSerializer.cs @@ -0,0 +1,24 @@ +namespace NEventStore.Serialization +{ + /// + /// A simple serializer that does not perform any serialization. + /// + public class DocumentObjectSerializer : IDocumentSerializer + { + /// + public object Serialize(T graph) where T : notnull + { + if (graph == null) + { + throw new ArgumentNullException(nameof(graph)); + } + return graph; + } + + /// + public T? Deserialize(object document) + { + return (T?)document; + } + } +} \ No newline at end of file diff --git a/src/NEventStore/Serialization/GzipSerializer.cs b/src/NEventStore/Serialization/GzipSerializer.cs new file mode 100644 index 000000000..d2dd6edcd --- /dev/null +++ b/src/NEventStore/Serialization/GzipSerializer.cs @@ -0,0 +1,45 @@ +using System.IO.Compression; +using Microsoft.Extensions.Logging; +using NEventStore.Logging; + +namespace NEventStore.Serialization +{ + /// + /// Represents a serializer that compresses the serialized object using GZip. + /// + public class GzipSerializer : ISerialize + { + private static readonly ILogger Logger = LogFactory.BuildLogger(typeof(GzipSerializer)); + private readonly ISerialize _inner; + + /// + /// Initializes a new instance of the GzipSerializer class. + /// + public GzipSerializer(ISerialize inner) + { + _inner = inner; + } + + /// + public virtual void Serialize(Stream output, T graph) where T : notnull + { + if (Logger.IsEnabled(LogLevel.Trace)) + { + Logger.LogTrace(Messages.SerializingGraph, typeof(T)); + } + using var compress = new DeflateStream(output, CompressionMode.Compress, true); + _inner.Serialize(compress, graph); + } + + /// + public virtual T? Deserialize(Stream input) + { + if (Logger.IsEnabled(LogLevel.Trace)) + { + Logger.LogTrace(Messages.DeserializingStream, typeof(T)); + } + using var decompress = new DeflateStream(input, CompressionMode.Decompress, true); + return _inner.Deserialize(decompress); + } + } +} \ No newline at end of file diff --git a/src/NEventStore/Serialization/IDocumentSerializer.cs b/src/NEventStore/Serialization/IDocumentSerializer.cs new file mode 100644 index 000000000..74c51782d --- /dev/null +++ b/src/NEventStore/Serialization/IDocumentSerializer.cs @@ -0,0 +1,27 @@ +namespace NEventStore.Serialization +{ + /// + /// Provides the ability to serialize an object graph to and from a document. + /// + /// + /// Instances of this class must be designed to be multi-thread safe such that they can be shared between threads. + /// + public interface IDocumentSerializer + { + /// + /// Serializes the object graph provided into a document. + /// + /// The type of object to be serialized + /// The object graph to be serialized. + /// The document form of the graph provided. + object Serialize(T graph) where T : notnull; + + /// + /// Deserializes the document provided into an object graph. + /// + /// The type of object graph. + /// The document to be deserialized. + /// An object graph of the specified type. + T? Deserialize(object document); + } +} \ No newline at end of file diff --git a/src/NEventStore/Serialization/ISerialize.cs b/src/NEventStore/Serialization/ISerialize.cs new file mode 100644 index 000000000..ba00cf418 --- /dev/null +++ b/src/NEventStore/Serialization/ISerialize.cs @@ -0,0 +1,29 @@ +namespace NEventStore.Serialization +{ + using System.IO; + + /// + /// Provides the ability to serialize and deserialize an object graph. + /// + /// + /// Instances of this class must be designed to be multi-thread safe such that they can be shared between threads. + /// + public interface ISerialize + { + /// + /// Serializes the object graph provided and writes a serialized representation to the output stream provided. + /// + /// The type of object to be serialized + /// The stream into which the serialized object graph should be written. + /// The object graph to be serialized. + void Serialize(Stream output, T graph) where T : notnull; + + /// + /// Deserializes the stream provided and reconstructs the corresponding object graph. + /// + /// The type of object to be deserialized. + /// The stream of bytes from which the object will be reconstructed. + /// The reconstructed object. + T? Deserialize(Stream input); + } +} \ No newline at end of file diff --git a/src/proj/EventStore.Serialization/Messages.Designer.cs b/src/NEventStore/Serialization/Messages.Designer.cs similarity index 91% rename from src/proj/EventStore.Serialization/Messages.Designer.cs rename to src/NEventStore/Serialization/Messages.Designer.cs index 5c5087240..5bd835077 100644 --- a/src/proj/EventStore.Serialization/Messages.Designer.cs +++ b/src/NEventStore/Serialization/Messages.Designer.cs @@ -1,99 +1,99 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.237 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace EventStore.Serialization { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Messages { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Messages() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("EventStore.Serialization.Messages", typeof(Messages).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to Deserializing stream to object of type '{0}'.. - /// - internal static string DeserializingStream { - get { - return ResourceManager.GetString("DeserializingStream", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Inspecting text-based stream contents.. - /// - internal static string InspectingTextStream { - get { - return ResourceManager.GetString("InspectingTextStream", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The encryption key must be exactly 16 bytes.. - /// - internal static string InvalidKeyLength { - get { - return ResourceManager.GetString("InvalidKeyLength", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Serializing object graph of type '{0}'.. - /// - internal static string SerializingGraph { - get { - return ResourceManager.GetString("SerializingGraph", resourceCulture); - } - } - } -} +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace NEventStore.Serialization { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Messages { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Messages() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("NEventStore.Serialization.Messages", typeof(Messages).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Deserializing stream to object of type '{0}'.. + /// + internal static string DeserializingStream { + get { + return ResourceManager.GetString("DeserializingStream", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Inspecting text-based stream contents.. + /// + internal static string InspectingTextStream { + get { + return ResourceManager.GetString("InspectingTextStream", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The encryption key must be exactly 16 bytes.. + /// + internal static string InvalidKeyLength { + get { + return ResourceManager.GetString("InvalidKeyLength", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Serializing object graph of type '{0}'.. + /// + internal static string SerializingGraph { + get { + return ResourceManager.GetString("SerializingGraph", resourceCulture); + } + } + } +} diff --git a/src/proj/EventStore.Serialization/Messages.resx b/src/NEventStore/Serialization/Messages.resx similarity index 97% rename from src/proj/EventStore.Serialization/Messages.resx rename to src/NEventStore/Serialization/Messages.resx index 9ef54595b..87a2e1f68 100644 --- a/src/proj/EventStore.Serialization/Messages.resx +++ b/src/NEventStore/Serialization/Messages.resx @@ -1,132 +1,132 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - Deserializing stream to object of type '{0}'. - - - Serializing object graph of type '{0}'. - - - The encryption key must be exactly 16 bytes. - - - Inspecting text-based stream contents. - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Deserializing stream to object of type '{0}'. + + + Serializing object graph of type '{0}'. + + + The encryption key must be exactly 16 bytes. + + + Inspecting text-based stream contents. + \ No newline at end of file diff --git a/src/NEventStore/Serialization/NonDisposableStream.cs b/src/NEventStore/Serialization/NonDisposableStream.cs new file mode 100644 index 000000000..4d9ffaf7a --- /dev/null +++ b/src/NEventStore/Serialization/NonDisposableStream.cs @@ -0,0 +1,83 @@ +namespace NEventStore.Serialization +{ + using System.IO; + + /// + /// Represents a stream that wraps another stream and prevents it from being disposed. + /// + internal class NonDisposableStream : Stream + { + private readonly Stream _stream; + + /// + /// Initializes a new instance of the NonDisposableStream class. + /// + public NonDisposableStream(Stream stream) + { + _stream = stream; + } + + public override bool CanRead + { + get { return _stream.CanRead; } + } + + public override bool CanSeek + { + get { return _stream.CanSeek; } + } + + public override bool CanWrite + { + get { return _stream.CanWrite; } + } + + public override long Length + { + get { return _stream.Length; } + } + + public override long Position + { + get { return _stream.Position; } + set { _stream.Position = value; } + } + +#pragma warning disable CA2215 // Dispose methods should call base class dispose + protected override void Dispose(bool disposing) +#pragma warning restore CA2215 // Dispose methods should call base class dispose + { + // no-op + } + + public override void Close() + { + // no-op + } + + public override void Flush() + { + _stream.Flush(); + } + + public override long Seek(long offset, SeekOrigin origin) + { + return _stream.Seek(offset, origin); + } + + public override void SetLength(long value) + { + _stream.SetLength(value); + } + + public override int Read(byte[] buffer, int offset, int count) + { + return _stream.Read(buffer, offset, count); + } + + public override void Write(byte[] buffer, int offset, int count) + { + _stream.Write(buffer, offset, count); + } + } +} \ No newline at end of file diff --git a/src/NEventStore/Serialization/RijndaelSerializer.cs b/src/NEventStore/Serialization/RijndaelSerializer.cs new file mode 100644 index 000000000..6bd1e615b --- /dev/null +++ b/src/NEventStore/Serialization/RijndaelSerializer.cs @@ -0,0 +1,85 @@ +using System.Collections; +using System.Security.Cryptography; +using Microsoft.Extensions.Logging; +using NEventStore.Logging; + +namespace NEventStore.Serialization +{ + /// + /// Represents a serializer that encrypts the serialized data using the Rijndael algorithm. + /// + public class RijndaelSerializer : ISerialize + { + private const int KeyLength = 16; // bytes + private static readonly ILogger Logger = LogFactory.BuildLogger(typeof(RijndaelSerializer)); + private readonly byte[] _encryptionKey; + private readonly ISerialize _inner; + + /// + /// Initializes a new instance of the RijndaelSerializer class. + /// + /// + public RijndaelSerializer(ISerialize inner, byte[] encryptionKey) + { + if (!KeyIsValid(encryptionKey, KeyLength)) + { + throw new ArgumentException(Messages.InvalidKeyLength, nameof(encryptionKey)); + } + + _encryptionKey = encryptionKey; + _inner = inner; + } + + /// + public virtual void Serialize(Stream output, T graph) where T: notnull + { + if (Logger.IsEnabled(LogLevel.Trace)) + { + Logger.LogTrace(Messages.SerializingGraph, typeof(T)); + } + + using var rijndael = new RijndaelManaged(); + rijndael.Key = _encryptionKey; + rijndael.Mode = CipherMode.CBC; + rijndael.GenerateIV(); + + using ICryptoTransform encryptor = rijndael.CreateEncryptor(); + using var wrappedOutput = new NonDisposableStream(output); + using var encryptionStream = new CryptoStream(wrappedOutput, encryptor, CryptoStreamMode.Write); + wrappedOutput.Write(rijndael.IV, 0, rijndael.IV.Length); + _inner.Serialize(encryptionStream, graph); + encryptionStream.Flush(); + encryptionStream.FlushFinalBlock(); + } + + /// + public virtual T? Deserialize(Stream input) + { + if (Logger.IsEnabled(LogLevel.Trace)) + { + Logger.LogTrace(Messages.DeserializingStream, typeof(T)); + } + + using var rijndael = new RijndaelManaged(); + rijndael.Key = _encryptionKey; + rijndael.IV = GetInitVectorFromStream(input, rijndael.IV.Length); + rijndael.Mode = CipherMode.CBC; + + using ICryptoTransform decrypter = rijndael.CreateDecryptor(); + using var decryptedStream = new CryptoStream(input, decrypter, CryptoStreamMode.Read); + return _inner.Deserialize(decryptedStream); + } + + private static bool KeyIsValid(ICollection key, int length) + { + return key != null && key.Count == length; + } + + private static byte[] GetInitVectorFromStream(Stream encrypted, int initVectorSizeInBytes) + { + var buffer = new byte[initVectorSizeInBytes]; + encrypted.Read(buffer, 0, buffer.Length); + return buffer; + } + } +} \ No newline at end of file diff --git a/src/NEventStore/Serialization/SerializationExtensions.cs b/src/NEventStore/Serialization/SerializationExtensions.cs new file mode 100644 index 000000000..6e7e7cb93 --- /dev/null +++ b/src/NEventStore/Serialization/SerializationExtensions.cs @@ -0,0 +1,42 @@ + +namespace NEventStore.Serialization +{ + /// + /// Implements extension methods that make call to the serialization infrastructure more simple. + /// + public static class SerializationExtensions + { + /// + /// Serializes the object provided. + /// + /// The type of object to be serialized + /// The serializer to use. + /// The object graph to be serialized. + /// A serialized representation of the object graph provided. + public static byte[] Serialize(this ISerialize serializer, T value) where T : notnull + { + using var stream = new MemoryStream(); + serializer.Serialize(stream, value); + return stream.ToArray(); + } + + /// + /// Deserializes the array of bytes provided. + /// + /// The type of object to be deserialized. + /// The serializer to use. + /// The serialized array of bytes. + /// The reconstituted object, if any. + public static T? Deserialize(this ISerialize serializer, byte[] serialized) + { + // add null or empty check + if (serialized == null || serialized.Length == 0) + { + throw new ArgumentNullException(nameof(serialized), "cannot be null or empty."); + } + + using var stream = new MemoryStream(serialized); + return serializer.Deserialize(stream); + } + } +} \ No newline at end of file diff --git a/src/NEventStore/SerializationWireup.cs b/src/NEventStore/SerializationWireup.cs new file mode 100644 index 000000000..e98f8f591 --- /dev/null +++ b/src/NEventStore/SerializationWireup.cs @@ -0,0 +1,63 @@ +using Microsoft.Extensions.Logging; +using NEventStore.Logging; +using NEventStore.Serialization; + +namespace NEventStore +{ + /// + /// Represents the configuration for serialization. + /// + public class SerializationWireup : Wireup + { + private static readonly ILogger Logger = LogFactory.BuildLogger(typeof(SerializationWireup)); + + /// + /// Initializes a new instance of the SerializationWireup class. + /// + public SerializationWireup(Wireup inner, ISerialize serializer) + : base(inner) + { + Container.Register(serializer); + } + + /// + /// Enable GZip compression on the serialized stream. + /// + public SerializationWireup Compress() + { + if (Logger.IsEnabled(LogLevel.Debug)) + { + Logger.LogDebug(Messages.ConfiguringCompression); + } + var wrapped = Container.Resolve() + ?? throw new InvalidOperationException("Serialize not configured."); + + if (Logger.IsEnabled(LogLevel.Information)) + { + Logger.LogInformation(Messages.WrappingSerializerGZip, wrapped.GetType()); + } + Container.Register(new GzipSerializer(wrapped)); + return this; + } + + /// + /// Enable Rijndael encryption on the serialized stream. + /// + public SerializationWireup EncryptWith(byte[] encryptionKey) + { + if (Logger.IsEnabled(LogLevel.Debug)) + { + Logger.LogDebug(Messages.ConfiguringEncryption); + } + var wrapped = Container.Resolve() + ?? throw new InvalidOperationException("Serialize not configured."); + + if (Logger.IsEnabled(LogLevel.Information)) + { + Logger.LogInformation(Messages.WrappingSerializerEncryption, wrapped.GetType()); + } + Container.Register(new RijndaelSerializer(wrapped, encryptionKey)); + return this; + } + } +} \ No newline at end of file diff --git a/src/NEventStore/SerializationWireupExtensions.cs b/src/NEventStore/SerializationWireupExtensions.cs new file mode 100644 index 000000000..faa39c577 --- /dev/null +++ b/src/NEventStore/SerializationWireupExtensions.cs @@ -0,0 +1,26 @@ +using NEventStore.Logging; +using Microsoft.Extensions.Logging; +using NEventStore.Serialization; + +namespace NEventStore +{ + /// + /// Represents the configuration for serialization. + /// + public static class SerializationWireupExtensions + { + private static readonly ILogger Logger = LogFactory.BuildLogger(typeof(PersistenceWireup)); + + /// + /// Configure custom serialization. + /// + public static SerializationWireup UsingCustomSerialization(this PersistenceWireup wireup, ISerialize serializer) + { + if (Logger.IsEnabled(LogLevel.Information)) + { + Logger.LogInformation(Resources.WireupSetSerializer, serializer.GetType()); + } + return new SerializationWireup(wireup, serializer); + } + } +} \ No newline at end of file diff --git a/src/NEventStore/Snapshot.cs b/src/NEventStore/Snapshot.cs new file mode 100644 index 000000000..6fb768167 --- /dev/null +++ b/src/NEventStore/Snapshot.cs @@ -0,0 +1,66 @@ +using System.Runtime.Serialization; + +namespace NEventStore +{ + /// + /// Represents a materialized view of a stream at specific revision. + /// + [DataContract] + [Serializable] + public class Snapshot : ISnapshot + { + /// + /// Initializes a new instance of the Snapshot class for the default bucket. + /// + /// The value which uniquely identifies the stream to which the snapshot applies. + /// The position at which the snapshot applies. + /// The snapshot or materialized view of the stream at the revision indicated. + public Snapshot(string streamId, int streamRevision, object payload) + : this(Bucket.Default, streamId, streamRevision, payload) + {} + + /// + /// Initializes a new instance of the Snapshot class. + /// + /// The value which uniquely identifies bucket the stream belongs to. + /// The value which uniquely identifies the stream to which the snapshot applies. + /// The position at which the snapshot applies. + /// The snapshot or materialized view of the stream at the revision indicated. + public Snapshot(string bucketId, string streamId, int streamRevision, object payload) + : this() + { + BucketId = bucketId; + StreamId = streamId; + StreamRevision = streamRevision; + Payload = payload; + } + + /// + /// Initializes a new instance of the Snapshot class. + /// + protected Snapshot() + {} + + /// + [DataMember] + public virtual string BucketId { get; private set; } + + /// + /// Gets the value which uniquely identifies the stream to which the snapshot applies. + /// + [DataMember] + public virtual string StreamId { get; private set; } + + /// + /// Gets the position at which the snapshot applies. + /// + [DataMember] + public virtual int StreamRevision { get; private set; } + + /// + /// Gets the snapshot or materialized view of the stream at the revision indicated. + /// + [DataMember] + public virtual object Payload { get; private set; } + } +} \ No newline at end of file diff --git a/src/NEventStore/StoreEventsExtensions.cs b/src/NEventStore/StoreEventsExtensions.cs new file mode 100644 index 000000000..bbd8a8d5d --- /dev/null +++ b/src/NEventStore/StoreEventsExtensions.cs @@ -0,0 +1,167 @@ +using NEventStore.Persistence; + +namespace NEventStore +{ + /// + /// Provides extension methods for . + /// + public static class StoreEventsExtensions + { + /// + /// Creates a new stream. + /// + /// The store events instance. + /// The value which uniquely identifies the stream to be created. + /// An empty stream. + public static IEventStream CreateStream(this IStoreEvents storeEvents, Guid streamId) + { + return CreateStream(storeEvents, Bucket.Default, streamId); + } + + /// + /// Creates a new stream. + /// + /// The store events instance. + /// The value which uniquely identifies the stream to be created. + /// An empty stream. + public static IEventStream CreateStream(this IStoreEvents storeEvents, string streamId) + { + EnsureStoreEventsNotNull(storeEvents); + return storeEvents.CreateStream(Bucket.Default, streamId); + } + + /// + /// Creates a new stream. + /// + /// The store events instance. + /// The value which uniquely identifies bucket the stream belongs to. + /// The value which uniquely identifies the stream within the bucket to be created. + /// An empty stream. + public static IEventStream CreateStream(this IStoreEvents storeEvents, string bucketId, Guid streamId) + { + EnsureStoreEventsNotNull(storeEvents); + return storeEvents.CreateStream(bucketId, streamId.ToString()); + } + + /// + /// Reads the stream indicated from the minimum revision specified up to the maximum revision specified or creates + /// an empty stream if no commits are found and a minimum revision of zero is provided. + /// + /// The store events instance. + /// The value which uniquely identifies the stream from which the events will be read. + /// The minimum revision of the stream to be read. + /// The maximum revision of the stream to be read. + /// A series of committed events represented as a stream. + /// + /// + /// + public static IEventStream OpenStream(this IStoreEvents storeEvents, Guid streamId, int minRevision = int.MinValue, int maxRevision = int.MaxValue) + { + EnsureStoreEventsNotNull(storeEvents); + return OpenStream(storeEvents, Bucket.Default, streamId, minRevision, maxRevision); + } + + /// + /// Reads the stream indicated from the minimum revision specified up to the maximum revision specified or creates + /// an empty stream if no commits are found and a minimum revision of zero is provided. + /// + /// The store events instance. + /// The value which uniquely identifies the stream from which the events will be read. + /// The minimum revision of the stream to be read. + /// The maximum revision of the stream to be read. + /// A series of committed events represented as a stream. + /// + /// + /// + public static IEventStream OpenStream(this IStoreEvents storeEvents, string streamId, int minRevision = int.MinValue, int maxRevision = int.MaxValue) + { + EnsureStoreEventsNotNull(storeEvents); + return storeEvents.OpenStream(Bucket.Default, streamId, minRevision, maxRevision); + } + + /// + /// Reads the stream indicated from the minimum revision specified up to the maximum revision specified or creates + /// an empty stream if no commits are found and a minimum revision of zero is provided. + /// + /// The store events instance. + /// The value which uniquely identifies bucket the stream belongs to. + /// The value which uniquely identifies the stream within the bucket to be created. + /// The minimum revision of the stream to be read. + /// The maximum revision of the stream to be read. + /// A series of committed events represented as a stream. + /// + /// + /// + public static IEventStream OpenStream(this IStoreEvents storeEvents, string bucketId, Guid streamId, int minRevision = int.MinValue, int maxRevision = int.MaxValue) + { + EnsureStoreEventsNotNull(storeEvents); + return storeEvents.OpenStream(bucketId, streamId.ToString(), minRevision, maxRevision); + } + + /// + /// Reads the stream indicated from the minimum revision specified up to the maximum revision specified or creates + /// an empty stream if no commits are found and a minimum revision of zero is provided. + /// + /// The store events instance. + /// The value which uniquely identifies the stream from which the events will be read. + /// The minimum revision of the stream to be read. + /// The maximum revision of the stream to be read. + /// The token to monitor for cancellation requests. + /// A series of committed events represented as a stream. + /// + /// + /// + public static Task OpenStreamAsync(this IStoreEvents storeEvents, Guid streamId, int minRevision = int.MinValue, int maxRevision = int.MaxValue, CancellationToken cancellationToken = default) + { + EnsureStoreEventsNotNull(storeEvents); + return OpenStreamAsync(storeEvents, Bucket.Default, streamId, minRevision, maxRevision, cancellationToken); + } + + /// + /// Reads the stream indicated from the minimum revision specified up to the maximum revision specified or creates + /// an empty stream if no commits are found and a minimum revision of zero is provided. + /// + /// The store events instance. + /// The value which uniquely identifies the stream from which the events will be read. + /// The minimum revision of the stream to be read. + /// The maximum revision of the stream to be read. + /// The token to monitor for cancellation requests. + /// A series of committed events represented as a stream. + /// + /// + /// + public static Task OpenStreamAsync(this IStoreEvents storeEvents, string streamId, int minRevision = int.MinValue, int maxRevision = int.MaxValue, CancellationToken cancellationToken = default) + { + EnsureStoreEventsNotNull(storeEvents); + return storeEvents.OpenStreamAsync(Bucket.Default, streamId, minRevision, maxRevision, cancellationToken); + } + + /// + /// Reads the stream indicated from the minimum revision specified up to the maximum revision specified or creates + /// an empty stream if no commits are found and a minimum revision of zero is provided. + /// + /// The store events instance. + /// The value which uniquely identifies bucket the stream belongs to. + /// The value which uniquely identifies the stream within the bucket to be created. + /// The minimum revision of the stream to be read. + /// The maximum revision of the stream to be read. + /// The token to monitor for cancellation requests. + /// A series of committed events represented as a stream. + /// + /// + /// + public static Task OpenStreamAsync(this IStoreEvents storeEvents, string bucketId, Guid streamId, int minRevision = int.MinValue, int maxRevision = int.MaxValue, CancellationToken cancellationToken = default) + { + EnsureStoreEventsNotNull(storeEvents); + return storeEvents.OpenStreamAsync(bucketId, streamId.ToString(), minRevision, maxRevision, cancellationToken); + } + + private static void EnsureStoreEventsNotNull(IStoreEvents storeEvents) + { + if (storeEvents == null) + { + throw new ArgumentNullException(nameof(storeEvents)); + } + } + } +} \ No newline at end of file diff --git a/src/NEventStore/StreamHeadObserver.cs b/src/NEventStore/StreamHeadObserver.cs new file mode 100644 index 000000000..1c30129cf --- /dev/null +++ b/src/NEventStore/StreamHeadObserver.cs @@ -0,0 +1,54 @@ +using NEventStore.Persistence; +using System.Runtime.ExceptionServices; + +namespace NEventStore +{ + /// + /// Represents an async observer that can receive and stores commits from a stream. + /// Can be used as base class for other observers. + /// + public class StreamHeadObserver : IAsyncObserver + { + /// + /// The list of commits read from the stream + /// + public IList StreamHeads { get; } = []; + + /// + /// Indicates if the read operation has completed + /// + public bool ReadCompleted { get; private set; } + + /// + /// Store the commits received from the stream + /// + public virtual Task OnNextAsync(IStreamHead value, CancellationToken cancellationToken) + { + StreamHeads.Add(value); + return Task.FromResult(true); + } + + /// + /// Notifies the observer that the provider has experienced an error condition. + /// + /// Preserve the stack trace and rethrow the exception that occurred while reading commits from the stream. + /// + /// + /// Override this method to log and handle the error. + /// + /// + public virtual Task OnErrorAsync(Exception ex, CancellationToken cancellationToken) + { + // Preserve the stack trace and rethrow the exception + ExceptionDispatchInfo.Capture(ex).Throw(); + return Task.CompletedTask; + } + + /// + public virtual Task OnCompletedAsync(CancellationToken cancellationToken) + { + ReadCompleted = true; + return Task.CompletedTask; + } + } +} \ No newline at end of file diff --git a/src/NEventStore/StreamNotFoundException.cs b/src/NEventStore/StreamNotFoundException.cs new file mode 100644 index 000000000..25478862d --- /dev/null +++ b/src/NEventStore/StreamNotFoundException.cs @@ -0,0 +1,43 @@ +using System.Runtime.Serialization; + +namespace NEventStore +{ + /// + /// Represents an attempt to retrieve a nonexistent event stream. + /// + [Serializable] + public class StreamNotFoundException : Exception + { + /// + /// Initializes a new instance of the StreamNotFoundException class. + /// + public StreamNotFoundException() + {} + + /// + /// Initializes a new instance of the StreamNotFoundException class. + /// + /// The message that describes the error. + public StreamNotFoundException(string message) + : base(message) + {} + + /// + /// Initializes a new instance of the StreamNotFoundException class. + /// + /// The message that describes the error. + /// The message that is the cause of the current exception. + public StreamNotFoundException(string message, Exception innerException) + : base(message, innerException) + {} + + /// + /// Initializes a new instance of the StreamNotFoundException class. + /// + /// The SerializationInfo that holds the serialized object data of the exception being thrown. + /// The StreamingContext that contains contextual information about the source or destination. + protected StreamNotFoundException(SerializationInfo info, StreamingContext context) + : base(info, context) + {} + } +} \ No newline at end of file diff --git a/src/NEventStore/SystemTime.cs b/src/NEventStore/SystemTime.cs new file mode 100644 index 000000000..ad5cb4ae4 --- /dev/null +++ b/src/NEventStore/SystemTime.cs @@ -0,0 +1,23 @@ +namespace NEventStore +{ + /// + /// Provides the ability to override the current moment in time to facilitate testing. + /// Original idea by Ayende Rahien: + /// http://ayende.com/Blog/archive/2008/07/07/Dealing-with-time-in-tests.aspx + /// + public static class SystemTime + { + /// + /// The callback to be used to resolve the current moment in time. + /// + public static Func? Resolver { get; set; } + + /// + /// Gets the current moment in time. + /// + public static DateTime UtcNow + { + get { return Resolver == null ? DateTime.UtcNow : Resolver(); } + } + } +} \ No newline at end of file diff --git a/src/NEventStore/TaskHelpers.cs b/src/NEventStore/TaskHelpers.cs new file mode 100644 index 000000000..173ead8e4 --- /dev/null +++ b/src/NEventStore/TaskHelpers.cs @@ -0,0 +1,78 @@ +namespace NEventStore +{ + internal static class TaskHelpers + { + internal static Task Delay(double milliseconds, CancellationToken cancellationToken) + { + var tcs = new TaskCompletionSource(); + TimerCallback callback = (_) => tcs.TrySetResult(true); +#pragma warning disable IDE0067 // Dispose objects before losing scope + var timer = new Timer(callback, null, 0, Convert.ToInt32(milliseconds)); +#pragma warning restore IDE0067 // Dispose objects before losing scope + CancellationTokenRegistration cancellationTokenRegistration = cancellationToken.Register(() => + { + timer.Dispose(); + tcs.TrySetCanceled(); + }); + return tcs.Task.ContinueWith(_ => + { + cancellationTokenRegistration.Dispose(); + timer.Dispose(); + }, TaskContinuationOptions.ExecuteSynchronously); + } + + public static void WhenCompleted(this Task task, Action> onComplete, Action> onFaulted, bool execSync = false) + { + if (task.IsCompleted) + { + if (task.IsFaulted) + { + onFaulted.Invoke(task); + return; + } + + onComplete.Invoke(task); + return; + } + + task.ContinueWith( + onComplete, + execSync ? + TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnRanToCompletion : + TaskContinuationOptions.OnlyOnRanToCompletion); + + task.ContinueWith( + onFaulted, + execSync ? + TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnFaulted : + TaskContinuationOptions.OnlyOnFaulted); + } + + public static void WhenCompleted(this Task task, Action onComplete, Action onFaulted, bool execSync = false) + { + if (task.IsCompleted) + { + if (task.IsFaulted) + { + onFaulted.Invoke(task); + return; + } + + onComplete.Invoke(task); + return; + } + + task.ContinueWith( + onComplete, + execSync ? + TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnRanToCompletion : + TaskContinuationOptions.OnlyOnRanToCompletion); + + task.ContinueWith( + onFaulted, + execSync ? + TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnFaulted : + TaskContinuationOptions.OnlyOnFaulted); + } + } +} \ No newline at end of file diff --git a/src/NEventStore/Wireup.cs b/src/NEventStore/Wireup.cs new file mode 100644 index 000000000..5a6cbdd00 --- /dev/null +++ b/src/NEventStore/Wireup.cs @@ -0,0 +1,157 @@ +using NEventStore.Conversion; +using NEventStore.Persistence; +using NEventStore.Persistence.InMemory; +using NEventStore.Logging; +using Microsoft.Extensions.Logging; + +namespace NEventStore +{ + /// + /// Represents the configuration for the event store. + /// + public class Wireup + { + private readonly NanoContainer? _container; + private readonly Wireup? _inner; + private readonly ILogger Logger = LogFactory.BuildLogger(typeof(Wireup)); + + /// + /// Initializes a new instance of the Wireup class. + /// + protected Wireup(NanoContainer container) + { + _container = container ?? throw new ArgumentNullException(nameof(container)); + } + + /// + /// Initializes a new instance of the Wireup class. + /// + protected Wireup(Wireup inner) + { + _inner = inner ?? throw new ArgumentNullException(nameof(inner)); + } + + /// + /// Gets the container. + /// + protected NanoContainer Container + { + get { return _container ?? _inner!.Container; } + } + + /// + /// Initializes a new instance of the Wire-up class. + /// + public static Wireup Init() + { + var container = new NanoContainer(); + + container.Register(new InMemoryPersistenceEngine()); + container.Register(BuildEventStore); + + return new Wireup(container); + } + + /// + /// Registers a service with the container. + /// + public virtual Wireup Register(T instance) where T : class + { + Container.Register(instance); + return this; + } + + /// + /// Add pipeline hooks to the processing pipeline. + /// + public virtual Wireup HookIntoPipelineUsing(IEnumerable hooks) + { + return HookIntoPipelineUsing((hooks ?? []).ToArray()); + } + + /// + /// Add pipeline hooks to the processing pipeline. + /// + public virtual Wireup HookIntoPipelineUsing(params IPipelineHook[] hooks) + { + if (Logger.IsEnabled(LogLevel.Information)) + { + Logger.LogInformation(Resources.WireupHookIntoPipeline, string.Join(", ", hooks.Select(h => h.GetType()))); + } + ICollection collection = (hooks ?? []).Where(x => x != null).ToArray(); + Container.Register(collection); + return this; + } + + /// + /// Add pipeline hooks to the processing pipeline. + /// + public virtual Wireup HookIntoPipelineUsing(IEnumerable hooks) + { + return HookIntoPipelineUsing((hooks ?? []).ToArray()); + } + + /// + /// Add pipeline hooks to the processing pipeline. + /// Asynchronous hooks are executed after the synchronous hooks. + /// + public virtual Wireup HookIntoPipelineUsing(params IPipelineHookAsync[] hooks) + { + if (Logger.IsEnabled(LogLevel.Information)) + { + Logger.LogInformation(Resources.WireupHookIntoPipeline, string.Join(", ", hooks.Select(h => h.GetType()))); + } + ICollection collection = (hooks ?? []).Where(x => x != null).ToArray(); + Container.Register(collection); + return this; + } + + /// + /// Builds the configured event store. + /// + public virtual IStoreEvents Build() + { + if (_inner != null) + { + return _inner.Build(); + } + + return Container.Resolve() + ?? throw new InvalidOperationException("IStoreEvents was not registered."); + } + + /// + /// + /// Provide some additional concurrency checks to avoid useless roundtrips to the databases in a non-transactional environment. + /// + /// + /// If you enable any sort of two-phase commit and/or transactional behavior on the Persistence drivers + /// you should not use the module. + /// + /// + public Wireup UseOptimisticPipelineHook(int maxStreamsToTrack = OptimisticPipelineHook.MaxStreamsToTrack) + { + Container.Register(_ => new OptimisticPipelineHook(maxStreamsToTrack)); + return this; + } + + private static IStoreEvents BuildEventStore(NanoContainer context) + { + var concurrency = context.Resolve(); + var upconverter = context.Resolve(); + + ICollection hooks = context.Resolve>() ?? []; + var pipelineHooks = new List(hooks); + if (concurrency != null) + { + pipelineHooks.Add(concurrency); + } + if (upconverter != null) + { + pipelineHooks.Add(upconverter); + } + ICollection hooksAsync = context.Resolve>() ?? []; + return new OptimisticEventStore(context.Resolve()!, pipelineHooks, hooksAsync); + } + } +} \ No newline at end of file diff --git a/src/Settings.FxCop b/src/Settings.FxCop deleted file mode 100644 index 7331f7732..000000000 --- a/src/Settings.FxCop +++ /dev/null @@ -1,186 +0,0 @@ - - - - True - c:\program files (x86)\microsoft fxcop 1.36\Xml\FxCopReport.xsl - - - - - - True - True - True - 10 - 1 - - False - - False - 15 - True - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Settings.StyleCop b/src/Settings.StyleCop deleted file mode 100644 index d89358c53..000000000 --- a/src/Settings.StyleCop +++ /dev/null @@ -1,184 +0,0 @@ - - - - - - - False - - - - - False - - - - - False - - - - - - - - - - False - - - - - False - - - - - False - - - - - - - - - - False - - - - - False - - - - - - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - - - - - - False - - - - - False - - - - - False - - - - - False - - - - - - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - False - - - - - - - - - - False - - - - - False - - - - - False - - - - - False - - - - - - - \ No newline at end of file diff --git a/src/packages.config b/src/packages.config new file mode 100644 index 000000000..e37e764ee --- /dev/null +++ b/src/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/proj/EventStore.Core/Conversion/EventUpconverterPipelineHook.cs b/src/proj/EventStore.Core/Conversion/EventUpconverterPipelineHook.cs deleted file mode 100644 index 51d5eccf5..000000000 --- a/src/proj/EventStore.Core/Conversion/EventUpconverterPipelineHook.cs +++ /dev/null @@ -1,56 +0,0 @@ -namespace EventStore.Conversion -{ - using System; - using System.Collections.Generic; - using Logging; - - public class EventUpconverterPipelineHook : IPipelineHook - { - private static readonly ILog Logger = LogFactory.BuildLogger(typeof(EventUpconverterPipelineHook)); - private readonly IDictionary> converters; - - public EventUpconverterPipelineHook(IDictionary> converters) - { - if (converters == null) - throw new ArgumentNullException("converters"); - - this.converters = converters; - } - public void Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); - } - protected virtual void Dispose(bool disposing) - { - this.converters.Clear(); - } - - public virtual Commit Select(Commit committed) - { - foreach (var eventMessage in committed.Events) - eventMessage.Body = this.Convert(eventMessage.Body); - - return committed; - } - private object Convert(object source) - { - Func converter; - if (!this.converters.TryGetValue(source.GetType(), out converter)) - return source; - - var target = converter(source); - Logger.Debug(Resources.ConvertingEvent, source.GetType(), target.GetType()); - - return this.Convert(target); - } - - public virtual bool PreCommit(Commit attempt) - { - return true; - } - public virtual void PostCommit(Commit committed) - { - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Core/Diagnostics/PerformanceCounterPersistenceEngine.cs b/src/proj/EventStore.Core/Diagnostics/PerformanceCounterPersistenceEngine.cs deleted file mode 100644 index 9fa34be90..000000000 --- a/src/proj/EventStore.Core/Diagnostics/PerformanceCounterPersistenceEngine.cs +++ /dev/null @@ -1,97 +0,0 @@ -namespace EventStore.Diagnostics -{ - using System; - using System.Collections.Generic; - using System.Diagnostics; - using Persistence; - - public class PerformanceCounterPersistenceEngine : IPersistStreams - { - public virtual void Initialize() - { - this.persistence.Initialize(); - } - - public virtual void Commit(Commit attempt) - { - var clock = Stopwatch.StartNew(); - this.persistence.Commit(attempt); - clock.Stop(); - - this.counters.CountCommit(attempt.Events.Count, clock.ElapsedMilliseconds); - } - public virtual void MarkCommitAsDispatched(Commit commit) - { - this.persistence.MarkCommitAsDispatched(commit); - this.counters.CountCommitDispatched(); - } - - public IEnumerable GetFromTo(DateTime start, DateTime end) - { - return this.persistence.GetFromTo(start, end); - } - - public virtual IEnumerable GetUndispatchedCommits() - { - return this.persistence.GetUndispatchedCommits(); - } - - public virtual IEnumerable GetFrom(Guid streamId, int minRevision, int maxRevision) - { - return this.persistence.GetFrom(streamId, minRevision, maxRevision); - } - public virtual IEnumerable GetFrom(DateTime start) - { - return this.persistence.GetFrom(start); - } - - public virtual bool AddSnapshot(Snapshot snapshot) - { - var result = this.persistence.AddSnapshot(snapshot); - if (result) - this.counters.CountSnapshot(); - - return result; - } - public virtual Snapshot GetSnapshot(Guid streamId, int maxRevision) - { - return this.persistence.GetSnapshot(streamId, maxRevision); - } - public virtual IEnumerable GetStreamsToSnapshot(int maxThreshold) - { - return this.persistence.GetStreamsToSnapshot(maxThreshold); - } - - public virtual void Purge() - { - this.persistence.Purge(); - } - - public PerformanceCounterPersistenceEngine(IPersistStreams persistence, string instanceName) - { - this.persistence = persistence; - this.counters = new PerformanceCounters(instanceName); - } - ~PerformanceCounterPersistenceEngine() - { - this.Dispose(false); - } - - public void Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); - } - protected virtual void Dispose(bool disposing) - { - if (!disposing) - return; - - this.counters.Dispose(); - this.persistence.Dispose(); - } - - private readonly PerformanceCounters counters; - private readonly IPersistStreams persistence; - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Core/Diagnostics/PerformanceCounters.cs b/src/proj/EventStore.Core/Diagnostics/PerformanceCounters.cs deleted file mode 100644 index 90f2a5dd2..000000000 --- a/src/proj/EventStore.Core/Diagnostics/PerformanceCounters.cs +++ /dev/null @@ -1,115 +0,0 @@ -namespace EventStore.Diagnostics -{ - using System; - using System.Diagnostics; - - internal class PerformanceCounters : IDisposable - { - public void CountCommit(int eventsCount, long elapsedMilliseconds) - { - this.totalCommits.Increment(); - this.commitsRate.Increment(); - this.avgCommitDuration.IncrementBy(elapsedMilliseconds); - this.avgCommitDurationBase.Increment(); - this.totalEvents.IncrementBy(eventsCount); - this.eventsRate.IncrementBy(eventsCount); - this.undispatchedCommits.Increment(); - } - public void CountSnapshot() - { - this.totalSnapshots.Increment(); - this.snapshotsRate.Increment(); - } - public void CountCommitDispatched() - { - this.undispatchedCommits.Decrement(); - } - - static PerformanceCounters() - { - if (PerformanceCounterCategory.Exists(CategoryName)) - return; - - var counters = new CounterCreationDataCollection - { - new CounterCreationData(TotalCommitsName, "Total number of commits persisted", PerformanceCounterType.NumberOfItems32), - new CounterCreationData(CommitsRateName, "Rate of commits persisted per second", PerformanceCounterType.RateOfCountsPerSecond32), - new CounterCreationData(AvgCommitDuration, "Average duration for each commit", PerformanceCounterType.AverageTimer32), - new CounterCreationData(AvgCommitDurationBase, "Average duration base for each commit", PerformanceCounterType.AverageBase), - new CounterCreationData(TotalEventsName, "Total number of events persisted", PerformanceCounterType.NumberOfItems32), - new CounterCreationData(EventsRateName, "Rate of events persisted per second", PerformanceCounterType.RateOfCountsPerSecond32), - new CounterCreationData(TotalSnapshotsName, "Total number of snapshots persisted", PerformanceCounterType.NumberOfItems32), - new CounterCreationData(SnapshotsRateName, "Rate of snapshots persisted per second", PerformanceCounterType.RateOfCountsPerSecond32), - new CounterCreationData(UndispatchedQueue, "Undispatched commit queue length", PerformanceCounterType.CountPerTimeInterval32) - }; - - // TODO: add other useful counts such as: - // - // * Total Commit Bytes - // * Average Commit Bytes - // * Total Queries - // * Queries Per Second - // * Average Query Duration - // * Commits per Query (Total / average / per second) - // * Events per Query (Total / average / per second) - // - // Some of these will involve hooking into other parts of the EventStore - - PerformanceCounterCategory.Create(CategoryName, "EventStore Event-Sourcing Persistence", PerformanceCounterCategoryType.MultiInstance, counters); - } - public PerformanceCounters(string instanceName) - { - this.totalCommits = new PerformanceCounter(CategoryName, TotalCommitsName, instanceName, false); - this.commitsRate = new PerformanceCounter(CategoryName, CommitsRateName, instanceName, false); - this.avgCommitDuration = new PerformanceCounter(CategoryName, AvgCommitDuration, instanceName, false); - this.avgCommitDurationBase = new PerformanceCounter(CategoryName, AvgCommitDurationBase, instanceName, false); - this.totalEvents = new PerformanceCounter(CategoryName, TotalEventsName, instanceName, false); - this.eventsRate = new PerformanceCounter(CategoryName, EventsRateName, instanceName, false); - this.totalSnapshots = new PerformanceCounter(CategoryName, TotalSnapshotsName, instanceName, false); - this.snapshotsRate = new PerformanceCounter(CategoryName, SnapshotsRateName, instanceName, false); - this.undispatchedCommits = new PerformanceCounter(CategoryName, UndispatchedQueue, instanceName, false); - } - ~PerformanceCounters() - { - this.Dispose(false); - } - - public void Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); - } - protected virtual void Dispose(bool disposing) - { - this.totalCommits.Dispose(); - this.commitsRate.Dispose(); - this.avgCommitDuration.Dispose(); - this.avgCommitDurationBase.Dispose(); - this.totalEvents.Dispose(); - this.eventsRate.Dispose(); - this.totalSnapshots.Dispose(); - this.snapshotsRate.Dispose(); - this.undispatchedCommits.Dispose(); - } - - private const string CategoryName = "EventStore"; - private const string TotalCommitsName = "Total Commits"; - private const string CommitsRateName = "Commits/Sec"; - private const string AvgCommitDuration = "Average Commit Duration"; - private const string AvgCommitDurationBase = "Average Commit Duration Base"; - private const string TotalEventsName = "Total Events"; - private const string EventsRateName = "Events/Sec"; - private const string TotalSnapshotsName = "Total Snapshots"; - private const string SnapshotsRateName = "Snapshots/Sec"; - private const string UndispatchedQueue = "Undispatched Queue Length"; - private readonly PerformanceCounter totalCommits; - private readonly PerformanceCounter commitsRate; - private readonly PerformanceCounter avgCommitDuration; - private readonly PerformanceCounter avgCommitDurationBase; - private readonly PerformanceCounter totalEvents; - private readonly PerformanceCounter eventsRate; - private readonly PerformanceCounter totalSnapshots; - private readonly PerformanceCounter snapshotsRate; - private readonly PerformanceCounter undispatchedCommits; - } -} diff --git a/src/proj/EventStore.Core/DispatchSchedulerPipelineHook.cs b/src/proj/EventStore.Core/DispatchSchedulerPipelineHook.cs deleted file mode 100644 index 90a02f3f0..000000000 --- a/src/proj/EventStore.Core/DispatchSchedulerPipelineHook.cs +++ /dev/null @@ -1,42 +0,0 @@ -namespace EventStore -{ - using System; - using Dispatcher; - - public class DispatchSchedulerPipelineHook : IPipelineHook - { - private readonly IScheduleDispatches scheduler; - - public DispatchSchedulerPipelineHook() - : this(null) - { - } - public DispatchSchedulerPipelineHook(IScheduleDispatches scheduler) - { - this.scheduler = scheduler ?? new NullDispatcher(); // serves as a scheduler also - } - public void Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); - } - protected virtual void Dispose(bool disposing) - { - this.scheduler.Dispose(); - } - - public Commit Select(Commit committed) - { - return committed; - } - public virtual bool PreCommit(Commit attempt) - { - return true; - } - public void PostCommit(Commit committed) - { - if (committed != null) - this.scheduler.ScheduleDispatch(committed); - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Core/Dispatcher/AsynchronousDispatchScheduler.cs b/src/proj/EventStore.Core/Dispatcher/AsynchronousDispatchScheduler.cs deleted file mode 100644 index 66edcc106..000000000 --- a/src/proj/EventStore.Core/Dispatcher/AsynchronousDispatchScheduler.cs +++ /dev/null @@ -1,56 +0,0 @@ -namespace EventStore.Dispatcher -{ - using System; - using System.Threading; - using Logging; - using Persistence; - using System.Collections.Concurrent; - using System.Threading.Tasks; - - public class AsynchronousDispatchScheduler : SynchronousDispatchScheduler - { - private static readonly ILog Logger = LogFactory.BuildLogger(typeof(AsynchronousDispatchScheduler)); - private BlockingCollection _queue; - private int _boundedCapacity = 1024; - private Task _worker; - private bool _working; - - public AsynchronousDispatchScheduler(IDispatchCommits dispatcher, IPersistStreams persistence) - : base(dispatcher, persistence) - { - } - - protected override void Start() - { - _queue = new BlockingCollection(new ConcurrentQueue(), _boundedCapacity); - _worker = new Task(Working); - _working = true; - _worker.Start(); - - base.Start(); - } - - public override void ScheduleDispatch(Commit commit) - { - Logger.Info(Resources.SchedulingDelivery, commit.CommitId); - _queue.Add(commit); - } - - void Working() - { - while (_working) - { - Commit commit = null; - if (_queue.TryTake(out commit, 100)) - base.ScheduleDispatch(commit); - } - } - protected override void Dispose(bool disposing) - { - base.Dispose(disposing); - _working = false; - } - - } - -} \ No newline at end of file diff --git a/src/proj/EventStore.Core/Dispatcher/DelegateMessageDispatcher.cs b/src/proj/EventStore.Core/Dispatcher/DelegateMessageDispatcher.cs deleted file mode 100644 index 9698d1639..000000000 --- a/src/proj/EventStore.Core/Dispatcher/DelegateMessageDispatcher.cs +++ /dev/null @@ -1,29 +0,0 @@ -namespace EventStore.Dispatcher -{ - using System; - - public class DelegateMessageDispatcher : IDispatchCommits - { - private readonly Action dispatch; - - public DelegateMessageDispatcher(Action dispatch) - { - this.dispatch = dispatch; - } - - public void Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); - } - protected virtual void Dispose(bool disposing) - { - // no op - } - - public virtual void Dispatch(Commit commit) - { - this.dispatch(commit); - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Core/Dispatcher/NullDispatcher.cs b/src/proj/EventStore.Core/Dispatcher/NullDispatcher.cs deleted file mode 100644 index 77dca33f9..000000000 --- a/src/proj/EventStore.Core/Dispatcher/NullDispatcher.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace EventStore.Dispatcher -{ - using System; - using Logging; - - public sealed class NullDispatcher : IScheduleDispatches, IDispatchCommits - { - private static readonly ILog Logger = LogFactory.BuildLogger(typeof(NullDispatcher)); - - public void Dispose() - { - Logger.Debug(Resources.ShuttingDownDispatcher); - GC.SuppressFinalize(this); - } - public void ScheduleDispatch(Commit commit) - { - Logger.Info(Resources.SchedulingDispatch, commit.CommitId); - this.Dispatch(commit); - } - public void Dispatch(Commit commit) - { - Logger.Info(Resources.DispatchingToDevNull); - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Core/Dispatcher/SynchronousDispatchScheduler.cs b/src/proj/EventStore.Core/Dispatcher/SynchronousDispatchScheduler.cs deleted file mode 100644 index ec302f636..000000000 --- a/src/proj/EventStore.Core/Dispatcher/SynchronousDispatchScheduler.cs +++ /dev/null @@ -1,80 +0,0 @@ -namespace EventStore.Dispatcher -{ - using System; - using Logging; - using Persistence; - - public class SynchronousDispatchScheduler : IScheduleDispatches - { - private static readonly ILog Logger = LogFactory.BuildLogger(typeof(SynchronousDispatchScheduler)); - private readonly IDispatchCommits dispatcher; - private readonly IPersistStreams persistence; - private bool disposed; - - public SynchronousDispatchScheduler(IDispatchCommits dispatcher, IPersistStreams persistence) - { - this.dispatcher = dispatcher; - this.persistence = persistence; - - Logger.Info(Resources.StartingDispatchScheduler); - this.Start(); - } - - public void Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); - } - protected virtual void Dispose(bool disposing) - { - if (!disposing || this.disposed) - return; - - Logger.Debug(Resources.ShuttingDownDispatchScheduler); - this.disposed = true; - this.dispatcher.Dispose(); - this.persistence.Dispose(); - } - - protected virtual void Start() - { - Logger.Debug(Resources.InitializingPersistence); - this.persistence.Initialize(); - - Logger.Debug(Resources.GettingUndispatchedCommits); - foreach (var commit in this.persistence.GetUndispatchedCommits()) - this.ScheduleDispatch(commit); - } - - public virtual void ScheduleDispatch(Commit commit) - { - this.DispatchImmediately(commit); - this.MarkAsDispatched(commit); - } - private void DispatchImmediately(Commit commit) - { - try - { - Logger.Info(Resources.SchedulingDispatch, commit.CommitId); - this.dispatcher.Dispatch(commit); - } - catch - { - Logger.Error(Resources.UnableToDispatch, this.dispatcher.GetType(), commit.CommitId); - throw; - } - } - private void MarkAsDispatched(Commit commit) - { - try - { - Logger.Info(Resources.MarkingCommitAsDispatched, commit.CommitId); - this.persistence.MarkCommitAsDispatched(commit); - } - catch (ObjectDisposedException) - { - Logger.Warn(Resources.UnableToMarkDispatched, commit.CommitId); - } - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Core/EventStore.Core.csproj b/src/proj/EventStore.Core/EventStore.Core.csproj deleted file mode 100644 index 716ddeeb5..000000000 --- a/src/proj/EventStore.Core/EventStore.Core.csproj +++ /dev/null @@ -1,96 +0,0 @@ - - - - Debug - AnyCPU - 9.0.30729 - 2.0 - {D6413244-42F5-4233-B347-D0A804B09CC9} - Library - Properties - EventStore - EventStore.Core - v4.0 - 512 - true - ..\..\EventStore.snk - ..\..\..\src\ - true - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - Properties\GlobalAssemblyInfo.cs - - - Properties\GlobalSuppressions.cs - - - Properties\VersionAssemblyInfo.cs - - - - - - - - - - - - - - - - - - - - - - True - True - Resources.resx - - - - - Properties\CustomDictionary.xml - - - - - {03946843-F343-419C-88EF-3E446D08DFA6} - EventStore - - - - - - - - ResXFileCodeGenerator - Resources.Designer.cs - Designer - - - - - - \ No newline at end of file diff --git a/src/proj/EventStore.Core/ExtensionMethods.cs b/src/proj/EventStore.Core/ExtensionMethods.cs deleted file mode 100644 index 34a09da8d..000000000 --- a/src/proj/EventStore.Core/ExtensionMethods.cs +++ /dev/null @@ -1,37 +0,0 @@ -namespace EventStore -{ - using System; - - internal static class ExtensionMethods - { - public static bool IsValid(this Commit attempt) - { - if (attempt == null) - throw new ArgumentNullException("attempt"); - - if (!attempt.HasIdentifier()) - throw new ArgumentException(Resources.CommitsMustBeUniquelyIdentified, "attempt"); - - if (attempt.CommitSequence <= 0) - throw new ArgumentException(Resources.NonPositiveSequenceNumber, "attempt"); - - if (attempt.StreamRevision <= 0) - throw new ArgumentException(Resources.NonPositiveRevisionNumber, "attempt"); - - if (attempt.StreamRevision < attempt.CommitSequence) - throw new ArgumentException(Resources.RevisionTooSmall, "attempt"); - - return true; - } - - public static bool HasIdentifier(this Commit attempt) - { - return attempt.StreamId != Guid.Empty && attempt.CommitId != Guid.Empty; - } - - public static bool IsEmpty(this Commit attempt) - { - return attempt == null || attempt.Events.Count == 0; - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Core/ImmutableCollection.cs b/src/proj/EventStore.Core/ImmutableCollection.cs deleted file mode 100644 index 5623669ef..000000000 --- a/src/proj/EventStore.Core/ImmutableCollection.cs +++ /dev/null @@ -1,70 +0,0 @@ -namespace EventStore -{ - using System; - using System.Collections; - using System.Collections.Generic; - using System.Linq; - - internal class ImmutableCollection : ICollection, ICollection - { - private readonly object @lock = new object(); - private readonly ICollection inner; - - public ImmutableCollection(ICollection inner) - { - this.inner = inner; - } - - public virtual int Count - { - get { return this.inner.Count; } - } - public virtual object SyncRoot - { - get { return this.@lock; } - } - public virtual bool IsSynchronized - { - get { return false; } - } - public virtual bool IsReadOnly - { - get { return true; } - } - - public virtual IEnumerator GetEnumerator() - { - return this.inner.GetEnumerator(); - } - IEnumerator IEnumerable.GetEnumerator() - { - return this.GetEnumerator(); - } - - public virtual void Add(T item) - { - throw new NotSupportedException(Resources.ReadOnlyCollection); - } - public virtual bool Remove(T item) - { - throw new NotSupportedException(Resources.ReadOnlyCollection); - } - public virtual void Clear() - { - throw new NotSupportedException(Resources.ReadOnlyCollection); - } - - public virtual bool Contains(T item) - { - return this.inner.Contains(item); - } - public virtual void CopyTo(T[] array, int arrayIndex) - { - this.inner.CopyTo(array, arrayIndex); - } - public virtual void CopyTo(Array array, int index) - { - this.CopyTo(array.Cast().ToArray(), index); - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Core/Logging/ConsoleWindowLogger.cs b/src/proj/EventStore.Core/Logging/ConsoleWindowLogger.cs deleted file mode 100644 index 79f1fa4ee..000000000 --- a/src/proj/EventStore.Core/Logging/ConsoleWindowLogger.cs +++ /dev/null @@ -1,51 +0,0 @@ -namespace EventStore.Logging -{ - using System; - - public class ConsoleWindowLogger : ILog - { - private static readonly object Sync = new object(); - private readonly ConsoleColor originalColor = Console.ForegroundColor; - private readonly Type typeToLog; - - public ConsoleWindowLogger(Type typeToLog) - { - this.typeToLog = typeToLog; - } - - public virtual void Verbose(string message, params object[] values) - { - this.Log(ConsoleColor.DarkGreen, message, values); - } - public virtual void Debug(string message, params object[] values) - { - this.Log(ConsoleColor.Green, message, values); - } - public virtual void Info(string message, params object[] values) - { - this.Log(ConsoleColor.White, message, values); - } - public virtual void Warn(string message, params object[] values) - { - this.Log(ConsoleColor.Yellow, message, values); - } - public virtual void Error(string message, params object[] values) - { - this.Log(ConsoleColor.DarkRed, message, values); - } - public virtual void Fatal(string message, params object[] values) - { - this.Log(ConsoleColor.Red, message, values); - } - - private void Log(ConsoleColor color, string message, params object[] values) - { - lock (Sync) - { - Console.ForegroundColor = color; - Console.WriteLine(message.FormatMessage(this.typeToLog, values)); - Console.ForegroundColor = this.originalColor; - } - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Core/Logging/ExtensionMethods.cs b/src/proj/EventStore.Core/Logging/ExtensionMethods.cs deleted file mode 100644 index 92c066c52..000000000 --- a/src/proj/EventStore.Core/Logging/ExtensionMethods.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace EventStore.Logging -{ - using System; - using System.Globalization; - using System.Threading; - - internal static class ExtensionMethods - { - private const string MessageFormat = "{0:yyyy/MM/dd HH:mm:ss.ff} - {1} - {2} - {3}"; - - public static string FormatMessage(this string message, Type typeToLog, params object[] values) - { - return string.Format( - CultureInfo.InvariantCulture, - MessageFormat, - DateTime.UtcNow, - Thread.CurrentThread.GetName(), - typeToLog.FullName, - string.Format(CultureInfo.InvariantCulture, message, values)); - } - private static string GetName(this Thread thread) - { - return !string.IsNullOrEmpty(thread.Name) - ? thread.Name - : thread.ManagedThreadId.ToString(CultureInfo.InvariantCulture); - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Core/Logging/OutputWindowLogger.cs b/src/proj/EventStore.Core/Logging/OutputWindowLogger.cs deleted file mode 100644 index dbe01aa89..000000000 --- a/src/proj/EventStore.Core/Logging/OutputWindowLogger.cs +++ /dev/null @@ -1,52 +0,0 @@ -namespace EventStore.Logging -{ - using System; - using System.Diagnostics; - - public class OutputWindowLogger : ILog - { - private static readonly object Sync = new object(); - private readonly Type typeToLog; - - public OutputWindowLogger(Type typeToLog) - { - this.typeToLog = typeToLog; - } - - public virtual void Verbose(string message, params object[] values) - { - this.DebugWindow("Verbose", message, values); - } - public virtual void Debug(string message, params object[] values) - { - this.DebugWindow("Debug", message, values); - } - public virtual void Info(string message, params object[] values) - { - this.TraceWindow("Info", message, values); - } - public virtual void Warn(string message, params object[] values) - { - this.TraceWindow("Warn", message, values); - } - public virtual void Error(string message, params object[] values) - { - this.TraceWindow("Error", message, values); - } - public virtual void Fatal(string message, params object[] values) - { - this.TraceWindow("Fatal", message, values); - } - - protected virtual void DebugWindow(string category, string message, params object[] values) - { - lock (Sync) - System.Diagnostics.Debug.WriteLine(category, message.FormatMessage(this.typeToLog, values)); - } - protected virtual void TraceWindow(string category, string message, params object[] values) - { - lock (Sync) - Trace.WriteLine(category, message.FormatMessage(this.typeToLog, values)); - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Core/OptimisticEventStore.cs b/src/proj/EventStore.Core/OptimisticEventStore.cs deleted file mode 100644 index f2375ddf2..000000000 --- a/src/proj/EventStore.Core/OptimisticEventStore.cs +++ /dev/null @@ -1,112 +0,0 @@ -namespace EventStore -{ - using System; - using System.Collections.Generic; - using System.Linq; - using Logging; - using Persistence; - - public class OptimisticEventStore : IStoreEvents, ICommitEvents - { - private static readonly ILog Logger = LogFactory.BuildLogger(typeof(OptimisticEventStore)); - private readonly IPersistStreams persistence; - private readonly IEnumerable pipelineHooks; - - public OptimisticEventStore(IPersistStreams persistence, IEnumerable pipelineHooks) - { - if (persistence == null) - throw new ArgumentNullException("persistence"); - - this.persistence = persistence; - this.pipelineHooks = pipelineHooks ?? new IPipelineHook[0]; - } - - public void Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); - } - protected virtual void Dispose(bool disposing) - { - if (!disposing) - return; - - Logger.Info(Resources.ShuttingDownStore); - this.persistence.Dispose(); - foreach (var hook in this.pipelineHooks) - hook.Dispose(); - } - - public virtual IEventStream CreateStream(Guid streamId) - { - Logger.Info(Resources.CreatingStream, streamId); - return new OptimisticEventStream(streamId, this); - } - public virtual IEventStream OpenStream(Guid streamId, int minRevision, int maxRevision) - { - maxRevision = maxRevision <= 0 ? int.MaxValue : maxRevision; - - Logger.Debug(Resources.OpeningStreamAtRevision, streamId, minRevision, maxRevision); - return new OptimisticEventStream(streamId, this, minRevision, maxRevision); - } - public virtual IEventStream OpenStream(Snapshot snapshot, int maxRevision) - { - if (snapshot == null) - throw new ArgumentNullException("snapshot"); - - Logger.Debug(Resources.OpeningStreamWithSnapshot, snapshot.StreamId, snapshot.StreamRevision, maxRevision); - maxRevision = maxRevision <= 0 ? int.MaxValue : maxRevision; - return new OptimisticEventStream(snapshot, this, maxRevision); - } - - public virtual IEnumerable GetFrom(Guid streamId, int minRevision, int maxRevision) - { - foreach (var commit in this.persistence.GetFrom(streamId, minRevision, maxRevision)) - { - var filtered = commit; - foreach (var hook in this.pipelineHooks.Where(x => (filtered = x.Select(filtered)) == null)) - { - Logger.Info(Resources.PipelineHookSkippedCommit, hook.GetType(), commit.CommitId); - break; - } - - if (filtered == null) - Logger.Info(Resources.PipelineHookFilteredCommit); - else - yield return filtered; - } - } - public virtual void Commit(Commit attempt) - { - if (!attempt.IsValid() || attempt.IsEmpty()) - { - Logger.Debug(Resources.CommitAttemptFailedIntegrityChecks); - return; - } - - foreach (var hook in this.pipelineHooks) - { - Logger.Debug(Resources.InvokingPreCommitHooks, attempt.CommitId, hook.GetType()); - if (hook.PreCommit(attempt)) - continue; - - Logger.Info(Resources.CommitRejectedByPipelineHook, hook.GetType(), attempt.CommitId); - return; - } - - Logger.Info(Resources.CommittingAttempt, attempt.CommitId, attempt.Events.Count); - this.persistence.Commit(attempt); - - foreach (var hook in this.pipelineHooks) - { - Logger.Debug(Resources.InvokingPostCommitPipelineHooks, attempt.CommitId, hook.GetType()); - hook.PostCommit(attempt); - } - } - - public virtual IPersistStreams Advanced - { - get { return this.persistence; } - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Core/OptimisticEventStream.cs b/src/proj/EventStore.Core/OptimisticEventStream.cs deleted file mode 100644 index bcbae0bb6..000000000 --- a/src/proj/EventStore.Core/OptimisticEventStream.cs +++ /dev/null @@ -1,191 +0,0 @@ -namespace EventStore -{ - using System; - using System.Collections.Generic; - using System.Diagnostics.CodeAnalysis; - using System.Linq; - using Logging; - - [SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix", - Justification = "This behaves like a stream--not a .NET 'Stream' object, but a stream nonetheless.")] - public class OptimisticEventStream : IEventStream - { - private static readonly ILog Logger = LogFactory.BuildLogger(typeof(OptimisticEventStream)); - private readonly ICollection committed = new LinkedList(); - private readonly ICollection events = new LinkedList(); - private readonly IDictionary uncommittedHeaders = new Dictionary(); - private readonly IDictionary committedHeaders = new Dictionary(); - private readonly ICollection identifiers = new HashSet(); - private readonly ICommitEvents persistence; - private bool disposed; - - public OptimisticEventStream(Guid streamId, ICommitEvents persistence) - { - this.StreamId = streamId; - this.persistence = persistence; - } - public OptimisticEventStream(Guid streamId, ICommitEvents persistence, int minRevision, int maxRevision) - : this(streamId, persistence) - { - var commits = persistence.GetFrom(streamId, minRevision, maxRevision); - this.PopulateStream(minRevision, maxRevision, commits); - - if (minRevision > 0 && this.committed.Count == 0) - throw new StreamNotFoundException(); - } - public OptimisticEventStream(Snapshot snapshot, ICommitEvents persistence, int maxRevision) - : this(snapshot.StreamId, persistence) - { - var commits = persistence.GetFrom(snapshot.StreamId, snapshot.StreamRevision, maxRevision); - this.PopulateStream(snapshot.StreamRevision + 1, maxRevision, commits); - this.StreamRevision = snapshot.StreamRevision + this.committed.Count; - } - - protected void PopulateStream(int minRevision, int maxRevision, IEnumerable commits) - { - foreach (var commit in commits ?? new Commit[0]) - { - Logger.Verbose(Resources.AddingCommitsToStream, commit.CommitId, commit.Events.Count, this.StreamId); - this.identifiers.Add(commit.CommitId); - - this.CommitSequence = commit.CommitSequence; - var currentRevision = commit.StreamRevision - commit.Events.Count + 1; - if (currentRevision > maxRevision) - return; - - this.CopyToCommittedHeaders(commit); - this.CopyToEvents(minRevision, maxRevision, currentRevision, commit); - } - } - private void CopyToCommittedHeaders(Commit commit) - { - foreach (var key in commit.Headers.Keys) - this.committedHeaders[key] = commit.Headers[key]; - } - private void CopyToEvents(int minRevision, int maxRevision, int currentRevision, Commit commit) - { - foreach (var @event in commit.Events) - { - if (currentRevision > maxRevision) - { - Logger.Debug(Resources.IgnoringBeyondRevision, commit.CommitId, this.StreamId, maxRevision); - break; - } - - if (currentRevision++ < minRevision) - { - Logger.Debug(Resources.IgnoringBeforeRevision, commit.CommitId, this.StreamId, maxRevision); - continue; - } - - this.committed.Add(@event); - this.StreamRevision = currentRevision - 1; - } - } - - public void Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); - } - protected virtual void Dispose(bool disposing) - { - this.disposed = true; - } - - public virtual Guid StreamId { get; private set; } - public virtual int StreamRevision { get; private set; } - public virtual int CommitSequence { get; private set; } - - public virtual ICollection CommittedEvents - { - get { return new ImmutableCollection(this.committed); } - } - public virtual IDictionary CommittedHeaders - { - get { return this.committedHeaders; } - } - - public virtual ICollection UncommittedEvents - { - get { return new ImmutableCollection(this.events); } - } - public virtual IDictionary UncommittedHeaders - { - get { return this.uncommittedHeaders; } - } - - public virtual void Add(EventMessage uncommittedEvent) - { - if (uncommittedEvent == null || uncommittedEvent.Body == null) - return; - - Logger.Debug(Resources.AppendingUncommittedToStream, this.StreamId); - this.events.Add(uncommittedEvent); - } - - public virtual void CommitChanges(Guid commitId) - { - Logger.Debug(Resources.AttemptingToCommitChanges, this.StreamId); - - if (this.identifiers.Contains(commitId)) - throw new DuplicateCommitException(); - - if (!this.HasChanges()) - return; - - try - { - this.PersistChanges(commitId); - } - catch (ConcurrencyException) - { - Logger.Info(Resources.UnderlyingStreamHasChanged, this.StreamId); - var commits = this.persistence.GetFrom(this.StreamId, this.StreamRevision + 1, int.MaxValue); - this.PopulateStream(this.StreamRevision + 1, int.MaxValue, commits); - - throw; - } - } - protected virtual bool HasChanges() - { - if (this.disposed) - throw new ObjectDisposedException(Resources.AlreadyDisposed); - - if (this.events.Count > 0) - return true; - - Logger.Warn(Resources.NoChangesToCommit, this.StreamId); - return false; - } - protected virtual void PersistChanges(Guid commitId) - { - var attempt = this.BuildCommitAttempt(commitId); - - Logger.Debug(Resources.PersistingCommit, commitId, this.StreamId); - this.persistence.Commit(attempt); - - this.PopulateStream(this.StreamRevision + 1, attempt.StreamRevision, new[] { attempt }); - this.ClearChanges(); - } - protected virtual Commit BuildCommitAttempt(Guid commitId) - { - Logger.Debug(Resources.BuildingCommitAttempt, commitId, this.StreamId); - return new Commit( - this.StreamId, - this.StreamRevision + this.events.Count, - commitId, - this.CommitSequence + 1, - SystemTime.UtcNow, - this.uncommittedHeaders.ToDictionary(x => x.Key, x => x.Value), - this.events.ToList()); - } - - public virtual void ClearChanges() - { - Logger.Debug(Resources.ClearingUncommittedChanges, this.StreamId); - this.events.Clear(); - this.uncommittedHeaders.Clear(); - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Core/OptimisticPipelineHook.cs b/src/proj/EventStore.Core/OptimisticPipelineHook.cs deleted file mode 100644 index 899d8c240..000000000 --- a/src/proj/EventStore.Core/OptimisticPipelineHook.cs +++ /dev/null @@ -1,127 +0,0 @@ -namespace EventStore -{ - using System; - using System.Collections.Generic; - using Logging; - using Persistence; - - /// - /// Tracks the heads of streams to reduce latency by avoiding roundtrips to storage. - /// - public class OptimisticPipelineHook : IPipelineHook - { - private static readonly ILog Logger = LogFactory.BuildLogger(typeof(OptimisticPipelineHook)); - private const int MaxStreamsToTrack = 100; - private readonly LinkedList maxItemsToTrack = new LinkedList(); - private readonly IDictionary heads = new Dictionary(); - private readonly int maxStreamsToTrack; - - public OptimisticPipelineHook() - : this(MaxStreamsToTrack) - { - } - public OptimisticPipelineHook(int maxStreamsToTrack) - { - Logger.Debug(Resources.TrackingStreams, maxStreamsToTrack); - this.maxStreamsToTrack = maxStreamsToTrack; - } - public void Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); - } - protected virtual void Dispose(bool disposing) - { - this.heads.Clear(); - this.maxItemsToTrack.Clear(); - } - - public virtual Commit Select(Commit committed) - { - this.Track(committed); - return committed; - } - public virtual bool PreCommit(Commit attempt) - { - Logger.Debug(Resources.OptimisticConcurrencyCheck, attempt.StreamId); - - var head = this.GetStreamHead(attempt.StreamId); - if (head == null) - return true; - - if (head.CommitSequence >= attempt.CommitSequence) - throw new ConcurrencyException(); - - if (head.StreamRevision >= attempt.StreamRevision) - throw new ConcurrencyException(); - - if (head.CommitSequence < attempt.CommitSequence - 1) - throw new StorageException(); // beyond the end of the stream - - if (head.StreamRevision < attempt.StreamRevision - attempt.Events.Count) - throw new StorageException(); // beyond the end of the stream - - Logger.Debug(Resources.NoConflicts, attempt.StreamId); - return true; - } - public virtual void PostCommit(Commit committed) - { - this.Track(committed); - } - - public virtual void Track(Commit committed) - { - if (committed == null) - return; - - lock (this.maxItemsToTrack) - { - this.UpdateStreamHead(committed); - this.TrackUpToCapacity(committed); - } - } - private void UpdateStreamHead(Commit committed) - { - var head = this.GetStreamHead(committed.StreamId); - if (AlreadyTracked(head)) - this.maxItemsToTrack.Remove(committed.StreamId); - - head = head ?? committed; - head = head.StreamRevision > committed.StreamRevision ? head : committed; - - this.heads[committed.StreamId] = head; - } - private static bool AlreadyTracked(Commit head) - { - return head != null; - } - private void TrackUpToCapacity(Commit committed) - { - Logger.Verbose(Resources.TrackingCommit, committed.CommitSequence, committed.StreamId); - this.maxItemsToTrack.AddFirst(committed.StreamId); - if (this.maxItemsToTrack.Count <= this.maxStreamsToTrack) - return; - - var expired = this.maxItemsToTrack.Last.Value; - Logger.Verbose(Resources.NoLongerTrackingStream, expired); - - this.heads.Remove(expired); - this.maxItemsToTrack.RemoveLast(); - } - - public virtual bool Contains(Commit attempt) - { - return this.GetStreamHead(attempt.StreamId) != null; - } - - private Commit GetStreamHead(Guid streamId) - { - lock (this.maxItemsToTrack) - { - Commit head; - this.heads.TryGetValue(streamId, out head); - return head; - } - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Core/Persistence/InMemoryPersistence/InMemoryPersistenceEngine.cs b/src/proj/EventStore.Core/Persistence/InMemoryPersistence/InMemoryPersistenceEngine.cs deleted file mode 100644 index 51f74c140..000000000 --- a/src/proj/EventStore.Core/Persistence/InMemoryPersistence/InMemoryPersistenceEngine.cs +++ /dev/null @@ -1,173 +0,0 @@ -namespace EventStore.Persistence.InMemoryPersistence -{ - using System; - using System.Collections.Generic; - using System.Linq; - using Logging; - - public class InMemoryPersistenceEngine : IPersistStreams - { - private static readonly ILog Logger = LogFactory.BuildLogger(typeof(InMemoryPersistenceEngine)); - private readonly IList commits = new List(); - private readonly ICollection heads = new LinkedList(); - private readonly ICollection undispatched = new LinkedList(); - private readonly ICollection snapshots = new LinkedList(); - private readonly IDictionary stamps = new Dictionary(); - private bool disposed; - - public void Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); - } - protected virtual void Dispose(bool disposing) - { - this.disposed = true; - Logger.Info(Resources.DisposingEngine); - } - private void ThrowWhenDisposed() - { - if (!this.disposed) - return; - - Logger.Warn(Resources.AlreadyDisposed); - throw new ObjectDisposedException(Resources.AlreadyDisposed); - } - - public void Initialize() - { - Logger.Info(Resources.InitializingEngine); - } - - public virtual IEnumerable GetFrom(Guid streamId, int minRevision, int maxRevision) - { - this.ThrowWhenDisposed(); - Logger.Debug(Resources.GettingAllCommitsFromRevision, streamId, minRevision, maxRevision); - - lock (this.commits) - return this.commits.Where(x => x.StreamId == streamId && x.StreamRevision >= minRevision && (x.StreamRevision - x.Events.Count + 1) <= maxRevision).ToArray(); - } - - public virtual IEnumerable GetFrom(DateTime start) - { - this.ThrowWhenDisposed(); - Logger.Debug(Resources.GettingAllCommitsFromTime, start); - - var commitId = this.stamps.Where(x => x.Value >= start).Select(x => x.Key).FirstOrDefault(); - if (commitId == Guid.Empty) - return new Commit[] { }; - - var startingCommit = this.commits.FirstOrDefault(x => x.CommitId == commitId); - return this.commits.Skip(this.commits.IndexOf(startingCommit)); - } - - public virtual IEnumerable GetFromTo(DateTime start, DateTime end) - { - this.ThrowWhenDisposed(); - Logger.Debug(Resources.GettingAllCommitsFromToTime, start, end); - - var commitId = this.stamps.Where(x => x.Value >= start && x.Value < end).Select(x => x.Key).FirstOrDefault(); - if (commitId == Guid.Empty) - return new Commit[] { }; - - var startingCommit = this.commits.FirstOrDefault(x => x.CommitId == commitId); - return this.commits.Skip(this.commits.IndexOf(startingCommit)); - } - - public virtual void Commit(Commit attempt) - { - this.ThrowWhenDisposed(); - Logger.Debug(Resources.AttemptingToCommit, attempt.CommitId, attempt.StreamId, attempt.CommitSequence); - - lock (this.commits) - { - if (this.commits.Contains(attempt)) - throw new DuplicateCommitException(); - if (this.commits.Any(c => c.StreamId == attempt.StreamId && c.StreamRevision == attempt.StreamRevision)) - throw new ConcurrencyException(); - - this.stamps[attempt.CommitId] = attempt.CommitStamp; - this.commits.Add(attempt); - - this.undispatched.Add(attempt); - - var head = this.heads.FirstOrDefault(x => x.StreamId == attempt.StreamId); - this.heads.Remove(head); - - Logger.Debug(Resources.UpdatingStreamHead, attempt.StreamId); - var snapshotRevision = head == null ? 0 : head.SnapshotRevision; - this.heads.Add(new StreamHead(attempt.StreamId, attempt.StreamRevision, snapshotRevision)); - } - } - - public virtual IEnumerable GetUndispatchedCommits() - { - lock (this.commits) - { - this.ThrowWhenDisposed(); - Logger.Debug(Resources.RetrievingUndispatchedCommits, this.commits.Count); - return this.commits.Where(c => this.undispatched.Contains(c)); - } - } - public virtual void MarkCommitAsDispatched(Commit commit) - { - this.ThrowWhenDisposed(); - Logger.Debug(Resources.MarkingAsDispatched, commit.CommitId); - - lock (this.commits) - this.undispatched.Remove(commit); - } - - public virtual IEnumerable GetStreamsToSnapshot(int maxThreshold) - { - this.ThrowWhenDisposed(); - Logger.Debug(Resources.GettingStreamsToSnapshot, maxThreshold); - - lock (this.commits) - return this.heads.Where(x => x.HeadRevision >= x.SnapshotRevision + maxThreshold) - .Select(stream => new StreamHead(stream.StreamId, stream.HeadRevision, stream.SnapshotRevision)); - } - public virtual Snapshot GetSnapshot(Guid streamId, int maxRevision) - { - this.ThrowWhenDisposed(); - Logger.Debug(Resources.GettingSnapshotForStream, streamId, maxRevision); - - lock (this.commits) - return this.snapshots - .Where(x => x.StreamId == streamId && x.StreamRevision <= maxRevision) - .OrderByDescending(x => x.StreamRevision) - .FirstOrDefault(); - } - public virtual bool AddSnapshot(Snapshot snapshot) - { - this.ThrowWhenDisposed(); - Logger.Debug(Resources.AddingSnapshot, snapshot.StreamId, snapshot.StreamRevision); - - lock (this.commits) - { - var currentHead = this.heads.FirstOrDefault(h => h.StreamId == snapshot.StreamId); - if (currentHead == null) - return false; - - this.snapshots.Add(snapshot); - this.heads.Remove(currentHead); - this.heads.Add(new StreamHead(currentHead.StreamId, currentHead.HeadRevision, snapshot.StreamRevision)); - } - - return true; - } - - public virtual void Purge() - { - this.ThrowWhenDisposed(); - Logger.Warn(Resources.PurgingStore); - - lock (this.commits) - { - this.commits.Clear(); - this.snapshots.Clear(); - this.heads.Clear(); - } - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Core/Persistence/InMemoryPersistence/InMemoryPersistenceFactory.cs b/src/proj/EventStore.Core/Persistence/InMemoryPersistence/InMemoryPersistenceFactory.cs deleted file mode 100644 index 0dc138c20..000000000 --- a/src/proj/EventStore.Core/Persistence/InMemoryPersistence/InMemoryPersistenceFactory.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace EventStore.Persistence.InMemoryPersistence -{ - public class InMemoryPersistenceFactory : IPersistenceFactory - { - public virtual IPersistStreams Build() - { - return new InMemoryPersistenceEngine(); - } - } -} diff --git a/src/proj/EventStore.Core/Properties/AssemblyInfo.cs b/src/proj/EventStore.Core/Properties/AssemblyInfo.cs deleted file mode 100644 index 7e6f99a20..000000000 --- a/src/proj/EventStore.Core/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,6 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -[assembly: AssemblyTitle("EventStore.Core")] -[assembly: AssemblyDescription("")] -[assembly: Guid("d8443030-57f6-4a88-be61-b505fa257863")] \ No newline at end of file diff --git a/src/proj/EventStore.Logging.Log4Net/EventStore.Logging.Log4Net.csproj b/src/proj/EventStore.Logging.Log4Net/EventStore.Logging.Log4Net.csproj deleted file mode 100644 index 81ba2cbfd..000000000 --- a/src/proj/EventStore.Logging.Log4Net/EventStore.Logging.Log4Net.csproj +++ /dev/null @@ -1,72 +0,0 @@ - - - - Debug - AnyCPU - 8.0.30703 - 2.0 - {EBCACF9E-7FE2-4C39-917E-2DD60EEE8C80} - Library - Properties - EventStore.Logging.Log4Net - EventStore.Logging.Log4Net - v4.0 - 512 - true - ..\..\EventStore.snk - ..\..\..\src\ - true - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - Properties\GlobalAssemblyInfo.cs - - - Properties\GlobalSuppressions.cs - - - Properties\VersionAssemblyInfo.cs - - - - - - - Properties\CustomDictionary.xml - - - - - ..\..\..\output\bin\EventStore.dll - - - {03946843-F343-419C-88EF-3E446D08DFA6} - EventStore - - - ..\..\packages\log4net.2.0.0\lib\net40-full\log4net.dll - - - - - - - - \ No newline at end of file diff --git a/src/proj/EventStore.Logging.Log4Net/Log4NetLogger.cs b/src/proj/EventStore.Logging.Log4Net/Log4NetLogger.cs deleted file mode 100644 index 803c6c0aa..000000000 --- a/src/proj/EventStore.Logging.Log4Net/Log4NetLogger.cs +++ /dev/null @@ -1,45 +0,0 @@ -namespace EventStore.Logging.Log4Net -{ - using System; - - public class Log4NetLogger : ILog - { - private readonly log4net.ILog log; - - public Log4NetLogger(Type typeToLog) - { - this.log = log4net.LogManager.GetLogger(typeToLog); - } - - public virtual void Verbose(string message, params object[] values) - { - if (this.log.IsDebugEnabled) - this.log.DebugFormat(message, values); - } - public virtual void Debug(string message, params object[] values) - { - if (this.log.IsDebugEnabled) - this.log.DebugFormat(message, values); - } - public virtual void Info(string message, params object[] values) - { - if (this.log.IsInfoEnabled) - this.log.InfoFormat(message, values); - } - public virtual void Warn(string message, params object[] values) - { - if (this.log.IsWarnEnabled) - this.log.WarnFormat(message, values); - } - public virtual void Error(string message, params object[] values) - { - if (this.log.IsErrorEnabled) - this.log.ErrorFormat(message, values); - } - public virtual void Fatal(string message, params object[] values) - { - if (this.log.IsFatalEnabled) - this.log.FatalFormat(message, values); - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Logging.Log4Net/Properties/AssemblyInfo.cs b/src/proj/EventStore.Logging.Log4Net/Properties/AssemblyInfo.cs deleted file mode 100644 index 7ba13c2b8..000000000 --- a/src/proj/EventStore.Logging.Log4Net/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,6 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -[assembly: AssemblyTitle("EventStore.Logging.Log4Net")] -[assembly: AssemblyDescription("")] -[assembly: Guid("68f81b23-8823-43f7-90bd-f68884a1bedb")] \ No newline at end of file diff --git a/src/proj/EventStore.Logging.Log4Net/packages.config b/src/proj/EventStore.Logging.Log4Net/packages.config deleted file mode 100644 index f791739a3..000000000 --- a/src/proj/EventStore.Logging.Log4Net/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/src/proj/EventStore.Logging.NLog/EventStore.Logging.NLog.csproj b/src/proj/EventStore.Logging.NLog/EventStore.Logging.NLog.csproj deleted file mode 100644 index bd1ab42d1..000000000 --- a/src/proj/EventStore.Logging.NLog/EventStore.Logging.NLog.csproj +++ /dev/null @@ -1,72 +0,0 @@ - - - - Debug - AnyCPU - 8.0.30703 - 2.0 - {4A336666-825A-49BA-B6F7-2156E3877A34} - Library - Properties - EventStore.Logging.NLog - EventStore.Logging.NLog - v4.0 - 512 - true - ..\..\EventStore.snk - ..\..\..\src\ - true - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - Properties\GlobalAssemblyInfo.cs - - - Properties\GlobalSuppressions.cs - - - Properties\VersionAssemblyInfo.cs - - - - - - - Properties\CustomDictionary.xml - - - - - ..\..\..\output\bin\EventStore.dll - - - {03946843-F343-419C-88EF-3E446D08DFA6} - EventStore - - - ..\..\packages\NLog.2.0.0.2000\lib\net40\NLog.dll - - - - - - - - \ No newline at end of file diff --git a/src/proj/EventStore.Logging.NLog/NLogLogger.cs b/src/proj/EventStore.Logging.NLog/NLogLogger.cs deleted file mode 100644 index 12026e2be..000000000 --- a/src/proj/EventStore.Logging.NLog/NLogLogger.cs +++ /dev/null @@ -1,52 +0,0 @@ -namespace EventStore.Logging.NLog -{ - using System; - using global::NLog; - using global::NLog.Config; - - public class NLogLogger : ILog - { - private static readonly LogFactory Factory = new LogFactory(); - private readonly Logger log; - - public NLogLogger(Type typeToLog) - { - this.log = Factory.GetLogger(typeToLog.FullName); - } - public NLogLogger(Type typeToLog, LoggingConfiguration configuration) - { - this.log = new LogFactory(configuration).GetLogger(typeToLog.FullName); - } - - public virtual void Verbose(string message, params object[] values) - { - if (this.log.IsTraceEnabled) - this.log.Trace(message, values); - } - public virtual void Debug(string message, params object[] values) - { - if (this.log.IsDebugEnabled) - this.log.Debug(message, values); - } - public virtual void Info(string message, params object[] values) - { - if (this.log.IsInfoEnabled) - this.log.Info(message, values); - } - public virtual void Warn(string message, params object[] values) - { - if (this.log.IsWarnEnabled) - this.log.Warn(message, values); - } - public virtual void Error(string message, params object[] values) - { - if (this.log.IsErrorEnabled) - this.log.Error(message, values); - } - public virtual void Fatal(string message, params object[] values) - { - if (this.log.IsFatalEnabled) - this.log.Fatal(message, values); - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Logging.NLog/Properties/AssemblyInfo.cs b/src/proj/EventStore.Logging.NLog/Properties/AssemblyInfo.cs deleted file mode 100644 index 06b82b574..000000000 --- a/src/proj/EventStore.Logging.NLog/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,6 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -[assembly: AssemblyTitle("EventStore.Logging.NLog")] -[assembly: AssemblyDescription("")] -[assembly: Guid("4e33b458-8aeb-4ec6-8f74-2ead32e11a6d")] \ No newline at end of file diff --git a/src/proj/EventStore.Logging.NLog/packages.config b/src/proj/EventStore.Logging.NLog/packages.config deleted file mode 100644 index dbdcb8ef9..000000000 --- a/src/proj/EventStore.Logging.NLog/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/src/proj/EventStore.Persistence.MongoPersistence.Wireup/EventStore.Persistence.MongoPersistence.Wireup.csproj b/src/proj/EventStore.Persistence.MongoPersistence.Wireup/EventStore.Persistence.MongoPersistence.Wireup.csproj deleted file mode 100644 index e4052c0f9..000000000 --- a/src/proj/EventStore.Persistence.MongoPersistence.Wireup/EventStore.Persistence.MongoPersistence.Wireup.csproj +++ /dev/null @@ -1,76 +0,0 @@ - - - - Debug - AnyCPU - 8.0.30703 - 2.0 - {E95780CB-3114-4925-A38A-1FA4CC4EC213} - Library - Properties - EventStore - EventStore.Persistence.MongoPersistence.Wireup - v4.0 - 512 - true - ..\..\EventStore.snk - ..\..\..\src\ - true - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - Properties\GlobalAssemblyInfo.cs - - - Properties\GlobalSuppressions.cs - - - Properties\VersionAssemblyInfo.cs - - - - - - - - Properties\CustomDictionary.xml - - - - - {32ADD8CE-0F3F-41D8-BFA1-6E5D685E64DD} - EventStore.Persistence.MongoPersistence - - - ..\..\..\output\bin\EventStore.dll - - - {03946843-F343-419C-88EF-3E446D08DFA6} - EventStore - - - {421664DB-C18D-4499-ABC1-C9086D525F80} - EventStore.Wireup - - - - - - \ No newline at end of file diff --git a/src/proj/EventStore.Persistence.MongoPersistence.Wireup/MongoPersistenceWireup.cs b/src/proj/EventStore.Persistence.MongoPersistence.Wireup/MongoPersistenceWireup.cs deleted file mode 100644 index ee47b39c9..000000000 --- a/src/proj/EventStore.Persistence.MongoPersistence.Wireup/MongoPersistenceWireup.cs +++ /dev/null @@ -1,26 +0,0 @@ -namespace EventStore -{ - using System.Transactions; - using Logging; - using Persistence.MongoPersistence; - using Serialization; - - public class MongoPersistenceWireup : PersistenceWireup - { - private static readonly ILog Logger = LogFactory.BuildLogger(typeof(MongoPersistenceWireup)); - - public MongoPersistenceWireup(Wireup inner, string connectionName, IDocumentSerializer serializer) - : base(inner) - { - Logger.Debug("Configuring Mongo persistence engine."); - - var options = this.Container.Resolve(); - if (options != TransactionScopeOption.Suppress) - Logger.Warn("MongoDB does not participate in transactions using TransactionScope."); - - this.Container.Register(c => new MongoPersistenceFactory( - connectionName, - serializer).Build()); - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Persistence.MongoPersistence.Wireup/MongoPersistenceWireupExtensions.cs b/src/proj/EventStore.Persistence.MongoPersistence.Wireup/MongoPersistenceWireupExtensions.cs deleted file mode 100644 index 576c59b3f..000000000 --- a/src/proj/EventStore.Persistence.MongoPersistence.Wireup/MongoPersistenceWireupExtensions.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace EventStore -{ - using Serialization; - - public static class MongoPersistenceWireupExtensions - { - public static PersistenceWireup UsingMongoPersistence( - this Wireup wireup, string connectionName, IDocumentSerializer serializer) - { - return new MongoPersistenceWireup(wireup, connectionName, serializer); - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Persistence.MongoPersistence.Wireup/Properties/AssemblyInfo.cs b/src/proj/EventStore.Persistence.MongoPersistence.Wireup/Properties/AssemblyInfo.cs deleted file mode 100644 index 28a47d0f3..000000000 --- a/src/proj/EventStore.Persistence.MongoPersistence.Wireup/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,6 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -[assembly: AssemblyTitle("EventStore.Persistence.MongoPersistence.Wireup")] -[assembly: AssemblyDescription("")] -[assembly: Guid("d0f595b8-6109-4bad-bc0c-ca261a3980f3")] \ No newline at end of file diff --git a/src/proj/EventStore.Persistence.MongoPersistence/EventStore.Persistence.MongoPersistence.csproj b/src/proj/EventStore.Persistence.MongoPersistence/EventStore.Persistence.MongoPersistence.csproj deleted file mode 100644 index 3ab8cab42..000000000 --- a/src/proj/EventStore.Persistence.MongoPersistence/EventStore.Persistence.MongoPersistence.csproj +++ /dev/null @@ -1,94 +0,0 @@ - - - - Debug - AnyCPU - 8.0.30703 - 2.0 - {32ADD8CE-0F3F-41D8-BFA1-6E5D685E64DD} - Library - Properties - EventStore.Persistence.MongoPersistence - EventStore.Persistence.MongoPersistence - v4.0 - 512 - true - ..\..\EventStore.snk - ..\..\..\src\ - true - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - ..\..\..\output\bin\EventStore.dll - - - {03946843-F343-419C-88EF-3E446D08DFA6} - EventStore - - - False - ..\..\packages\mongocsharpdriver.1.5\lib\net35\MongoDB.Bson.dll - - - False - ..\..\packages\mongocsharpdriver.1.5\lib\net35\MongoDB.Driver.dll - - - - - - - - Properties\GlobalAssemblyInfo.cs - - - Properties\GlobalSuppressions.cs - - - Properties\VersionAssemblyInfo.cs - - - - True - True - Messages.resx - - - - - - - - Properties\CustomDictionary.xml - - - - - ResXFileCodeGenerator - Messages.Designer.cs - Designer - - - - - - - - \ No newline at end of file diff --git a/src/proj/EventStore.Persistence.MongoPersistence/ExtensionMethods.cs b/src/proj/EventStore.Persistence.MongoPersistence/ExtensionMethods.cs deleted file mode 100644 index ab17314b6..000000000 --- a/src/proj/EventStore.Persistence.MongoPersistence/ExtensionMethods.cs +++ /dev/null @@ -1,108 +0,0 @@ -namespace EventStore.Persistence.MongoPersistence -{ - using System; - using System.Collections.Generic; - using System.Linq; - using MongoDB.Bson; - using MongoDB.Bson.Serialization; - using MongoDB.Driver; - using MongoDB.Driver.Builders; - using Serialization; - - public static class ExtensionMethods - { - public static BsonDocument ToMongoCommit(this Commit commit, IDocumentSerializer serializer) - { - var streamRevision = commit.StreamRevision - (commit.Events.Count - 1); - var events = commit.Events.Select(e => new BsonDocument { { "StreamRevision", streamRevision++ }, { "Payload", new BsonDocumentWrapper(typeof(EventMessage), serializer.Serialize(e)) } }); - return new BsonDocument - { - { "_id", new BsonDocument { { "StreamId", commit.StreamId }, { "CommitSequence", commit.CommitSequence } } }, - { "CommitId", commit.CommitId }, - { "CommitStamp", commit.CommitStamp }, - { "Headers", BsonDocumentWrapper.Create(commit.Headers) }, - { "Events", BsonArray.Create(events) }, - { "Dispatched", false } - }; - } - public static Commit ToCommit(this BsonDocument doc, IDocumentSerializer serializer) - { - if (doc == null) - return null; - - var id = doc["_id"].AsBsonDocument; - var streamId = id["StreamId"].AsGuid; - var commitSequence = id["CommitSequence"].AsInt32; - - var events = doc["Events"].AsBsonArray.Select(e => e.AsBsonDocument["Payload"].IsBsonDocument ? BsonSerializer.Deserialize(e.AsBsonDocument["Payload"].AsBsonDocument) : serializer.Deserialize(e.AsBsonDocument["Payload"].AsByteArray)).ToList(); - var streamRevision = doc["Events"].AsBsonArray.Last().AsBsonDocument["StreamRevision"].AsInt32; - return new Commit( - streamId, - streamRevision, - doc["CommitId"].AsGuid, - commitSequence, - doc["CommitStamp"].AsDateTime, - BsonSerializer.Deserialize>(doc["Headers"].AsBsonDocument), - events); - } - - public static BsonDocument ToMongoSnapshot(this Snapshot snapshot, IDocumentSerializer serializer) - { - return new BsonDocument - { - { "_id", new BsonDocument { { "StreamId", snapshot.StreamId }, { "StreamRevision", snapshot.StreamRevision } } }, - { "Payload", BsonDocumentWrapper.Create(serializer.Serialize(snapshot.Payload)) } - }; - } - - public static Snapshot ToSnapshot(this BsonDocument doc, IDocumentSerializer serializer) - { - if (doc == null) - return null; - - var id = doc["_id"].AsBsonDocument; - var streamId = id["StreamId"].AsGuid; - var streamRevision = id["StreamRevision"].AsInt32; - var bsonPayload = doc["Payload"]; - - object payload; - switch (bsonPayload.BsonType) - { - case BsonType.Binary: - payload = serializer.Deserialize(bsonPayload.AsByteArray); - break; - case BsonType.Document: - payload = BsonSerializer.Deserialize(bsonPayload.AsBsonDocument); - break; - default: - payload = bsonPayload.RawValue; - break; - } - - return new Snapshot( - streamId, - streamRevision, - payload); - } - - public static StreamHead ToStreamHead(this BsonDocument doc) - { - return new StreamHead( - doc["_id"].AsGuid, - doc["HeadRevision"].AsInt32, - doc["SnapshotRevision"].AsInt32); - } - - public static IMongoQuery ToMongoCommitIdQuery(this Commit commit) - { - return Query.EQ("_id", Query.And(Query.EQ("StreamId", commit.StreamId), Query.EQ("CommitSequence", commit.CommitSequence)).ToBsonDocument()); - } - - public static IMongoQuery ToSnapshotQuery(this Guid streamId, int maxRevision) - { - return Query.And( - Query.GT("_id", Query.And(Query.EQ("StreamId", streamId), Query.EQ("StreamRevision", BsonNull.Value)).ToBsonDocument()), - Query.LTE("_id", Query.And(Query.EQ("StreamId", streamId), Query.EQ("StreamRevision", maxRevision)).ToBsonDocument())); - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Persistence.MongoPersistence/Messages.Designer.cs b/src/proj/EventStore.Persistence.MongoPersistence/Messages.Designer.cs deleted file mode 100644 index 0645d4ecd..000000000 --- a/src/proj/EventStore.Persistence.MongoPersistence/Messages.Designer.cs +++ /dev/null @@ -1,216 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.237 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace EventStore.Persistence.MongoPersistence { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Messages { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Messages() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("EventStore.Persistence.MongoPersistence.Messages", typeof(Messages).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to Adding snapshot to stream '{0}' at position {1}.. - /// - internal static string AddingSnapshot { - get { - return ResourceManager.GetString("AddingSnapshot", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Attempting to commit {0} events on stream '{1}' at sequence {2}.. - /// - internal static string AttemptingToCommit { - get { - return ResourceManager.GetString("AttemptingToCommit", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Commit '{0}' persisted.. - /// - internal static string CommitPersisted { - get { - return ResourceManager.GetString("CommitPersisted", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Concurrent write detected.. - /// - internal static string ConcurrentWriteDetected { - get { - return ResourceManager.GetString("ConcurrentWriteDetected", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Concurrency issue; determining whether attempt was duplicate.. - /// - internal static string DetectingConcurrency { - get { - return ResourceManager.GetString("DetectingConcurrency", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Getting all commits for stream '{0}' between revisions '{1}' and '{2}'.. - /// - internal static string GettingAllCommitsBetween { - get { - return ResourceManager.GetString("GettingAllCommitsBetween", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Getting all commits from '{0}' forward.. - /// - internal static string GettingAllCommitsFrom { - get { - return ResourceManager.GetString("GettingAllCommitsFrom", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Getting all commits from '{0}' forward.. - /// - internal static string GettingAllCommitsFromTo { - get { - return ResourceManager.GetString("GettingAllCommitsFromTo", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Getting snapshot for stream '{0}' on or before revision {1}.. - /// - internal static string GettingRevision { - get { - return ResourceManager.GetString("GettingRevision", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Getting a list of streams to snapshot.. - /// - internal static string GettingStreamsToSnapshot { - get { - return ResourceManager.GetString("GettingStreamsToSnapshot", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Getting the list of all undispatched commits.. - /// - internal static string GettingUndispatchedCommits { - get { - return ResourceManager.GetString("GettingUndispatchedCommits", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Initializing storage engine.. - /// - internal static string InitializingStorage { - get { - return ResourceManager.GetString("InitializingStorage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Marking commit '{0}' as dispatched.. - /// - internal static string MarkingCommitAsDispatched { - get { - return ResourceManager.GetString("MarkingCommitAsDispatched", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Purging all stored data.. - /// - internal static string PurgingStorage { - get { - return ResourceManager.GetString("PurgingStorage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Shutting down persistence.. - /// - internal static string ShuttingDownPersistence { - get { - return ResourceManager.GetString("ShuttingDownPersistence", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Storage threw exception of type '{0}'.. - /// - internal static string StorageThrewException { - get { - return ResourceManager.GetString("StorageThrewException", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Storage is unavailabe.. - /// - internal static string StorageUnavailable { - get { - return ResourceManager.GetString("StorageUnavailable", resourceCulture); - } - } - } -} diff --git a/src/proj/EventStore.Persistence.MongoPersistence/Messages.resx b/src/proj/EventStore.Persistence.MongoPersistence/Messages.resx deleted file mode 100644 index a182f1b53..000000000 --- a/src/proj/EventStore.Persistence.MongoPersistence/Messages.resx +++ /dev/null @@ -1,168 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - Adding snapshot to stream '{0}' at position {1}. - - - Attempting to commit {0} events on stream '{1}' at sequence {2}. - - - Commit '{0}' persisted. - - - Concurrent write detected. - - - Concurrency issue; determining whether attempt was duplicate. - - - Getting all commits for stream '{0}' between revisions '{1}' and '{2}'. - - - Getting all commits from '{0}' forward. - - - Getting snapshot for stream '{0}' on or before revision {1}. - - - Getting a list of streams to snapshot. - - - Getting the list of all undispatched commits. - - - Initializing storage engine. - - - Marking commit '{0}' as dispatched. - - - Purging all stored data. - - - Shutting down persistence. - - - Storage is unavailabe. - - - - - \ No newline at end of file diff --git a/src/proj/EventStore.Persistence.MongoPersistence/MongoPersistenceEngine.cs b/src/proj/EventStore.Persistence.MongoPersistence/MongoPersistenceEngine.cs deleted file mode 100644 index 1326a4d0c..000000000 --- a/src/proj/EventStore.Persistence.MongoPersistence/MongoPersistenceEngine.cs +++ /dev/null @@ -1,303 +0,0 @@ -namespace EventStore.Persistence.MongoPersistence -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Threading; - using Logging; - using MongoDB.Bson; - using MongoDB.Driver; - using MongoDB.Driver.Builders; - using Serialization; - - public class MongoPersistenceEngine : IPersistStreams - { - private const string ConcurrencyException = "E1100"; - private static readonly ILog Logger = LogFactory.BuildLogger(typeof(MongoPersistenceEngine)); - private readonly MongoCollectionSettings commitSettings; - private readonly MongoCollectionSettings snapshotSettings; - private readonly MongoCollectionSettings streamSettings; - private readonly MongoDatabase store; - private readonly IDocumentSerializer serializer; - private bool disposed; - private int initialized; - - public MongoPersistenceEngine(MongoDatabase store, IDocumentSerializer serializer) - { - if (store == null) - throw new ArgumentNullException("store"); - - if (serializer == null) - throw new ArgumentNullException("serializer"); - - this.store = store; - this.serializer = serializer; - - this.commitSettings = this.store.CreateCollectionSettings("Commits"); - this.commitSettings.AssignIdOnInsert = false; - this.commitSettings.SafeMode = SafeMode.True; - - this.snapshotSettings = this.store.CreateCollectionSettings("Snapshots"); - this.snapshotSettings.AssignIdOnInsert = false; - this.snapshotSettings.SafeMode = SafeMode.False; - - this.streamSettings = this.store.CreateCollectionSettings("Streams"); - this.streamSettings.AssignIdOnInsert = false; - this.streamSettings.SafeMode = SafeMode.False; - } - - public void Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); - } - protected virtual void Dispose(bool disposing) - { - if (!disposing || this.disposed) - return; - - Logger.Debug(Messages.ShuttingDownPersistence); - this.disposed = true; - } - - public virtual void Initialize() - { - if (Interlocked.Increment(ref this.initialized) > 1) - return; - - Logger.Debug(Messages.InitializingStorage); - - this.TryMongo(() => - { - this.PersistedCommits.EnsureIndex( - IndexKeys.Ascending("Dispatched").Ascending("CommitStamp"), - IndexOptions.SetName("Dispatched_Index").SetUnique(false)); - - this.PersistedCommits.EnsureIndex( - IndexKeys.Ascending("_id.StreamId", "Events.StreamRevision"), - IndexOptions.SetName("GetFrom_Index").SetUnique(true)); - - this.PersistedCommits.EnsureIndex( - IndexKeys.Ascending("CommitStamp"), - IndexOptions.SetName("CommitStamp_Index").SetUnique(false)); - - this.PersistedStreamHeads.EnsureIndex( - IndexKeys.Ascending("Unsnapshotted"), - IndexOptions.SetName("Unsnapshotted_Index").SetUnique(false)); - }); - } - - public virtual IEnumerable GetFrom(Guid streamId, int minRevision, int maxRevision) - { - Logger.Debug(Messages.GettingAllCommitsBetween, streamId, minRevision, maxRevision); - - return this.TryMongo(() => - { - var query = Query.And( - Query.EQ("_id.StreamId", streamId), - Query.GTE("Events.StreamRevision", minRevision), - Query.LTE("Events.StreamRevision", maxRevision)); - - return this.PersistedCommits - .Find(query) - .SetSortOrder("Events.StreamRevision") - .Select(mc => mc.ToCommit(this.serializer)); - }); - } - public virtual IEnumerable GetFrom(DateTime start) - { - Logger.Debug(Messages.GettingAllCommitsFrom, start); - - return this.TryMongo(() => this.PersistedCommits - .Find(Query.GTE("CommitStamp", start)) - .SetSortOrder("CommitStamp") - .Select(x => x.ToCommit(this.serializer))); - } - - public virtual IEnumerable GetFromTo(DateTime start, DateTime end) - { - Logger.Debug(Messages.GettingAllCommitsFromTo, start, end); - - return this.TryMongo(() => this.PersistedCommits - .Find(Query.And(Query.GTE("CommitStamp", start), Query.LT("CommitStamp", end))) - .SetSortOrder("CommitStamp") - .Select(x => x.ToCommit(this.serializer))); - } - - public virtual void Commit(Commit attempt) - { - Logger.Debug(Messages.AttemptingToCommit, - attempt.Events.Count, attempt.StreamId, attempt.CommitSequence); - - this.TryMongo(() => - { - var commit = attempt.ToMongoCommit(this.serializer); - - try - { - // for concurrency / duplicate commit detection safe mode is required - this.PersistedCommits.Insert(commit, SafeMode.True); - this.UpdateStreamHeadAsync(attempt.StreamId, attempt.StreamRevision, attempt.Events.Count); - Logger.Debug(Messages.CommitPersisted, attempt.CommitId); - } - catch (MongoException e) - { - if (!e.Message.Contains(ConcurrencyException)) - throw; - - var savedCommit = this.PersistedCommits.FindOne(attempt.ToMongoCommitIdQuery()).ToCommit(this.serializer); - if (savedCommit.CommitId == attempt.CommitId) - throw new DuplicateCommitException(); - - Logger.Debug(Messages.ConcurrentWriteDetected); - throw new ConcurrencyException(); - } - }); - } - - public virtual IEnumerable GetUndispatchedCommits() - { - Logger.Debug(Messages.GettingUndispatchedCommits); - - return this.TryMongo(() => this.PersistedCommits - .Find(Query.EQ("Dispatched", false)) - .SetSortOrder("CommitStamp") - .Select(mc => mc.ToCommit(this.serializer))); - } - public virtual void MarkCommitAsDispatched(Commit commit) - { - Logger.Debug(Messages.MarkingCommitAsDispatched, commit.CommitId); - - this.TryMongo(() => - { - var query = commit.ToMongoCommitIdQuery(); - var update = Update.Set("Dispatched", true); - this.PersistedCommits.Update(query, update); - }); - } - - public virtual IEnumerable GetStreamsToSnapshot(int maxThreshold) - { - Logger.Debug(Messages.GettingStreamsToSnapshot); - - return this.TryMongo(() => - { - var query = Query.GTE("Unsnapshotted", maxThreshold); - - return this.PersistedStreamHeads - .Find(query) - .SetSortOrder(SortBy.Descending("Unsnapshotted")) - .Select(x => x.ToStreamHead()); - }); - } - public virtual Snapshot GetSnapshot(Guid streamId, int maxRevision) - { - Logger.Debug(Messages.GettingRevision, streamId, maxRevision); - - return this.TryMongo(() => this.PersistedSnapshots - .Find(streamId.ToSnapshotQuery(maxRevision)) - .SetSortOrder(SortBy.Descending("_id")) - .SetLimit(1) - .Select(mc => mc.ToSnapshot(this.serializer)) - .FirstOrDefault()); - } - public virtual bool AddSnapshot(Snapshot snapshot) - { - if (snapshot == null) - return false; - - Logger.Debug(Messages.AddingSnapshot, snapshot.StreamId, snapshot.StreamRevision); - - try - { - var mongoSnapshot = snapshot.ToMongoSnapshot(this.serializer); - var query = Query.EQ("_id", mongoSnapshot["_id"]); - var update = Update.Set("Payload", mongoSnapshot["Payload"]); - - // Doing an upsert instead of an insert allows us to overwrite an existing snapshot and not get stuck with a - // stream that needs to be snapshotted because the insert fails and the SnapshotRevision isn't being updated. - this.PersistedSnapshots.Update(query, update, UpdateFlags.Upsert); - - // More commits could have been made between us deciding that a snapshot is required and writing it so just - // resetting the Unsnapshotted count may be a little off. Adding snapshots should be a separate process so - // this is a good chance to make sure the numbers are still in-sync - it only adds a 'read' after all ... - var streamHead = this.PersistedStreamHeads.FindOneById(snapshot.StreamId).ToStreamHead(); - var unsnapshotted = streamHead.HeadRevision - snapshot.StreamRevision; - this.PersistedStreamHeads.Update( - Query.EQ("_id", snapshot.StreamId), - Update.Set("SnapshotRevision", snapshot.StreamRevision).Set("Unsnapshotted", unsnapshotted)); - - return true; - } - catch (Exception) - { - return false; - } - } - - public virtual void Purge() - { - Logger.Warn(Messages.PurgingStorage); - - this.PersistedCommits.Drop(); - this.PersistedStreamHeads.Drop(); - this.PersistedSnapshots.Drop(); - } - - private void UpdateStreamHeadAsync(Guid streamId, int streamRevision, int eventsCount) - { - ThreadPool.QueueUserWorkItem(x => this.TryMongo(() => - { - this.PersistedStreamHeads.Update( - Query.EQ("_id", streamId), - Update.Set("HeadRevision", streamRevision).Inc("SnapshotRevision", 0).Inc("Unsnapshotted", eventsCount), - UpdateFlags.Upsert); - }), null); - } - - protected virtual MongoCollection PersistedCommits - { - get { return this.store.GetCollection(this.commitSettings); } - } - protected virtual MongoCollection PersistedStreamHeads - { - get { return this.store.GetCollection(this.streamSettings); } - } - protected virtual MongoCollection PersistedSnapshots - { - get { return this.store.GetCollection(this.snapshotSettings); } - } - - protected virtual T TryMongo(Func callback) - { - var results = default(T); - - this.TryMongo(() => - { - results = callback(); - }); - - return results; - } - protected virtual void TryMongo(Action callback) - { - if (this.disposed) - throw new ObjectDisposedException("Attempt to use storage after it has been disposed."); - - try - { - callback(); - } - catch (MongoConnectionException e) - { - Logger.Warn(Messages.StorageUnavailable); - throw new StorageUnavailableException(e.Message, e); - } - catch (MongoException e) - { - Logger.Error(Messages.StorageThrewException, e.GetType()); - throw new StorageException(e.Message, e); - } - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Persistence.MongoPersistence/MongoPersistenceFactory.cs b/src/proj/EventStore.Persistence.MongoPersistence/MongoPersistenceFactory.cs deleted file mode 100644 index ec0d56dcb..000000000 --- a/src/proj/EventStore.Persistence.MongoPersistence/MongoPersistenceFactory.cs +++ /dev/null @@ -1,35 +0,0 @@ -namespace EventStore.Persistence.MongoPersistence -{ - using System.Configuration; - using MongoDB.Driver; - using Serialization; - - public class MongoPersistenceFactory : IPersistenceFactory - { - private readonly string connectionName; - private readonly IDocumentSerializer serializer; - - public MongoPersistenceFactory(string connectionName, IDocumentSerializer serializer) - { - this.connectionName = connectionName; - this.serializer = serializer; - } - - public virtual IPersistStreams Build() - { - var connectionString = this.TransformConnectionString(this.GetConnectionString()); - var database = MongoDatabase.Create(connectionString); - return new MongoPersistenceEngine(database, this.serializer); - } - - protected virtual string GetConnectionString() - { - return ConfigurationManager.ConnectionStrings[this.connectionName].ConnectionString; - } - - protected virtual string TransformConnectionString(string connectionString) - { - return connectionString; - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Persistence.MongoPersistence/Properties/AssemblyInfo.cs b/src/proj/EventStore.Persistence.MongoPersistence/Properties/AssemblyInfo.cs deleted file mode 100644 index 56b087dcd..000000000 --- a/src/proj/EventStore.Persistence.MongoPersistence/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,6 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -[assembly: AssemblyTitle("EventStore.Persistence.MongoPersistence")] -[assembly: AssemblyDescription("")] -[assembly: Guid("a531c143-8f3a-4123-b8ed-44323eac543c")] \ No newline at end of file diff --git a/src/proj/EventStore.Persistence.MongoPersistence/packages.config b/src/proj/EventStore.Persistence.MongoPersistence/packages.config deleted file mode 100644 index c5c12a236..000000000 --- a/src/proj/EventStore.Persistence.MongoPersistence/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/src/proj/EventStore.Persistence.RavenPersistence.Wireup/EventStore.Persistence.RavenPersistence.Wireup.csproj b/src/proj/EventStore.Persistence.RavenPersistence.Wireup/EventStore.Persistence.RavenPersistence.Wireup.csproj deleted file mode 100644 index 0f7971413..000000000 --- a/src/proj/EventStore.Persistence.RavenPersistence.Wireup/EventStore.Persistence.RavenPersistence.Wireup.csproj +++ /dev/null @@ -1,81 +0,0 @@ - - - - Debug - AnyCPU - 8.0.30703 - 2.0 - {A99B2B16-B6BE-4B83-ACE0-56A734DB9AEF} - Library - Properties - EventStore - EventStore.Persistence.RavenPersistence.Wireup - v4.0 - 512 - true - ..\..\EventStore.snk - ..\..\..\src\ - true - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - Properties\GlobalAssemblyInfo.cs - - - Properties\GlobalSuppressions.cs - - - Properties\VersionAssemblyInfo.cs - - - - - - - - Properties\CustomDictionary.xml - - - - - {F9E7FD69-0818-48CA-9249-5387739E1B6A} - EventStore.Persistence.RavenPersistence - - - ..\..\..\output\bin\EventStore.dll - - - {A5BF4B86-26F6-418D-BE35-C6CC3A623D27} - EventStore.Serialization - - - {03946843-F343-419C-88EF-3E446D08DFA6} - EventStore - - - {421664DB-C18D-4499-ABC1-C9086D525F80} - EventStore.Wireup - - - - - - - \ No newline at end of file diff --git a/src/proj/EventStore.Persistence.RavenPersistence.Wireup/Properties/AssemblyInfo.cs b/src/proj/EventStore.Persistence.RavenPersistence.Wireup/Properties/AssemblyInfo.cs deleted file mode 100644 index 6a2936e66..000000000 --- a/src/proj/EventStore.Persistence.RavenPersistence.Wireup/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,6 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -[assembly: AssemblyTitle("EventStore.Persistence.RavenPersistence.Wireup")] -[assembly: AssemblyDescription("")] -[assembly: Guid("d4285207-9888-409a-800d-736fbe51e36d")] \ No newline at end of file diff --git a/src/proj/EventStore.Persistence.RavenPersistence.Wireup/RavenPersistenceWireup.cs b/src/proj/EventStore.Persistence.RavenPersistence.Wireup/RavenPersistenceWireup.cs deleted file mode 100644 index f0b0f6048..000000000 --- a/src/proj/EventStore.Persistence.RavenPersistence.Wireup/RavenPersistenceWireup.cs +++ /dev/null @@ -1,144 +0,0 @@ -namespace EventStore -{ - using System; - using System.Transactions; - using Logging; - using Persistence.RavenPersistence; - using Serialization; - - public class RavenPersistenceWireup : PersistenceWireup - { - private static readonly ILog Logger = LogFactory.BuildLogger(typeof(RavenPersistenceWireup)); - - // these values are considered "safe by default" according to Raven docs - private int pageSize = 128; - private int maxServerPageSize = 1024; - private bool consistentQueries; // stale queries perform better - private IDocumentSerializer serializer = new DocumentObjectSerializer(); - private Uri url; - private string defaultDatabase; - private string partition = null; - private string connectionName; - private string connectionString; - - public RavenPersistenceWireup(Wireup inner) - : this(inner, string.Empty) - { - } - - public RavenPersistenceWireup(Wireup inner, string connectionName) - : base(inner) - { - Logger.Debug("Configuring Raven persistence engine."); - - this.connectionName = connectionName; - - this.Container.Register(c => new RavenConfiguration - { - Serializer = this.ResolveSerializer(c), - ScopeOption = c.Resolve(), - ConsistentQueries = this.consistentQueries, - MaxServerPageSize = this.maxServerPageSize, - RequestedPageSize = this.pageSize, - ConnectionName = this.connectionName, - ConnectionString = this.connectionString, - DefaultDatabase = this.defaultDatabase, - Url = this.url, - Partition = this.partition - }); - - this.Container.Register(c => new RavenPersistenceFactory(c.Resolve()).Build()); - } - - public virtual RavenPersistenceWireup ConnectionStringName(string connectionStringName) - { - Logger.Debug("Using connection string named '{0}'.", connectionStringName); - - this.connectionName = connectionStringName; - return this; - } - - public virtual RavenPersistenceWireup ConnectionString(string connectionStringValue) - { - Logger.Debug("Using connection string value '{0}'.", connectionStringValue); - - this.connectionString = connectionStringValue; - return this; - } - - public virtual RavenPersistenceWireup DefaultDatabase(string database) - { - Logger.Debug("Using database named '{0}'.", database); - - this.defaultDatabase = database; - return this; - } - - public virtual RavenPersistenceWireup Url(string address) - { - Logger.Debug("Using database at '{0}'.", address); - - this.url = new Uri(address, UriKind.Absolute); - return this; - } - - public virtual RavenPersistenceWireup Partition(string name) - { - this.partition = name; - - return this; - } - - public virtual RavenPersistenceWireup PageEvery(int records) - { - Logger.Debug("Page result set every {0} records.", records); - - this.pageSize = records; - return this; - } - - public virtual RavenPersistenceWireup MaxServerPageSizeConfiguration(int records) - { - Logger.Debug("The maximum allowed page size as configured on the Raven server is {0} records.", records); - - this.maxServerPageSize = records; - return this; - } - - public virtual RavenPersistenceWireup ConsistentQueries(bool fullyConsistent) - { - Logger.Debug("Queries to Raven will return results which are fully consistent: {0}", fullyConsistent); - - this.consistentQueries = fullyConsistent; - return this; - } - - public virtual RavenPersistenceWireup ConsistentQueries() - { - return this.ConsistentQueries(true); - } - - public virtual RavenPersistenceWireup StaleQueries() - { - return this.ConsistentQueries(false); - } - - public virtual RavenPersistenceWireup WithSerializer(IDocumentSerializer instance) - { - Logger.Debug("Registering serializer of type '{0}'.", instance.GetType()); - - this.serializer = instance; - return this; - } - - private IDocumentSerializer ResolveSerializer(NanoContainer container) - { - var registered = container.Resolve(); - if (registered == null) - return this.serializer; - - Logger.Debug("Wrapping registered serializer of type '{0}' inside of a ByteStreamDocumentSerializer", registered.GetType()); - return new ByteStreamDocumentSerializer(registered); - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Persistence.RavenPersistence.Wireup/RavenPersistenceWireupExtensions.cs b/src/proj/EventStore.Persistence.RavenPersistence.Wireup/RavenPersistenceWireupExtensions.cs deleted file mode 100644 index 24e3bddfb..000000000 --- a/src/proj/EventStore.Persistence.RavenPersistence.Wireup/RavenPersistenceWireupExtensions.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace EventStore -{ - public static class RavenPersistenceWireupExtensions - { - public static RavenPersistenceWireup UsingRavenPersistence( - this Wireup wireup, string connectionName) - { - return new RavenPersistenceWireup(wireup, connectionName); - } - - public static RavenPersistenceWireup UsingRavenPersistence( - this Wireup wireup) - { - return new RavenPersistenceWireup(wireup); - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Persistence.RavenPersistence/EventStore.Persistence.RavenPersistence.csproj b/src/proj/EventStore.Persistence.RavenPersistence/EventStore.Persistence.RavenPersistence.csproj deleted file mode 100644 index 91f8431fe..000000000 --- a/src/proj/EventStore.Persistence.RavenPersistence/EventStore.Persistence.RavenPersistence.csproj +++ /dev/null @@ -1,112 +0,0 @@ - - - - Debug - AnyCPU - 8.0.30703 - 2.0 - {F9E7FD69-0818-48CA-9249-5387739E1B6A} - Library - Properties - EventStore.Persistence.RavenPersistence - EventStore.Persistence.RavenPersistence - v4.0 - 512 - true - ..\..\EventStore.snk - ..\..\..\src\ - true - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - Properties\GlobalAssemblyInfo.cs - - - Properties\GlobalSuppressions.cs - - - Properties\VersionAssemblyInfo.cs - - - - - - - - - - True - True - Messages.resx - - - - - - - - - - - - - ..\..\..\output\bin\EventStore.dll - - - {03946843-F343-419C-88EF-3E446D08DFA6} - EventStore - - - False - ..\..\packages\Newtonsoft.Json.4.5.7\lib\net40\Newtonsoft.Json.dll - - - ..\..\packages\NLog.2.0.0.2000\lib\net40\NLog.dll - - - False - ..\..\packages\RavenDB.Client.1.0.972\lib\net40\Raven.Abstractions.dll - - - ..\..\packages\RavenDB.Client.1.0.972\lib\net40\Raven.Client.Lightweight.dll - True - - - - - - - - Properties\CustomDictionary.xml - - - - - ResXFileCodeGenerator - Messages.Designer.cs - Designer - - - - - - - - \ No newline at end of file diff --git a/src/proj/EventStore.Persistence.RavenPersistence/ExtensionMethods.cs b/src/proj/EventStore.Persistence.RavenPersistence/ExtensionMethods.cs deleted file mode 100644 index 455604de0..000000000 --- a/src/proj/EventStore.Persistence.RavenPersistence/ExtensionMethods.cs +++ /dev/null @@ -1,126 +0,0 @@ -namespace EventStore.Persistence.RavenPersistence -{ - using System; - using System.Collections.Generic; - using System.Globalization; - using System.Linq; - using System.Transactions; - using Serialization; - - public static class ExtensionMethods - { - public static string ToRavenCommitId(this Commit commit, string partition) - { - var id = string.Format(CultureInfo.InvariantCulture, "{0}/{1}", commit.StreamId, commit.CommitSequence); - - if (!string.IsNullOrEmpty(partition)) - id = string.Format("{0}/{1}", partition, id); - - return id; - } - - public static RavenCommit ToRavenCommit(this Commit commit, string partition, IDocumentSerializer serializer) - { - return new RavenCommit - { - Id = ToRavenCommitId(commit, partition), - Partition = partition, - StreamId = commit.StreamId, - CommitSequence = commit.CommitSequence, - StartingStreamRevision = commit.StreamRevision - (commit.Events.Count - 1), - StreamRevision = commit.StreamRevision, - CommitId = commit.CommitId, - CommitStamp = commit.CommitStamp, - Headers = commit.Headers, - Payload = serializer.Serialize(commit.Events) - }; - } - - public static Commit ToCommit(this RavenCommit commit, IDocumentSerializer serializer) - { - return new Commit( - commit.StreamId, - commit.StreamRevision, - commit.CommitId, - commit.CommitSequence, - commit.CommitStamp, - commit.Headers, - serializer.Deserialize>(commit.Payload)); - } - - public static string ToRavenSnapshotId(Snapshot snapshot, string partition) - { - return string.Format("Snapshots/{0}/{1}", snapshot.StreamId, snapshot.StreamRevision); - } - - public static RavenSnapshot ToRavenSnapshot(this Snapshot snapshot, string partition, IDocumentSerializer serializer) - { - return new RavenSnapshot - { - Id = ToRavenSnapshotId(snapshot, partition), - Partition = partition, - StreamId = snapshot.StreamId, - StreamRevision = snapshot.StreamRevision, - Payload = serializer.Serialize(snapshot.Payload) - }; - } - - public static Snapshot ToSnapshot(this RavenSnapshot snapshot, IDocumentSerializer serializer) - { - if (snapshot == null) - return null; - - return new Snapshot( - snapshot.StreamId, - snapshot.StreamRevision, - serializer.Deserialize(snapshot.Payload)); - } - - public static string ToRavenStreamId(this Guid streamId, string partition) - { - var id = string.Format("StreamHeads/{0}", streamId); - - if (!string.IsNullOrEmpty(partition)) - id = string.Format("{0}/{1}", partition, id); - - return id; - } - - public static RavenStreamHead ToRavenStreamHead(this Commit commit, string partition) - { - return new RavenStreamHead - { - Id = commit.StreamId.ToRavenStreamId(partition), - Partition = partition, - StreamId = commit.StreamId, - HeadRevision = commit.StreamRevision, - SnapshotRevision = 0 - }; - } - - public static RavenStreamHead ToRavenStreamHead(this Snapshot snapshot, string partition) - { - return new RavenStreamHead - { - Id = snapshot.StreamId.ToRavenStreamId(partition), - Partition = partition, - StreamId = snapshot.StreamId, - HeadRevision = snapshot.StreamRevision, - SnapshotRevision = snapshot.StreamRevision - }; - } - - public static StreamHead ToStreamHead(this RavenStreamHead streamHead) - { - return new StreamHead( - streamHead.StreamId, - streamHead.HeadRevision, - streamHead.SnapshotRevision); - } - - public static IEnumerable Page(this IQueryable query, int pageSize, TransactionScope scope) - { - return new PagedEnumerationCollection(query, pageSize, scope); - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Persistence.RavenPersistence/Indexes/EventStoreDocumentsByEntityName.cs b/src/proj/EventStore.Persistence.RavenPersistence/Indexes/EventStoreDocumentsByEntityName.cs deleted file mode 100644 index fed2d447f..000000000 --- a/src/proj/EventStore.Persistence.RavenPersistence/Indexes/EventStoreDocumentsByEntityName.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; -using System.Linq; -using Raven.Abstractions.Indexing; -using Raven.Client.Indexes; - -namespace EventStore.Persistence.RavenPersistence.Indexes -{ - public class EventStoreDocumentsByEntityName : AbstractIndexCreationTask - { - public override string IndexName - { - get { return "EventStoreDocumentsByEntityName"; } - } - - public override IndexDefinition CreateIndexDefinition() - { - return new IndexDefinition - { - //Redundant ?? null needed for compatibility with older models. Please do not remove. - Map = @"from doc in docs - let Tag = doc[""@metadata""][""Raven-Entity-Name""] - where Tag != null - select new { Tag, LastModified = (DateTime)doc[""@metadata""][""Last-Modified""], Partition = doc.Partition ?? null };", - Indexes = - { - {"Tag", FieldIndexing.NotAnalyzed}, - {"Partition", FieldIndexing.NotAnalyzed}, - }, - Stores = - { - {"Tag", FieldStorage.No}, - {"LastModified", FieldStorage.No}, - {"Partition", FieldStorage.No} - } - }; - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Persistence.RavenPersistence/Indexes/RavenCommitByDate.cs b/src/proj/EventStore.Persistence.RavenPersistence/Indexes/RavenCommitByDate.cs deleted file mode 100644 index 6ff7f2f54..000000000 --- a/src/proj/EventStore.Persistence.RavenPersistence/Indexes/RavenCommitByDate.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace EventStore.Persistence.RavenPersistence.Indexes -{ - using System.Linq; - using Raven.Client.Indexes; - - public class RavenCommitByDate : AbstractIndexCreationTask - { - public RavenCommitByDate() - { - //Redundant ?? null needed for compatibility with older models. Please do not remove. - this.Map = commits => from c in commits select new { c.CommitStamp, Partition = c.Partition ?? null }; - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Persistence.RavenPersistence/Indexes/RavenCommitByRevisionRange.cs b/src/proj/EventStore.Persistence.RavenPersistence/Indexes/RavenCommitByRevisionRange.cs deleted file mode 100644 index 9f3419655..000000000 --- a/src/proj/EventStore.Persistence.RavenPersistence/Indexes/RavenCommitByRevisionRange.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace EventStore.Persistence.RavenPersistence.Indexes -{ - using System.Linq; - using Raven.Client.Indexes; - - public class RavenCommitByRevisionRange : AbstractIndexCreationTask - { - public RavenCommitByRevisionRange() - { - //Redundant ?? null needed for compatibility with older models. Please do not remove. - this.Map = commits => from c in commits - select new { c.StreamId, c.StartingStreamRevision, c.StreamRevision, Partition = c.Partition ?? null }; - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Persistence.RavenPersistence/Indexes/RavenCommitsByDispatched.cs b/src/proj/EventStore.Persistence.RavenPersistence/Indexes/RavenCommitsByDispatched.cs deleted file mode 100644 index 2f40bc45e..000000000 --- a/src/proj/EventStore.Persistence.RavenPersistence/Indexes/RavenCommitsByDispatched.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace EventStore.Persistence.RavenPersistence.Indexes -{ - using System.Linq; - using Raven.Client.Indexes; - - public class RavenCommitsByDispatched : AbstractIndexCreationTask - { - public RavenCommitsByDispatched() - { - //Redundant ?? null needed for compatibility with older models. Please do not remove. - this.Map = commits => from c in commits select new { c.Dispatched, Partition = c.Partition ?? null }; - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Persistence.RavenPersistence/Indexes/RavenSnapshotByStreamIdAndRevision.cs b/src/proj/EventStore.Persistence.RavenPersistence/Indexes/RavenSnapshotByStreamIdAndRevision.cs deleted file mode 100644 index 4be01e876..000000000 --- a/src/proj/EventStore.Persistence.RavenPersistence/Indexes/RavenSnapshotByStreamIdAndRevision.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace EventStore.Persistence.RavenPersistence.Indexes -{ - using System.Linq; - using Raven.Client.Indexes; - - public class RavenSnapshotByStreamIdAndRevision : AbstractIndexCreationTask - { - public RavenSnapshotByStreamIdAndRevision() - { - //Redundant ?? null needed for compatibility with older models. Please do not remove. - Map = snapshots => from s in snapshots select new { s.StreamId, s.StreamRevision, Partition = s.Partition ?? null }; - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Persistence.RavenPersistence/Indexes/RavenStreamHeadBySnapshotAge.cs b/src/proj/EventStore.Persistence.RavenPersistence/Indexes/RavenStreamHeadBySnapshotAge.cs deleted file mode 100644 index 8f98a943a..000000000 --- a/src/proj/EventStore.Persistence.RavenPersistence/Indexes/RavenStreamHeadBySnapshotAge.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace EventStore.Persistence.RavenPersistence.Indexes -{ - using System.Linq; - using Raven.Client.Indexes; - - public class RavenStreamHeadBySnapshotAge : AbstractIndexCreationTask - { - public RavenStreamHeadBySnapshotAge() - { - //Redundant ?? null needed for compatibility with older models. Please do not remove. - Map = snapshots => from s in snapshots select new { SnapshotAge = s.HeadRevision - s.SnapshotRevision, Partition = s.Partition ?? null }; - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Persistence.RavenPersistence/Messages.Designer.cs b/src/proj/EventStore.Persistence.RavenPersistence/Messages.Designer.cs deleted file mode 100644 index 9011e18f4..000000000 --- a/src/proj/EventStore.Persistence.RavenPersistence/Messages.Designer.cs +++ /dev/null @@ -1,270 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.237 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace EventStore.Persistence.RavenPersistence { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Messages { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Messages() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("EventStore.Persistence.RavenPersistence.Messages", typeof(Messages).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to Adding snapshot to stream '{0}' at position {1}.. - /// - internal static string AddingSnapshot { - get { - return ResourceManager.GetString("AddingSnapshot", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Attempting to commit {0} events on stream '{1}' at sequence {2}.. - /// - internal static string AttemptingToCommit { - get { - return ResourceManager.GetString("AttemptingToCommit", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Commit '{0}' persisted.. - /// - internal static string CommitPersisted { - get { - return ResourceManager.GetString("CommitPersisted", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Concurrent write detected.. - /// - internal static string ConcurrentWriteDetected { - get { - return ResourceManager.GetString("ConcurrentWriteDetected", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Concurrency issue; determining whether attempt was duplicate.. - /// - internal static string DetectingConcurrency { - get { - return ResourceManager.GetString("DetectingConcurrency", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Duplicate commit detected.. - /// - internal static string DuplicateCommitDetected { - get { - return ResourceManager.GetString("DuplicateCommitDetected", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Enumerated {0} rows, re-querying for next page.. - /// - internal static string EnumeratedRowCount { - get { - return ResourceManager.GetString("EnumeratedRowCount", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Getting all commits for stream '{0}' between revisions '{1}' and '{2}'.. - /// - internal static string GettingAllCommitsBetween { - get { - return ResourceManager.GetString("GettingAllCommitsBetween", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Getting all commits from '{0}' forward.. - /// - internal static string GettingAllCommitsFrom { - get { - return ResourceManager.GetString("GettingAllCommitsFrom", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Getting all commits from '{0}' forward.. - /// - internal static string GettingAllCommitsFromTo { - get { - return ResourceManager.GetString("GettingAllCommitsFromTo", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Getting snapshot for stream '{0}' on or before revision {1}.. - /// - internal static string GettingRevision { - get { - return ResourceManager.GetString("GettingRevision", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Getting a list of streams to snapshot.. - /// - internal static string GettingStreamsToSnapshot { - get { - return ResourceManager.GetString("GettingStreamsToSnapshot", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Getting the list of all undispatched commits.. - /// - internal static string GettingUndispatchedCommits { - get { - return ResourceManager.GetString("GettingUndispatchedCommits", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Initializing storage engine.. - /// - internal static string InitializingStorage { - get { - return ResourceManager.GetString("InitializingStorage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Marking commit '{0}' as dispatched.. - /// - internal static string MarkingCommitAsDispatched { - get { - return ResourceManager.GetString("MarkingCommitAsDispatched", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Configured paging size is too small.. - /// - internal static string PagingSizeTooSmall { - get { - return ResourceManager.GetString("PagingSizeTooSmall", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Purging all stored data.. - /// - internal static string PurgingStorage { - get { - return ResourceManager.GetString("PurgingStorage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Scope is complete.. - /// - internal static string ScopeCompleted { - get { - return ResourceManager.GetString("ScopeCompleted", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Serializer cannot be null.. - /// - internal static string SerializerCannotBeNull { - get { - return ResourceManager.GetString("SerializerCannotBeNull", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Shutting down persistence.. - /// - internal static string ShuttingDownPersistence { - get { - return ResourceManager.GetString("ShuttingDownPersistence", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Storage has already been disposed.. - /// - internal static string StorageAlreadyDisposed { - get { - return ResourceManager.GetString("StorageAlreadyDisposed", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Storage threw exception of type '{0}'.. - /// - internal static string StorageThrewException { - get { - return ResourceManager.GetString("StorageThrewException", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Storage is unavailabe.. - /// - internal static string StorageUnavailable { - get { - return ResourceManager.GetString("StorageUnavailable", resourceCulture); - } - } - } -} diff --git a/src/proj/EventStore.Persistence.RavenPersistence/Messages.resx b/src/proj/EventStore.Persistence.RavenPersistence/Messages.resx deleted file mode 100644 index d5994f4dd..000000000 --- a/src/proj/EventStore.Persistence.RavenPersistence/Messages.resx +++ /dev/null @@ -1,186 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - Attempting to commit {0} events on stream '{1}' at sequence {2}. - - - Getting all commits for stream '{0}' between revisions '{1}' and '{2}'. - - - Getting all commits from '{0}' forward. - - - Initializing storage engine. - - - Shutting down persistence. - - - Commit '{0}' persisted. - - - Concurrent write detected. - - - Concurrency issue; determining whether attempt was duplicate. - - - Adding snapshot to stream '{0}' at position {1}. - - - Getting snapshot for stream '{0}' on or before revision {1}. - - - Getting a list of streams to snapshot. - - - Getting the list of all undispatched commits. - - - Marking commit '{0}' as dispatched. - - - Purging all stored data. - - - Serializer cannot be null. - - - Configured paging size is too small. - - - Storage threw exception of type '{0}'. - - - Storage is unavailabe. - - - Duplicate commit detected. - - - Storage has already been disposed. - - - Enumerated {0} rows, re-querying for next page. - - - Scope is complete. - - \ No newline at end of file diff --git a/src/proj/EventStore.Persistence.RavenPersistence/PagedEnumerationCollection.cs b/src/proj/EventStore.Persistence.RavenPersistence/PagedEnumerationCollection.cs deleted file mode 100644 index 81c2b8228..000000000 --- a/src/proj/EventStore.Persistence.RavenPersistence/PagedEnumerationCollection.cs +++ /dev/null @@ -1,108 +0,0 @@ -namespace EventStore.Persistence.RavenPersistence -{ - using System; - using System.Collections; - using System.Collections.Generic; - using System.Linq; - using System.Transactions; - using Logging; - - public sealed class PagedEnumerationCollection : IEnumerable - { - private readonly IQueryable source; - private readonly int take; - private readonly TransactionScope scope; - - public PagedEnumerationCollection(IQueryable source, int take, TransactionScope scope) - { - this.source = source; - this.scope = scope; - this.take = take; - } - public IEnumerator GetEnumerator() - { - return new PagedEnumerator(this.source, this.take, this.scope); - } - IEnumerator IEnumerable.GetEnumerator() - { - return this.GetEnumerator(); - } - - private sealed class PagedEnumerator : IEnumerator - { - private static readonly ILog Logger = LogFactory.BuildLogger(typeof(PagedEnumerator)); - private readonly IQueryable source; - private readonly int take; - private readonly TransactionScope scope; - private int skip; - private IEnumerator current; - - public PagedEnumerator(IQueryable source, int take, TransactionScope scope) - { - this.source = source; - this.scope = scope; - this.take = take; - } - public void Dispose() - { - this.Reset(); - - if (this.scope != null) - { - Logger.Verbose(Messages.ScopeCompleted); - this.scope.Complete(); - this.scope.Dispose(); - } - - GC.SuppressFinalize(this); - } - - public void Reset() - { - if (this.current != null) - this.current.Dispose(); - - this.current = null; - this.skip = 0; - } - public bool MoveNext() - { - this.current = this.current ?? this.source.Skip(this.skip).Take(this.take).GetEnumerator(); - - if (this.current.MoveNext()) - return this.IncrementPosition(); - - if (!this.PageCompletelyEnumerated()) - return false; // ISSUE: if our page size doesn't agree with Raven, this won't evaluate properly... - - Logger.Verbose(Messages.EnumeratedRowCount, this.skip); - - this.current.Dispose(); - this.current = this.source.Skip(this.skip).Take(this.take).GetEnumerator(); - - if (this.current.MoveNext()) - return this.IncrementPosition(); - - return false; - } - private bool IncrementPosition() - { - this.skip++; - return true; - } - private bool PageCompletelyEnumerated() - { - return this.skip > 0 && 0 == this.skip % this.take; - } - - public T Current - { - get { return this.current == null ? default(T) : this.current.Current; } - } - object IEnumerator.Current - { - get { return this.Current; } - } - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Persistence.RavenPersistence/Properties/AssemblyInfo.cs b/src/proj/EventStore.Persistence.RavenPersistence/Properties/AssemblyInfo.cs deleted file mode 100644 index 76822887d..000000000 --- a/src/proj/EventStore.Persistence.RavenPersistence/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,6 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -[assembly: AssemblyTitle("EventStore.Persistence.RavenPersistence")] -[assembly: AssemblyDescription("")] -[assembly: Guid("cb0f9492-685f-4748-b6c8-cbd2a4387b8d")] \ No newline at end of file diff --git a/src/proj/EventStore.Persistence.RavenPersistence/RavenCommit.cs b/src/proj/EventStore.Persistence.RavenPersistence/RavenCommit.cs deleted file mode 100644 index 40760f284..000000000 --- a/src/proj/EventStore.Persistence.RavenPersistence/RavenCommit.cs +++ /dev/null @@ -1,32 +0,0 @@ -namespace EventStore.Persistence.RavenPersistence -{ - using System; - using System.Collections.Generic; - using System.Diagnostics.CodeAnalysis; - using Newtonsoft.Json; - - public class RavenCommit - { - public string Id { get; set; } - - public Guid StreamId { get; set; } - public int CommitSequence { get; set; } - - public int StartingStreamRevision { get; set; } - public int StreamRevision { get; set; } - - public Guid CommitId { get; set; } - public DateTime CommitStamp { get; set; } - - public string Partition { get; set; } - - [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", - Justification = "This is a simple DTO and is only used internally by Raven.")] - public Dictionary Headers { get; set; } - - [JsonProperty(TypeNameHandling = TypeNameHandling.All)] - public object Payload { get; set; } - - public bool Dispatched { get; set; } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Persistence.RavenPersistence/RavenConfiguration.cs b/src/proj/EventStore.Persistence.RavenPersistence/RavenConfiguration.cs deleted file mode 100644 index 2ed0575f3..000000000 --- a/src/proj/EventStore.Persistence.RavenPersistence/RavenConfiguration.cs +++ /dev/null @@ -1,31 +0,0 @@ -namespace EventStore.Persistence.RavenPersistence -{ - using System; - using System.Transactions; - using Serialization; - - public class RavenConfiguration - { - public string ConnectionName { get; set; } - public string ConnectionString { get; set; } - public Uri Url { get; set; } - public string DefaultDatabase { get; set; } - public string Partition { get; set; } - - public IDocumentSerializer Serializer { get; set; } - public TransactionScopeOption ScopeOption { get; set; } - public bool ConsistentQueries { get; set; } - public int RequestedPageSize { get; set; } - public int MaxServerPageSize { get; set; } - public int PageSize - { - get - { - if (this.RequestedPageSize > this.MaxServerPageSize) - return this.MaxServerPageSize; - - return this.RequestedPageSize; - } - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Persistence.RavenPersistence/RavenPersistenceEngine.cs b/src/proj/EventStore.Persistence.RavenPersistence/RavenPersistenceEngine.cs deleted file mode 100644 index 939dbb843..000000000 --- a/src/proj/EventStore.Persistence.RavenPersistence/RavenPersistenceEngine.cs +++ /dev/null @@ -1,410 +0,0 @@ -namespace EventStore.Persistence.RavenPersistence -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Linq.Expressions; - using System.Net; - using System.Threading; - using System.Transactions; - using Indexes; - using Logging; - using Raven.Abstractions.Commands; - using Raven.Abstractions.Data; - using Raven.Client; - using Raven.Client.Connection; - using Raven.Client.Exceptions; - using Raven.Client.Indexes; - using Raven.Json.Linq; - using Serialization; - - public class RavenPersistenceEngine : IPersistStreams - { - private const int MinPageSize = 10; - private static readonly ILog Logger = LogFactory.BuildLogger(typeof(RavenPersistenceEngine)); - private readonly IDocumentStore store; - private readonly IDocumentSerializer serializer; - private readonly TransactionScopeOption scopeOption; - private readonly bool consistentQueries; - private readonly int pageSize; - private int initialized; - private readonly string partition; - - public IDocumentStore Store - { - get { return store; } - } - - public RavenPersistenceEngine(IDocumentStore store, RavenConfiguration config) - { - if (store == null) - throw new ArgumentNullException("store"); - - if (config == null) - throw new ArgumentNullException("config"); - - if (config.Serializer == null) - throw new ArgumentException(Messages.SerializerCannotBeNull, "config"); - - if (config.PageSize < MinPageSize) - throw new ArgumentException(Messages.PagingSizeTooSmall, "config"); - - this.store = store; - this.serializer = config.Serializer; - this.scopeOption = config.ScopeOption; - this.consistentQueries = config.ConsistentQueries; - this.pageSize = config.PageSize; - this.partition = config.Partition; - } - - public void Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); - } - - protected virtual void Dispose(bool disposing) - { - if (!disposing) - return; - - Logger.Debug(Messages.ShuttingDownPersistence); - this.store.Dispose(); - } - - public virtual void Initialize() - { - if (Interlocked.Increment(ref this.initialized) > 1) - return; - - Logger.Debug(Messages.InitializingStorage); - - this.TryRaven(() => - { - using (var scope = this.OpenCommandScope()) - { - new RavenCommitByDate().Execute(this.store); - new RavenCommitByRevisionRange().Execute(this.store); - new RavenCommitsByDispatched().Execute(this.store); - new RavenSnapshotByStreamIdAndRevision().Execute(this.store); - new RavenStreamHeadBySnapshotAge().Execute(this.store); - new EventStoreDocumentsByEntityName().Execute(this.store); - scope.Complete(); - } - - return true; - }); - } - - public virtual IEnumerable GetFrom(Guid streamId, int minRevision, int maxRevision) - { - Logger.Debug(Messages.GettingAllCommitsBetween, streamId, minRevision, maxRevision); - - return this.QueryCommits(x => - x.StreamId == streamId && x.StreamRevision >= minRevision && x.StartingStreamRevision <= maxRevision) - .OrderBy(x => x.CommitSequence); - } - - public virtual IEnumerable GetFrom(DateTime start) - { - Logger.Debug(Messages.GettingAllCommitsFrom, start); - - return this.QueryCommits(x => x.CommitStamp >= start) - .OrderBy(x => x.CommitStamp); - } - - public virtual IEnumerable GetFromTo(DateTime start, DateTime end) - { - Logger.Debug(Messages.GettingAllCommitsFromTo, start, end); - - return this.QueryCommits(x => x.CommitStamp >= start && x.CommitStamp < end) - .OrderBy(x => x.CommitStamp); - } - - public virtual void Commit(Commit attempt) - { - Logger.Debug(Messages.AttemptingToCommit, - attempt.Events.Count, attempt.StreamId, attempt.CommitSequence); - - try - { - this.TryRaven(() => - { - using (var scope = this.OpenCommandScope()) - using (var session = this.store.OpenSession()) - { - session.Advanced.UseOptimisticConcurrency = true; - session.Store(attempt.ToRavenCommit(this.partition, this.serializer)); - session.SaveChanges(); - scope.Complete(); - } - - Logger.Debug(Messages.CommitPersisted, attempt.CommitId); - this.SaveStreamHead(attempt.ToRavenStreamHead(this.partition)); - return true; - }); - } - catch (Raven.Abstractions.Exceptions.ConcurrencyException) - { - var savedCommit = this.LoadSavedCommit(attempt); - if (savedCommit.CommitId == attempt.CommitId) - throw new DuplicateCommitException(); - - Logger.Debug(Messages.ConcurrentWriteDetected); - throw new ConcurrencyException(); - } - } - - private RavenCommit LoadSavedCommit(Commit attempt) - { - Logger.Debug(Messages.DetectingConcurrency); - - return this.TryRaven(() => - { - using (var scope = this.OpenQueryScope()) - using (var session = this.store.OpenSession()) - { - var commit = session.Load(attempt.ToRavenCommitId(this.partition)); - scope.Complete(); - return commit; - } - }); - } - - public virtual IEnumerable GetUndispatchedCommits() - { - Logger.Debug(Messages.GettingUndispatchedCommits); - return this.QueryCommits(c => c.Dispatched == false) - .OrderBy(x => x.CommitStamp); - } - - public virtual void MarkCommitAsDispatched(Commit commit) - { - if (commit == null) - throw new ArgumentNullException("commit"); - - var patch = new PatchRequest - { - Type = PatchCommandType.Set, - Name = "Dispatched", - Value = RavenJToken.Parse("true") - }; - var data = new PatchCommandData - { - Key = commit.ToRavenCommitId(this.partition), - Patches = new[] { patch } - }; - - Logger.Debug(Messages.MarkingCommitAsDispatched, commit.CommitId); - - this.TryRaven(() => - { - using (var scope = this.OpenCommandScope()) - using (var session = this.store.OpenSession()) - { - session.Advanced.DatabaseCommands.Batch(new[] { data }); - session.SaveChanges(); - scope.Complete(); - return true; - } - }); - } - - public virtual IEnumerable GetStreamsToSnapshot(int maxThreshold) - { - Logger.Debug(Messages.GettingStreamsToSnapshot); - - return this.Query(s => s.SnapshotAge >= maxThreshold && s.Partition == this.partition) - .Select(s => s.ToStreamHead()); - } - - public virtual Snapshot GetSnapshot(Guid streamId, int maxRevision) - { - Logger.Debug(Messages.GettingRevision, streamId, maxRevision); - - return Query(x => x.StreamId == streamId && x.StreamRevision <= maxRevision && x.Partition == this.partition) - .OrderByDescending(x => x.StreamRevision) - .FirstOrDefault() - .ToSnapshot(this.serializer); - } - - public virtual bool AddSnapshot(Snapshot snapshot) - { - if (snapshot == null) - return false; - - Logger.Debug(Messages.AddingSnapshot, snapshot.StreamId, snapshot.StreamRevision); - - try - { - return this.TryRaven(() => - { - using (var scope = this.OpenCommandScope()) - using (var session = this.store.OpenSession()) - { - var ravenSnapshot = snapshot.ToRavenSnapshot(this.partition, this.serializer); - session.Store(ravenSnapshot); - session.SaveChanges(); - scope.Complete(); - } - - this.SaveStreamHead(snapshot.ToRavenStreamHead(this.partition)); - - return true; - }); - } - catch (Raven.Abstractions.Exceptions.ConcurrencyException) - { - return false; - } - } - - public virtual void Purge() - { - Logger.Warn(Messages.PurgingStorage); - - this.TryRaven(() => - { - using (var scope = this.OpenCommandScope()) - using (var session = this.store.OpenSession()) - { - PurgeDocuments(session); - - session.SaveChanges(); - scope.Complete(); - return true; - } - }); - } - - private void PurgeDocuments(IDocumentSession session) - { - Func getTagCondition = t => "Tag:" + session.Advanced.DocumentStore.Conventions.GetTypeTagName(t); - - var typeQuery = "(" + getTagCondition(typeof(RavenCommit)) + " OR " + getTagCondition(typeof(RavenSnapshot)) + " OR " + getTagCondition(typeof(RavenStreamHead)) + ")"; - var partitionQuery = "Partition:" + (this.partition ?? "[[NULL_VALUE]]"); - var queryText = partitionQuery + " AND " + typeQuery; - - var query = new IndexQuery { Query = queryText }; - - session.Advanced.DatabaseCommands - .DeleteByIndex("EventStoreDocumentsByEntityName", query, true); - } - - private IEnumerable QueryCommits(Expression> query) - where TIndex : AbstractIndexCreationTask, new() - { - var commits = Query(query, c => c.Partition == this.partition); - - return commits.Select(x => x.ToCommit(this.serializer)); - } - - private IEnumerable Query(params Expression>[] conditions) - where TIndex : AbstractIndexCreationTask, new() - { - return this.TryRaven(() => - { - var scope = this.OpenQueryScope(); - - try - { - using (var session = this.OpenQuerySession()) - { - IQueryable query = session.Query() - .Customize(x => { if (this.consistentQueries) x.WaitForNonStaleResults(); }); - - query = conditions.Aggregate(query, (current, condition) => current.Where(condition)); - - return query.Page(this.pageSize, scope); - } - } - catch (Exception) - { - scope.Dispose(); - throw; - } - }); - } - private IDocumentSession OpenQuerySession() - { - var session = this.store.OpenSession(); - - // defaults to 30 total requests per session (not good for paging over large data sets) - // which may be encountered when calling GetFrom() and enumerating over the entire store. - // see http://ravendb.net/documentation/safe-by-default for more information. - session.Advanced.MaxNumberOfRequestsPerSession = int.MaxValue; - - return session; - } - - private void SaveStreamHead(RavenStreamHead streamHead) - { - if (this.consistentQueries) - this.SaveStreamHeadAsync(streamHead); - else - ThreadPool.QueueUserWorkItem(x => this.SaveStreamHeadAsync(streamHead), null); - } - private void SaveStreamHeadAsync(RavenStreamHead updated) - { - this.TryRaven(() => - { - using (var scope = this.OpenCommandScope()) - using (var session = this.store.OpenSession()) - { - var current = session.Load(updated.StreamId.ToRavenStreamId(this.partition)) ?? updated; - current.HeadRevision = updated.HeadRevision; - - if (updated.SnapshotRevision > 0) - current.SnapshotRevision = updated.SnapshotRevision; - - session.Advanced.UseOptimisticConcurrency = false; - session.Store(current); - session.SaveChanges(); - scope.Complete(); // if this fails it's no big deal, stream heads can be updated whenever - } - return true; - }); - } - - protected virtual T TryRaven(Func callback) - { - try - { - return callback(); - } - catch (WebException e) - { - Logger.Warn(Messages.StorageUnavailable); - throw new StorageUnavailableException(e.Message, e); - } - catch (NonUniqueObjectException e) - { - Logger.Warn(Messages.DuplicateCommitDetected); - throw new DuplicateCommitException(e.Message, e); - } - catch (Raven.Abstractions.Exceptions.ConcurrencyException) - { - Logger.Warn(Messages.ConcurrentWriteDetected); - throw; - } - catch (ObjectDisposedException) - { - Logger.Warn(Messages.StorageAlreadyDisposed); - throw; - } - catch (Exception e) - { - Logger.Error(Messages.StorageThrewException, e.GetType()); - throw new StorageException(e.Message, e); - } - } - protected virtual TransactionScope OpenQueryScope() - { - return this.OpenCommandScope() ?? new TransactionScope(TransactionScopeOption.Suppress); - } - protected virtual TransactionScope OpenCommandScope() - { - return new TransactionScope(this.scopeOption); - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Persistence.RavenPersistence/RavenPersistenceFactory.cs b/src/proj/EventStore.Persistence.RavenPersistence/RavenPersistenceFactory.cs deleted file mode 100644 index 198b12665..000000000 --- a/src/proj/EventStore.Persistence.RavenPersistence/RavenPersistenceFactory.cs +++ /dev/null @@ -1,41 +0,0 @@ -namespace EventStore.Persistence.RavenPersistence -{ - using Raven.Client; - using Raven.Client.Document; - - public class RavenPersistenceFactory : IPersistenceFactory - { - private readonly RavenConfiguration config; - - public RavenPersistenceFactory(RavenConfiguration config) - { - this.config = config; - } - - public virtual IPersistStreams Build() - { - var store = this.GetStore(); - return new RavenPersistenceEngine(store, this.config); - } - protected virtual IDocumentStore GetStore() - { - var store = new DocumentStore(); - - if (!string.IsNullOrEmpty(this.config.ConnectionName)) - store.ConnectionStringName = this.config.ConnectionName; - - if (!string.IsNullOrEmpty(this.config.ConnectionString)) - store.ParseConnectionString(config.ConnectionString); - - if (this.config.Url != null) - store.Url = this.config.Url.ToString(); - - if (!string.IsNullOrEmpty(this.config.DefaultDatabase)) - store.DefaultDatabase = this.config.DefaultDatabase; - - store.Initialize(); - - return store; - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Persistence.RavenPersistence/RavenSnapshot.cs b/src/proj/EventStore.Persistence.RavenPersistence/RavenSnapshot.cs deleted file mode 100644 index 9bc997582..000000000 --- a/src/proj/EventStore.Persistence.RavenPersistence/RavenSnapshot.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace EventStore.Persistence.RavenPersistence -{ - using System; - - public class RavenSnapshot - { - public string Id { get; set; } - public string Partition { get; set; } - public Guid StreamId { get; set; } - public int StreamRevision { get; set; } - public object Payload { get; set; } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Persistence.RavenPersistence/RavenStreamHead.cs b/src/proj/EventStore.Persistence.RavenPersistence/RavenStreamHead.cs deleted file mode 100644 index 9e89731e2..000000000 --- a/src/proj/EventStore.Persistence.RavenPersistence/RavenStreamHead.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace EventStore.Persistence.RavenPersistence -{ - using System; - - public class RavenStreamHead - { - public string Id { get; set; } - public string Partition { get; set; } - public Guid StreamId { get; set; } - public int HeadRevision { get; set; } - public int SnapshotRevision { get; set; } - public int SnapshotAge - { - get { return this.HeadRevision - this.SnapshotRevision; } // set by map/reduce on the server - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Persistence.RavenPersistence/packages.config b/src/proj/EventStore.Persistence.RavenPersistence/packages.config deleted file mode 100644 index e30d0620e..000000000 --- a/src/proj/EventStore.Persistence.RavenPersistence/packages.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/src/proj/EventStore.Persistence.SqlPersistence/CommitExtensions.cs b/src/proj/EventStore.Persistence.SqlPersistence/CommitExtensions.cs deleted file mode 100644 index 8ee3ae5ba..000000000 --- a/src/proj/EventStore.Persistence.SqlPersistence/CommitExtensions.cs +++ /dev/null @@ -1,58 +0,0 @@ -namespace EventStore.Persistence.SqlPersistence -{ - using System; - using System.Collections.Generic; - using System.Data; - using Logging; - using Serialization; - - public static class CommitExtensions - { - private const int StreamIdIndex = 0; - private const int StreamRevisionIndex = 1; - private const int CommitIdIndex = 2; - private const int CommitSequenceIndex = 3; - private const int CommitStampIndex = 4; - private const int HeadersIndex = 5; - private const int PayloadIndex = 6; - private static readonly ILog Logger = LogFactory.BuildLogger(typeof(CommitExtensions)); - - public static Commit GetCommit(this IDataRecord record, ISerialize serializer) - { - Logger.Verbose(Messages.DeserializingCommit, serializer.GetType()); - var headers = serializer.Deserialize>(record, HeadersIndex); - var events = serializer.Deserialize>(record, PayloadIndex); - - return new Commit( - record[StreamIdIndex].ToGuid(), - record[StreamRevisionIndex].ToInt(), - record[CommitIdIndex].ToGuid(), - record[CommitSequenceIndex].ToInt(), - record[CommitStampIndex].ToDateTime(), - headers, - events); - } - - public static Guid StreamId(this IDataRecord record) - { - return record[StreamIdIndex].ToGuid(); - } - public static int CommitSequence(this IDataRecord record) - { - return record[CommitSequenceIndex].ToInt(); - } - - public static T Deserialize(this ISerialize serializer, IDataRecord record, int index) - { - if (index >= record.FieldCount) - return default(T); - - var value = record[index]; - if (value == null || value == DBNull.Value) - return default(T); - - var bytes = (byte[])value; - return bytes.Length == 0 ? default(T) : serializer.Deserialize(bytes); - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Persistence.SqlPersistence/ConfigurationConnectionFactory.cs b/src/proj/EventStore.Persistence.SqlPersistence/ConfigurationConnectionFactory.cs deleted file mode 100644 index 942e39b1c..000000000 --- a/src/proj/EventStore.Persistence.SqlPersistence/ConfigurationConnectionFactory.cs +++ /dev/null @@ -1,166 +0,0 @@ -namespace EventStore.Persistence.SqlPersistence -{ - using System; - using System.Collections.Generic; - using System.Configuration; - using System.Data; - using System.Data.Common; - using System.Linq; - using Logging; - - public class ConfigurationConnectionFactory : IConnectionFactory - { - private const int DefaultShards = 16; - private const string DefaultConnectionName = "EventStore"; - - private static readonly ILog Logger = LogFactory.BuildLogger(typeof(ConfigurationConnectionFactory)); - private static readonly IDictionary CachedSettings = - new Dictionary(); - private static readonly IDictionary CachedFactories = - new Dictionary(); - - private readonly string masterConnectionName; - private readonly string replicaConnectionName; - private readonly int shards; - - public ConfigurationConnectionFactory(string connectionName) - : this(connectionName, connectionName, DefaultShards) - { - } - public ConfigurationConnectionFactory( - string masterConnectionName, string replicaConnectionName, int shards) - { - this.masterConnectionName = masterConnectionName ?? DefaultConnectionName; - this.replicaConnectionName = replicaConnectionName ?? this.masterConnectionName; - this.shards = shards >= 0 ? shards : DefaultShards; - - Logger.Debug(Messages.ConfiguringConnections, - this.masterConnectionName, this.replicaConnectionName, this.shards); - } - - public static IDisposable OpenScope() - { - var settings = CachedSettings.FirstOrDefault(); - if (string.IsNullOrEmpty(settings.Key)) - throw new ConfigurationErrorsException(Messages.NotConnectionsAvailable); - - return OpenScope(Guid.Empty, settings.Key); - } - public static IDisposable OpenScope(string connectionName) - { - return OpenScope(Guid.Empty, connectionName); - } - public static IDisposable OpenScope(Guid streamId, string connectionName) - { - var factory = new ConfigurationConnectionFactory(connectionName); - return factory.Open(streamId, connectionName); - } - - public virtual ConnectionStringSettings Settings - { - get { return this.GetConnectionStringSettings(this.masterConnectionName); } - } - - public virtual IDbConnection OpenMaster(Guid streamId) - { - Logger.Verbose(Messages.OpeningMasterConnection, this.masterConnectionName); - return this.Open(streamId, this.masterConnectionName); - } - public virtual IDbConnection OpenReplica(Guid streamId) - { - Logger.Verbose(Messages.OpeningReplicaConnection, this.replicaConnectionName); - return this.Open(streamId, this.replicaConnectionName); - } - protected virtual IDbConnection Open(Guid streamId, string connectionName) - { - var setting = this.GetSetting(connectionName); - var connectionString = this.BuildConnectionString(streamId, setting); - return new ConnectionScope(connectionString, () => this.Open(connectionString, setting)); - } - protected virtual IDbConnection Open(string connectionString, ConnectionStringSettings setting) - { - var factory = this.GetFactory(setting); - var connection = factory.CreateConnection(); - if (connection == null) - throw new ConfigurationErrorsException(Messages.BadConnectionName); - - connection.ConnectionString = connectionString; - - try - { - Logger.Verbose(Messages.OpeningConnection, setting.Name); - connection.Open(); - } - catch (Exception e) - { - Logger.Warn(Messages.OpenFailed, setting.Name); - throw new StorageUnavailableException(e.Message, e); - } - - return connection; - } - - protected virtual ConnectionStringSettings GetSetting(string connectionName) - { - lock (CachedSettings) - { - ConnectionStringSettings setting; - if (CachedSettings.TryGetValue(connectionName, out setting)) - return setting; - - setting = this.GetConnectionStringSettings(connectionName); - return CachedSettings[connectionName] = setting; - } - } - protected virtual DbProviderFactory GetFactory(ConnectionStringSettings setting) - { - lock (CachedFactories) - { - DbProviderFactory factory; - if (CachedFactories.TryGetValue(setting.Name, out factory)) - return factory; - - factory = DbProviderFactories.GetFactory(setting.ProviderName); - Logger.Debug(Messages.DiscoveredConnectionProvider, setting.Name, factory.GetType()); - return CachedFactories[setting.Name] = factory; - } - } - protected virtual ConnectionStringSettings GetConnectionStringSettings(string connectionName) - { - Logger.Debug(Messages.DiscoveringConnectionSettings, connectionName); - - var settings = ConfigurationManager.ConnectionStrings - .Cast() - .FirstOrDefault(x => x.Name == connectionName); - - if (settings == null) - throw new ConfigurationErrorsException( - Messages.ConnectionNotFound.FormatWith(connectionName)); - - if ((settings.ConnectionString ?? string.Empty).Trim().Length == 0) - throw new ConfigurationErrorsException( - Messages.MissingConnectionString.FormatWith(connectionName)); - - if ((settings.ProviderName ?? string.Empty).Trim().Length == 0) - throw new ConfigurationErrorsException( - Messages.MissingProviderName.FormatWith(connectionName)); - - return settings; - } - - protected virtual string BuildConnectionString(Guid streamId, ConnectionStringSettings setting) - { - if (this.shards == 0) - return setting.ConnectionString; - - Logger.Verbose(Messages.EmbeddingShardKey, setting.Name); - return setting.ConnectionString.FormatWith(this.ComputeHashKey(streamId)); - } - protected virtual string ComputeHashKey(Guid streamId) - { - // simple sharding scheme which could easily be improved through such techniques - // as consistent hashing (Amazon Dynamo) or other kinds of sharding. - return (this.shards == 0 ? 0 : streamId.ToByteArray()[0] % this.shards).ToString(); - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Persistence.SqlPersistence/ConnectionScope.cs b/src/proj/EventStore.Persistence.SqlPersistence/ConnectionScope.cs deleted file mode 100644 index 4df6154b3..000000000 --- a/src/proj/EventStore.Persistence.SqlPersistence/ConnectionScope.cs +++ /dev/null @@ -1,54 +0,0 @@ -namespace EventStore.Persistence.SqlPersistence -{ - using System; - using System.Data; - - public class ConnectionScope : ThreadScope, IDbConnection - { - public ConnectionScope(string connectionName, Func factory) - : base(connectionName, factory) - { - } - IDbTransaction IDbConnection.BeginTransaction() - { - return this.Current.BeginTransaction(); - } - IDbTransaction IDbConnection.BeginTransaction(IsolationLevel il) - { - return this.Current.BeginTransaction(il); - } - void IDbConnection.Close() - { - // no-op--let Dispose do the real work. - } - void IDbConnection.ChangeDatabase(string databaseName) - { - this.Current.ChangeDatabase(databaseName); - } - IDbCommand IDbConnection.CreateCommand() - { - return this.Current.CreateCommand(); - } - void IDbConnection.Open() - { - this.Current.Open(); - } - string IDbConnection.ConnectionString - { - get { return this.Current.ConnectionString; } - set { this.Current.ConnectionString = value; } - } - int IDbConnection.ConnectionTimeout - { - get { return this.Current.ConnectionTimeout; } - } - string IDbConnection.Database - { - get { return this.Current.Database; } - } - ConnectionState IDbConnection.State - { - get { return this.Current.State; } - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Persistence.SqlPersistence/EventStore.Persistence.SqlPersistence.csproj b/src/proj/EventStore.Persistence.SqlPersistence/EventStore.Persistence.SqlPersistence.csproj deleted file mode 100644 index ee095de6c..000000000 --- a/src/proj/EventStore.Persistence.SqlPersistence/EventStore.Persistence.SqlPersistence.csproj +++ /dev/null @@ -1,208 +0,0 @@ - - - - Debug - AnyCPU - 9.0.30729 - 2.0 - {DAFD3F38-33F3-4F53-BFEA-44E51BD1E8F1} - Library - Properties - EventStore.Persistence.SqlPersistence - EventStore.Persistence.SqlPersistence - v4.0 - 512 - true - ..\..\EventStore.snk - ..\..\..\src\ - true - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - Properties\GlobalAssemblyInfo.cs - - - Properties\GlobalSuppressions.cs - - - Properties\VersionAssemblyInfo.cs - - - - - - - - True - True - OracleNativeStatements.resx - - - - - True - True - Messages.resx - - - - - - - - - Code - - - AccessStatements.resx - True - True - - - - - CommonSqlStatements.resx - True - True - - - - Code - - - FirebirdSqlStatements.resx - True - True - - - - True - True - MsSqlStatements.resx - - - Code - - - MySqlStatements.resx - True - True - - - - - Code - - - PostgreSqlStatements.resx - True - True - - - Code - - - SqlCeStatements.resx - True - True - - - Code - - - SqliteStatements.resx - True - True - - - - - - - - Properties\CustomDictionary.xml - - - - - {03946843-F343-419C-88EF-3E446D08DFA6} - EventStore - - - - - - - - - - - - ResXFileCodeGenerator - Messages.Designer.cs - Designer - - - ResXFileCodeGenerator - AccessStatements.Designer.cs - - - Designer - ResXFileCodeGenerator - CommonSqlStatements.Designer.cs - - - ResXFileCodeGenerator - FirebirdSqlStatements.Designer.cs - Designer - - - ResXFileCodeGenerator - MsSqlStatements.Designer.cs - Designer - - - ResXFileCodeGenerator - MySqlStatements.Designer.cs - - - Designer - ResXFileCodeGenerator - OracleNativeStatements.Designer.cs - - - Designer - ResXFileCodeGenerator - PostgreSqlStatements.Designer.cs - - - ResXFileCodeGenerator - SqlCeStatements.Designer.cs - - - Designer - ResXFileCodeGenerator - SqliteStatements.Designer.cs - - - - - - \ No newline at end of file diff --git a/src/proj/EventStore.Persistence.SqlPersistence/ExtensionMethods.cs b/src/proj/EventStore.Persistence.SqlPersistence/ExtensionMethods.cs deleted file mode 100644 index 83538bfd1..000000000 --- a/src/proj/EventStore.Persistence.SqlPersistence/ExtensionMethods.cs +++ /dev/null @@ -1,39 +0,0 @@ -namespace EventStore.Persistence.SqlPersistence -{ - using System; - using System.Data; - using Logging; - - internal static class ExtensionMethods - { - private static readonly ILog Logger = LogFactory.BuildLogger(typeof(ExtensionMethods)); - - public static Guid ToGuid(this object value) - { - if (value is Guid) - return (Guid)value; - - var bytes = value as byte[]; - return bytes != null ? new Guid(bytes) : Guid.Empty; - } - public static int ToInt(this object value) - { - return value is int ? (int)value : - value is long ? (int)(long)value : - value is decimal ? (int)(decimal)value : Convert.ToInt32(value); - } - public static DateTime ToDateTime(this object value) - { - value = value is decimal ? (long)(decimal)value : value; - return value is long ? new DateTime((long)value) : (DateTime)value; - } - - public static IDbCommand SetParameter(this IDbCommand command, string name, object value) - { - Logger.Verbose("Rebinding parameter '{0}' with value: {1}", name, value); - var parameter = (IDataParameter)command.Parameters[name]; - parameter.Value = value; - return command; - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Persistence.SqlPersistence/IConnectionFactory.cs b/src/proj/EventStore.Persistence.SqlPersistence/IConnectionFactory.cs deleted file mode 100644 index c1222602a..000000000 --- a/src/proj/EventStore.Persistence.SqlPersistence/IConnectionFactory.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace EventStore.Persistence.SqlPersistence -{ - using System; - using System.Data; - - public interface IConnectionFactory - { - IDbConnection OpenMaster(Guid streamId); - IDbConnection OpenReplica(Guid streamId); - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Persistence.SqlPersistence/IDbStatement.cs b/src/proj/EventStore.Persistence.SqlPersistence/IDbStatement.cs deleted file mode 100644 index bec119980..000000000 --- a/src/proj/EventStore.Persistence.SqlPersistence/IDbStatement.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace EventStore.Persistence.SqlPersistence -{ - using System; - using System.Collections.Generic; - using System.Data; - using SqlDialects; - - public interface IDbStatement : IDisposable - { - void AddParameter(string name, object value); - - int ExecuteNonQuery(string commandText); - int ExecuteWithoutExceptions(string commandText); - - object ExecuteScalar(string commandText); - - int PageSize { get; set; } - - IEnumerable ExecuteWithQuery(string queryText); - IEnumerable ExecutePagedQuery(string queryText, NextPageDelegate nextpage); - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Persistence.SqlPersistence/ISqlDialect.cs b/src/proj/EventStore.Persistence.SqlPersistence/ISqlDialect.cs deleted file mode 100644 index fea7694ba..000000000 --- a/src/proj/EventStore.Persistence.SqlPersistence/ISqlDialect.cs +++ /dev/null @@ -1,51 +0,0 @@ -namespace EventStore.Persistence.SqlPersistence -{ - using System; - using System.Data; - using System.Transactions; - - public interface ISqlDialect - { - string InitializeStorage { get; } - string PurgeStorage { get; } - - string GetCommitsFromStartingRevision { get; } - string GetCommitsFromInstant { get; } - string GetCommitsFromToInstant { get; } - - string PersistCommit { get; } - string DuplicateCommit { get; } - - string GetStreamsRequiringSnapshots { get; } - string GetSnapshot { get; } - string AppendSnapshotToCommit { get; } - - string GetUndispatchedCommits { get; } - string MarkCommitAsDispatched { get; } - - string StreamId { get; } - string StreamRevision { get; } - string MaxStreamRevision { get; } - string Items { get; } - string CommitId { get; } - string CommitSequence { get; } - string CommitStamp { get; } - string CommitStampStart { get; } - string CommitStampEnd { get; } - string Headers { get; } - string Payload { get; } - string Threshold { get; } - - string Limit { get; } - string Skip { get; } - bool CanPage { get; } - - object CoalesceParameterValue(object value); - - IDbTransaction OpenTransaction(IDbConnection connection); - IDbStatement BuildStatement( - TransactionScope scope, IDbConnection connection, IDbTransaction transaction); - - bool IsDuplicate(Exception exception); - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Persistence.SqlPersistence/Messages.Designer.cs b/src/proj/EventStore.Persistence.SqlPersistence/Messages.Designer.cs deleted file mode 100644 index b916fb864..000000000 --- a/src/proj/EventStore.Persistence.SqlPersistence/Messages.Designer.cs +++ /dev/null @@ -1,558 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.239 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace EventStore.Persistence.SqlPersistence { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Messages { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Messages() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("EventStore.Persistence.SqlPersistence.Messages", typeof(Messages).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to Adding parameter named '{0}' to statement.. - /// - internal static string AddingParameter { - get { - return ResourceManager.GetString("AddingParameter", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Adding snapshot to stream '{0}' at position {1}.. - /// - internal static string AddingSnapshot { - get { - return ResourceManager.GetString("AddingSnapshot", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Attempt to use storage after it has been disposed.. - /// - internal static string AlreadyDisposed { - get { - return ResourceManager.GetString("AlreadyDisposed", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Attempting to commit {0} events on stream '{1}' at sequence {2}.. - /// - internal static string AttemptingToCommit { - get { - return ResourceManager.GetString("AttemptingToCommit", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to A connection could not be created for the specified named connection.. - /// - internal static string BadConnectionName { - get { - return ResourceManager.GetString("BadConnectionName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The factory provided was unable to create an object to store.. - /// - internal static string BadFactoryResult { - get { - return ResourceManager.GetString("BadFactoryResult", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Binding parameter '{0}' with value: {1}. - /// - internal static string BindingParameter { - get { - return ResourceManager.GetString("BindingParameter", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cleaning up root threaded scope. - /// - internal static string CleaningRootThreadScope { - get { - return ResourceManager.GetString("CleaningRootThreadScope", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Command has client-controlled transaction: {0}.. - /// - internal static string ClientControlledTransaction { - get { - return ResourceManager.GetString("ClientControlledTransaction", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Command executed, {0} rows affected.. - /// - internal static string CommandExecuted { - get { - return ResourceManager.GetString("CommandExecuted", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Command text to be executed: {0}. - /// - internal static string CommandTextToExecute { - get { - return ResourceManager.GetString("CommandTextToExecute", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Non-query statement threw an exception of type '{0}'.. - /// - internal static string CommandThrewException { - get { - return ResourceManager.GetString("CommandThrewException", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Commit '{0}' persisted successfully.. - /// - internal static string CommitPersisted { - get { - return ResourceManager.GetString("CommitPersisted", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Concurrent write detected.. - /// - internal static string ConcurrentWriteDetected { - get { - return ResourceManager.GetString("ConcurrentWriteDetected", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Configuring connections: master '{0}'; replica '{1}', shards: {2}.. - /// - internal static string ConfiguringConnections { - get { - return ResourceManager.GetString("ConfiguringConnections", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Could not find connection name '{0}' in the configuration file.. - /// - internal static string ConnectionNotFound { - get { - return ResourceManager.GetString("ConnectionNotFound", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Creating command.. - /// - internal static string CreatingCommand { - get { - return ResourceManager.GetString("CreatingCommand", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Deserializing commit from record using serializer of type '{0}'.. - /// - internal static string DeserializingCommit { - get { - return ResourceManager.GetString("DeserializingCommit", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Deserializing snapshot from data record.. - /// - internal static string DeserializingSnapshot { - get { - return ResourceManager.GetString("DeserializingSnapshot", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Discovered DB provider factory settings for '{0}', using '{1}'.. - /// - internal static string DiscoveredConnectionProvider { - get { - return ResourceManager.GetString("DiscoveredConnectionProvider", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Discovering connection settings for '{0}'.. - /// - internal static string DiscoveringConnectionSettings { - get { - return ResourceManager.GetString("DiscoveringConnectionSettings", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Disposing underlying thread-scoped resource.. - /// - internal static string DisposingRootThreadScopeResources { - get { - return ResourceManager.GetString("DisposingRootThreadScopeResources", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Disposing SQL statement resources, including any transactions and connections.. - /// - internal static string DisposingStatement { - get { - return ResourceManager.GetString("DisposingStatement", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Disposing threaded scope; scope is root: {0}. - /// - internal static string DisposingThreadScope { - get { - return ResourceManager.GetString("DisposingThreadScope", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Duplicate commit detected; throwing.. - /// - internal static string DuplicateCommit { - get { - return ResourceManager.GetString("DuplicateCommit", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Embedding sharding key into connection string for '{0}'.. - /// - internal static string EmbeddingShardKey { - get { - return ResourceManager.GetString("EmbeddingShardKey", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Enumerated {0} rows, re-querying for next page.. - /// - internal static string EnumeratedRowCount { - get { - return ResourceManager.GetString("EnumeratedRowCount", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Enumeration of paged results threw exception of type '{0}'.. - /// - internal static string EnumerationThrewException { - get { - return ResourceManager.GetString("EnumerationThrewException", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Executed statement threw an exception, but the exception was suppressed.. - /// - internal static string ExceptionSuppressed { - get { - return ResourceManager.GetString("ExceptionSuppressed", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Executing command.. - /// - internal static string ExecutingCommand { - get { - return ResourceManager.GetString("ExecutingCommand", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Executing query.. - /// - internal static string ExecutingQuery { - get { - return ResourceManager.GetString("ExecutingQuery", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Getting all commits for stream '{0}' between revisions '{1}' and '{2}'.. - /// - internal static string GettingAllCommitsBetween { - get { - return ResourceManager.GetString("GettingAllCommitsBetween", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Getting all commits from '{0}' forward.. - /// - internal static string GettingAllCommitsFrom { - get { - return ResourceManager.GetString("GettingAllCommitsFrom", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Getting all commits from '{0}' to '{1}'.. - /// - internal static string GettingAllCommitsFromTo { - get { - return ResourceManager.GetString("GettingAllCommitsFromTo", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Getting snapshot for stream '{0}' on or before revision {1}.. - /// - internal static string GettingRevision { - get { - return ResourceManager.GetString("GettingRevision", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Getting a list of streams to snapshot.. - /// - internal static string GettingStreamsToSnapshot { - get { - return ResourceManager.GetString("GettingStreamsToSnapshot", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Getting the list of all undispatched commits.. - /// - internal static string GettingUndispatchedCommits { - get { - return ResourceManager.GetString("GettingUndispatchedCommits", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Initializing storage engine.. - /// - internal static string InitializingStorage { - get { - return ResourceManager.GetString("InitializingStorage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Marking commit '{0}' as dispatched.. - /// - internal static string MarkingCommitAsDispatched { - get { - return ResourceManager.GetString("MarkingCommitAsDispatched", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Executing query with max page size of {0}.. - /// - internal static string MaxPageSize { - get { - return ResourceManager.GetString("MaxPageSize", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Could not find the required attribute 'connection string' on the connection name '{0}' in the configuration file.. - /// - internal static string MissingConnectionString { - get { - return ResourceManager.GetString("MissingConnectionString", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Could not find the required attribute 'providerName' on the connection name '{0}' in the configuration file.. - /// - internal static string MissingProviderName { - get { - return ResourceManager.GetString("MissingProviderName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to No connections have been opened yet.. - /// - internal static string NotConnectionsAvailable { - get { - return ResourceManager.GetString("NotConnectionsAvailable", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The object has been disposed and cannot be used.. - /// - internal static string ObjectAlreadyDisposed { - get { - return ResourceManager.GetString("ObjectAlreadyDisposed", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Unable to open connection '{0}'.. - /// - internal static string OpenFailed { - get { - return ResourceManager.GetString("OpenFailed", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Opening connection '{0}'.. - /// - internal static string OpeningConnection { - get { - return ResourceManager.GetString("OpeningConnection", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Opening master connection '{0}'. - /// - internal static string OpeningMasterConnection { - get { - return ResourceManager.GetString("OpeningMasterConnection", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Opening replica connection '{0}'.. - /// - internal static string OpeningReplicaConnection { - get { - return ResourceManager.GetString("OpeningReplicaConnection", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Opening new threaded scope for key '{0}'; scope is root: {1}. - /// - internal static string OpeningThreadScope { - get { - return ResourceManager.GetString("OpeningThreadScope", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Purging all stored data.. - /// - internal static string PurgingStorage { - get { - return ResourceManager.GetString("PurgingStorage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Enumeration of result set completed, completing associated transaction scope.. - /// - internal static string QueryCompleted { - get { - return ResourceManager.GetString("QueryCompleted", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Exception is recoverable, completing scope.. - /// - internal static string RecoverableExceptionCompletesScope { - get { - return ResourceManager.GetString("RecoverableExceptionCompletesScope", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Shutting down persistence.. - /// - internal static string ShuttingDownPersistence { - get { - return ResourceManager.GetString("ShuttingDownPersistence", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Storage threw exception of type '{0}', wrapping and re-throwing.. - /// - internal static string StorageThrewException { - get { - return ResourceManager.GetString("StorageThrewException", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Participating in connection with ambient transaction scope of . - /// - internal static string UsingScope { - get { - return ResourceManager.GetString("UsingScope", resourceCulture); - } - } - } -} diff --git a/src/proj/EventStore.Persistence.SqlPersistence/Messages.resx b/src/proj/EventStore.Persistence.SqlPersistence/Messages.resx deleted file mode 100644 index a0403003b..000000000 --- a/src/proj/EventStore.Persistence.SqlPersistence/Messages.resx +++ /dev/null @@ -1,285 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - Could not find connection name '{0}' in the configuration file. - - - Could not find the required attribute 'connection string' on the connection name '{0}' in the configuration file. - - - Could not find the required attribute 'providerName' on the connection name '{0}' in the configuration file. - - - A connection could not be created for the specified named connection. - - - Configuring connections: master '{0}'; replica '{1}', shards: {2}. - - - Opening master connection '{0}' - - - Opening replica connection '{0}'. - - - Opening connection '{0}'. - - - Unable to open connection '{0}'. - - - Discovered DB provider factory settings for '{0}', using '{1}'. - - - Discovering connection settings for '{0}'. - - - Embedding sharding key into connection string for '{0}'. - - - Storage threw exception of type '{0}', wrapping and re-throwing. - - - Enumeration of result set completed, completing associated transaction scope. - - - Participating in connection with ambient transaction scope of - - - Executing command. - - - Command executed, {0} rows affected. - - - Executing query. - - - Deserializing commit from record using serializer of type '{0}'. - - - Commit '{0}' persisted successfully. - - - Attempting to commit {0} events on stream '{1}' at sequence {2}. - - - Marking commit '{0}' as dispatched. - - - Getting a list of streams to snapshot. - - - Getting snapshot for stream '{0}' on or before revision {1}. - - - Adding snapshot to stream '{0}' at position {1}. - - - Getting the list of all undispatched commits. - - - Getting all commits from '{0}' forward. - - - Getting all commits from '{0}' to '{1}'. - - - Getting all commits for stream '{0}' between revisions '{1}' and '{2}'. - - - Initializing storage engine. - - - Purging all stored data. - - - Deserializing snapshot from data record. - - - Executed statement threw an exception, but the exception was suppressed. - - - Adding parameter named '{0}' to statement. - - - Disposing SQL statement resources, including any transactions and connections. - - - Duplicate commit detected; throwing. - - - Non-query statement threw an exception of type '{0}'. - - - Executing query with max page size of {0}. - - - Creating command. - - - Command has client-controlled transaction: {0}. - - - Command text to be executed: {0} - - - Binding parameter '{0}' with value: {1} - - - Enumerated {0} rows, re-querying for next page. - - - Enumeration of paged results threw exception of type '{0}'. - - - Shutting down persistence. - - - Exception is recoverable, completing scope. - - - The object has been disposed and cannot be used. - - - Attempt to use storage after it has been disposed. - - - Opening new threaded scope for key '{0}'; scope is root: {1} - - - Disposing threaded scope; scope is root: {0} - - - Cleaning up root threaded scope - - - Disposing underlying thread-scoped resource. - - - The factory provided was unable to create an object to store. - - - No connections have been opened yet. - - - Concurrent write detected. - - \ No newline at end of file diff --git a/src/proj/EventStore.Persistence.SqlPersistence/Properties/AssemblyInfo.cs b/src/proj/EventStore.Persistence.SqlPersistence/Properties/AssemblyInfo.cs deleted file mode 100644 index fdb56426f..000000000 --- a/src/proj/EventStore.Persistence.SqlPersistence/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,6 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -[assembly: AssemblyTitle("EventStore.Persistence.SqlPersistence")] -[assembly: AssemblyDescription("")] -[assembly: Guid("54e3cc86-6f17-4271-89da-4772beda6b8b")] \ No newline at end of file diff --git a/src/proj/EventStore.Persistence.SqlPersistence/SnapshotExtensions.cs b/src/proj/EventStore.Persistence.SqlPersistence/SnapshotExtensions.cs deleted file mode 100644 index f14896f63..000000000 --- a/src/proj/EventStore.Persistence.SqlPersistence/SnapshotExtensions.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace EventStore.Persistence.SqlPersistence -{ - using System.Data; - using Logging; - using Serialization; - - internal static class SnapshotExtensions - { - private const int StreamIdIndex = 0; - private const int StreamRevisionIndex = 1; - private const int PayloadIndex = 2; - private static readonly ILog Logger = LogFactory.BuildLogger(typeof(SnapshotExtensions)); - - public static Snapshot GetSnapshot(this IDataRecord record, ISerialize serializer) - { - Logger.Verbose(Messages.DeserializingSnapshot); - - return new Snapshot( - record[StreamIdIndex].ToGuid(), - record[StreamRevisionIndex].ToInt(), - serializer.Deserialize(record, PayloadIndex)); - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/AccessDialect.cs b/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/AccessDialect.cs deleted file mode 100644 index 752592339..000000000 --- a/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/AccessDialect.cs +++ /dev/null @@ -1,102 +0,0 @@ -namespace EventStore.Persistence.SqlPersistence.SqlDialects -{ - using System; - using System.Collections.Generic; - using System.Data; - using System.Linq; - using System.Text.RegularExpressions; - using System.Transactions; - - public class AccessDialect : CommonSqlDialect - { - private const string ParameterPattern = "@[a-z0-9_]+"; - - public override string InitializeStorage - { - get { return AccessStatements.InitializeStorage; } - } - public override string AppendSnapshotToCommit - { - get { return base.AppendSnapshotToCommit.Replace("/*FROM DUAL*/", "FROM DUAL"); } - } - public override string GetSnapshot - { - get { return base.GetSnapshot.Replace("SELECT *", "SELECT TOP 1 *").Replace("LIMIT 1", string.Empty); } - } - public override string GetStreamsRequiringSnapshots - { - get { return RemovePaging(AccessStatements.GetStreamsToSnapshot); } - } - public override string GetCommitsFromInstant - { - get { return RemovePaging(base.GetCommitsFromInstant); } - } - public override string GetCommitsFromToInstant - { - get { return RemovePaging(base.GetCommitsFromToInstant); } - } - public override string GetCommitsFromStartingRevision - { - get { return RemovePaging(base.GetCommitsFromStartingRevision); } - } - public override string GetUndispatchedCommits - { - get { return RemovePaging(base.GetUndispatchedCommits); } - } - private static string RemovePaging(string query) - { - return query - .Replace("\n LIMIT @Limit OFFSET @Skip;", ";") - .Replace("\n LIMIT @Limit;", ";"); - } - - public override bool CanPage - { - get { return false; } - } - - public override object CoalesceParameterValue(object value) - { - if (value is DateTime) - return (decimal)((DateTime)value).Ticks; - - return value; - } - - public override IDbStatement BuildStatement( - TransactionScope scope, IDbConnection connection, IDbTransaction transaction) - { - return new AccessDbStatement(this, scope, connection, transaction); - } - - private class AccessDbStatement : DelimitedDbStatement - { - public AccessDbStatement( - ISqlDialect dialect, - TransactionScope scope, - IDbConnection connection, - IDbTransaction transaction) - : base(dialect, scope, connection, transaction) - { - } - - protected override void BuildParameters(IDbCommand command) - { - foreach (var item in this.Parameters.Where(item => item.Value is int)) - command.CommandText = command.CommandText.Replace(item.Key, item.Value.ToString()); - - // parameter names are resolved based upon their order, not name - foreach (var name in DiscoverParameters(command.CommandText)) - this.BuildParameter(command, name, this.Parameters[name]); - } - private static IEnumerable DiscoverParameters(string statement) - { - if (string.IsNullOrEmpty(statement)) - return new string[] { }; - - var matches = Regex.Matches(statement, ParameterPattern, RegexOptions.IgnoreCase); - return from Match match in matches select match.Value; // non-unique - } - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/AccessStatements.Designer.cs b/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/AccessStatements.Designer.cs deleted file mode 100644 index 0eb4d788e..000000000 --- a/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/AccessStatements.Designer.cs +++ /dev/null @@ -1,105 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.225 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace EventStore.Persistence.SqlPersistence.SqlDialects { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class AccessStatements { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal AccessStatements() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("EventStore.Persistence.SqlPersistence.SqlDialects.AccessStatements", typeof(AccessStatements).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to SELECT C.StreamId, MAX(C.StreamRevision) AS StreamRevision, MAX(IIf(S.StreamRevision IS NULL, 0, S.StreamRevision)) AS SnapshotRevision - /// FROM Commits AS C - /// LEFT JOIN Snapshots AS S - /// ON ( C.StreamId = S.StreamId AND C.StreamRevision >= S.StreamRevision ) - /// GROUP BY C.StreamId - ///HAVING MAX(C.StreamRevision) >= MAX(IIf(S.StreamRevision IS NULL, 0, S.StreamRevision)) + @Threshold;. - /// - internal static string GetStreamsToSnapshot { - get { - return ResourceManager.GetString("GetStreamsToSnapshot", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to CREATE TABLE Dual - ///( - /// DualTableValue char(1) NOT NULL - ///); - ///INSERT INTO Dual VALUES (' '); - /// - ///CREATE TABLE Commits - ///( - /// StreamId guid NOT NULL, - /// StreamRevision int NOT NULL, - /// Items tinyint NOT NULL, - /// CommitId guid NOT NULL, - /// CommitSequence int NOT NULL, - /// CommitStamp decimal NOT NULL, - /// Dispatched bit NOT NULL DEFAULT 0, - /// Headers image NULL, - /// Payload image NOT NULL, - /// CONSTRAINT PK_Commits PRIMARY KEY (StreamId, CommitSequence) - ///); - ///CRE [rest of string was truncated]";. - /// - internal static string InitializeStorage { - get { - return ResourceManager.GetString("InitializeStorage", resourceCulture); - } - } - } -} diff --git a/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/AccessStatements.resx b/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/AccessStatements.resx deleted file mode 100644 index a049dadf1..000000000 --- a/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/AccessStatements.resx +++ /dev/null @@ -1,161 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - SELECT C.StreamId, MAX(C.StreamRevision) AS StreamRevision, MAX(IIf(S.StreamRevision IS NULL, 0, S.StreamRevision)) AS SnapshotRevision - FROM Commits AS C - LEFT JOIN Snapshots AS S - ON ( C.StreamId = S.StreamId AND C.StreamRevision >= S.StreamRevision ) - GROUP BY C.StreamId -HAVING MAX(C.StreamRevision) >= MAX(IIf(S.StreamRevision IS NULL, 0, S.StreamRevision)) + @Threshold; - - - CREATE TABLE Dual -( - DualTableValue char(1) NOT NULL -); -INSERT INTO Dual VALUES (' '); - -CREATE TABLE Commits -( - StreamId guid NOT NULL, - StreamRevision int NOT NULL, - Items tinyint NOT NULL, - CommitId guid NOT NULL, - CommitSequence int NOT NULL, - CommitStamp decimal NOT NULL, - Dispatched bit NOT NULL DEFAULT 0, - Headers image NULL, - Payload image NOT NULL, - CONSTRAINT PK_Commits PRIMARY KEY (StreamId, CommitSequence) -); -CREATE UNIQUE INDEX IX_Commits ON Commits (StreamId, CommitId); -CREATE UNIQUE INDEX IX_Commits_Revisions ON Commits (StreamId, StreamRevision, Items); -CREATE INDEX IX_Commits_Dispatched ON Commits (Dispatched); -CREATE INDEX IX_Commits_Stamp ON Commits (CommitStamp); - -CREATE TABLE Snapshots -( - StreamId guid NOT NULL, - StreamRevision int NOT NULL, - Payload image NOT NULL, - CONSTRAINT PK_Snapshots PRIMARY KEY (StreamId, StreamRevision) -); - - \ No newline at end of file diff --git a/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/CommonDbStatement.cs b/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/CommonDbStatement.cs deleted file mode 100644 index bd71e5bca..000000000 --- a/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/CommonDbStatement.cs +++ /dev/null @@ -1,160 +0,0 @@ -namespace EventStore.Persistence.SqlPersistence.SqlDialects -{ - using System; - using System.Collections.Generic; - using System.Data; - using System.Transactions; - using Logging; - - public class CommonDbStatement : IDbStatement - { - private const int InfinitePageSize = 0; - private static readonly ILog Logger = LogFactory.BuildLogger(typeof(CommonDbStatement)); - private readonly ISqlDialect dialect; - private readonly TransactionScope scope; - private readonly IDbConnection connection; - private readonly IDbTransaction transaction; - - protected IDictionary Parameters { get; private set; } - protected ISqlDialect Dialect { get { return dialect; } } - - public CommonDbStatement( - ISqlDialect dialect, - TransactionScope scope, - IDbConnection connection, - IDbTransaction transaction) - { - this.Parameters = new Dictionary(); - - this.dialect = dialect; - this.scope = scope; - this.connection = connection; - this.transaction = transaction; - } - - public void Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); - } - protected virtual void Dispose(bool disposing) - { - Logger.Verbose(Messages.DisposingStatement); - - if (this.transaction != null) - this.transaction.Dispose(); - - if (this.connection != null) - this.connection.Dispose(); - - if (this.scope != null) - this.scope.Dispose(); - } - - public virtual int PageSize { get; set; } - - public virtual void AddParameter(string name, object value) - { - Logger.Debug(Messages.AddingParameter, name); - this.Parameters[name] = this.dialect.CoalesceParameterValue(value); - } - - public virtual int ExecuteWithoutExceptions(string commandText) - { - try - { - return this.ExecuteNonQuery(commandText); - } - catch (Exception) - { - Logger.Debug(Messages.ExceptionSuppressed); - return 0; - } - } - public virtual int ExecuteNonQuery(string commandText) - { - try - { - using (var command = this.BuildCommand(commandText)) - return command.ExecuteNonQuery(); - } - catch (Exception e) - { - if (this.dialect.IsDuplicate(e)) - throw new UniqueKeyViolationException(e.Message, e); - - throw; - } - } - - public virtual object ExecuteScalar(string commandText) - { - using (var command = this.BuildCommand(commandText)) - return command.ExecuteScalar(); - } - - public virtual IEnumerable ExecuteWithQuery(string queryText) - { - return this.ExecuteQuery(queryText, (query, latest) => { }, InfinitePageSize); - } - public virtual IEnumerable ExecutePagedQuery(string queryText, NextPageDelegate nextpage) - { - var pageSize = this.dialect.CanPage ? this.PageSize : InfinitePageSize; - if (pageSize > 0) - { - Logger.Verbose(Messages.MaxPageSize, pageSize); - this.Parameters.Add(this.dialect.Limit, pageSize); - } - - return this.ExecuteQuery(queryText, nextpage, pageSize); - } - protected virtual IEnumerable ExecuteQuery(string queryText, NextPageDelegate nextpage, int pageSize) - { - this.Parameters.Add(this.dialect.Skip, 0); - var command = this.BuildCommand(queryText); - - try - { - return new PagedEnumerationCollection(this.scope, this.dialect, command, nextpage, pageSize, this); - } - catch (Exception) - { - command.Dispose(); - throw; - } - } - protected virtual IDbCommand BuildCommand(string statement) - { - Logger.Verbose(Messages.CreatingCommand); - var command = this.connection.CreateCommand(); - command.Transaction = this.transaction; - command.CommandText = statement; - - Logger.Verbose(Messages.ClientControlledTransaction, this.transaction != null); - Logger.Verbose(Messages.CommandTextToExecute, statement); - - this.BuildParameters(command); - - return command; - } - protected virtual void BuildParameters(IDbCommand command) - { - foreach (var item in this.Parameters) - this.BuildParameter(command, item.Key, item.Value); - } - protected virtual void BuildParameter(IDbCommand command, string name, object value) - { - var parameter = command.CreateParameter(); - parameter.ParameterName = name; - this.SetParameterValue(parameter, value, null); - - Logger.Verbose(Messages.BindingParameter, name, parameter.Value); - command.Parameters.Add(parameter); - } - protected virtual void SetParameterValue(IDataParameter param, object value, DbType? type) - { - param.Value = value ?? DBNull.Value; - param.DbType = type ?? (value == null ? DbType.Binary : param.DbType); - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/CommonSqlDialect.cs b/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/CommonSqlDialect.cs deleted file mode 100644 index e7b69301a..000000000 --- a/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/CommonSqlDialect.cs +++ /dev/null @@ -1,141 +0,0 @@ -namespace EventStore.Persistence.SqlPersistence.SqlDialects -{ - using System; - using System.Data; - using System.Transactions; - - public abstract class CommonSqlDialect : ISqlDialect - { - public abstract string InitializeStorage { get; } - public virtual string PurgeStorage - { - get { return CommonSqlStatements.PurgeStorage; } - } - - public virtual string GetCommitsFromStartingRevision - { - get { return CommonSqlStatements.GetCommitsFromStartingRevision; } - } - public virtual string GetCommitsFromInstant - { - get { return CommonSqlStatements.GetCommitsFromInstant; } - } - public virtual string GetCommitsFromToInstant - { - get { return CommonSqlStatements.GetCommitsFromToInstant; } - } - public virtual string PersistCommit - { - get { return CommonSqlStatements.PersistCommit; } - } - public virtual string DuplicateCommit - { - get { return CommonSqlStatements.DuplicateCommit; } - } - - public virtual string GetStreamsRequiringSnapshots - { - get { return CommonSqlStatements.GetStreamsRequiringSnapshots; } - } - public virtual string GetSnapshot - { - get { return CommonSqlStatements.GetSnapshot; } - } - public virtual string AppendSnapshotToCommit - { - get { return CommonSqlStatements.AppendSnapshotToCommit; } - } - - public virtual string GetUndispatchedCommits - { - get { return CommonSqlStatements.GetUndispatchedCommits; } - } - public virtual string MarkCommitAsDispatched - { - get { return CommonSqlStatements.MarkCommitAsDispatched; } - } - - public virtual string StreamId - { - get { return "@StreamId"; } - } - public virtual string StreamRevision - { - get { return "@StreamRevision"; } - } - public virtual string MaxStreamRevision - { - get { return "@MaxStreamRevision"; } - } - public virtual string Items - { - get { return "@Items"; } - } - public virtual string CommitId - { - get { return "@CommitId"; } - } - public virtual string CommitSequence - { - get { return "@CommitSequence"; } - } - public virtual string CommitStamp - { - get { return "@CommitStamp"; } - } - public virtual string CommitStampStart - { - get { return "@CommitStampStart"; } - } - public virtual string CommitStampEnd - { - get { return "@CommitStampEnd"; } - } - public virtual string Headers - { - get { return "@Headers"; } - } - public virtual string Payload - { - get { return "@Payload"; } - } - public virtual string Threshold - { - get { return "@Threshold"; } - } - - public virtual string Limit - { - get { return "@Limit"; } - } - public virtual string Skip - { - get { return "@Skip"; } - } - public virtual bool CanPage - { - get { return true; } - } - - public virtual object CoalesceParameterValue(object value) - { - return value; - } - - public virtual bool IsDuplicate(Exception exception) - { - var message = exception.Message.ToUpperInvariant(); - return message.Contains("DUPLICATE") || message.Contains("UNIQUE") || message.Contains("CONSTRAINT"); - } - - public virtual IDbTransaction OpenTransaction(IDbConnection connection) - { - return null; - } - public virtual IDbStatement BuildStatement( - TransactionScope scope, IDbConnection connection, IDbTransaction transaction) - { - return new CommonDbStatement(this, scope, connection, transaction); - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/CommonSqlStatements.Designer.cs b/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/CommonSqlStatements.Designer.cs deleted file mode 100644 index bfbbd543f..000000000 --- a/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/CommonSqlStatements.Designer.cs +++ /dev/null @@ -1,221 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.17020 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace EventStore.Persistence.SqlPersistence.SqlDialects { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class CommonSqlStatements { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal CommonSqlStatements() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("EventStore.Persistence.SqlPersistence.SqlDialects.CommonSqlStatements", typeof(CommonSqlStatements).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to INSERT - /// INTO Snapshots - /// ( StreamId, StreamRevision, Payload ) - ///SELECT @StreamId, @StreamRevision, @Payload - ////*FROM DUAL*/ - /// WHERE EXISTS - /// ( SELECT * - /// FROM Commits - /// WHERE StreamId = @StreamId - /// AND (StreamRevision - Items) <= @StreamRevision ) - /// AND NOT EXISTS - /// ( SELECT * - /// FROM Snapshots - /// WHERE StreamId = @StreamId - /// AND StreamRevision = @StreamRevision );. - /// - internal static string AppendSnapshotToCommit { - get { - return ResourceManager.GetString("AppendSnapshotToCommit", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to SELECT COUNT(*) - /// FROM Commits - /// WHERE StreamId = @StreamId - /// AND CommitSequence = @CommitSequence - /// AND CommitId = @CommitId;. - /// - internal static string DuplicateCommit { - get { - return ResourceManager.GetString("DuplicateCommit", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to SELECT StreamId, StreamRevision, CommitId, CommitSequence, CommitStamp, Headers, Payload - /// FROM Commits - /// WHERE CommitStamp >= @CommitStamp - /// ORDER BY CommitStamp, StreamId, StreamRevision - /// LIMIT @Limit OFFSET @Skip;. - /// - internal static string GetCommitsFromInstant { - get { - return ResourceManager.GetString("GetCommitsFromInstant", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to SELECT StreamId, StreamRevision, CommitId, CommitSequence, CommitStamp, Headers, Payload - /// FROM Commits - /// WHERE StreamId = @StreamId - /// AND StreamRevision >= @StreamRevision - /// AND (StreamRevision - Items) <= @MaxStreamRevision - /// AND CommitSequence > @CommitSequence - /// ORDER BY CommitSequence - /// LIMIT @Limit;. - /// - internal static string GetCommitsFromStartingRevision { - get { - return ResourceManager.GetString("GetCommitsFromStartingRevision", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to SELECT StreamId, StreamRevision, CommitId, CommitSequence, CommitStamp, Headers, Payload - /// FROM Commits - /// WHERE CommitStamp >= @CommitStampStart - /// AND CommitStamp < @CommitStampEnd - /// ORDER BY CommitStamp, StreamId, StreamRevision - /// LIMIT @Limit OFFSET @Skip;. - /// - internal static string GetCommitsFromToInstant { - get { - return ResourceManager.GetString("GetCommitsFromToInstant", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to SELECT * - /// FROM Snapshots - /// WHERE StreamId = @StreamId - /// AND StreamRevision <= @StreamRevision - /// ORDER BY StreamRevision DESC - /// LIMIT 1;. - /// - internal static string GetSnapshot { - get { - return ResourceManager.GetString("GetSnapshot", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to SELECT C.StreamId, MAX(C.StreamRevision) AS StreamRevision, MAX(COALESCE(S.StreamRevision, 0)) AS SnapshotRevision - /// FROM Commits AS C - /// LEFT OUTER JOIN Snapshots AS S - /// ON C.StreamId = S.StreamId - /// AND C.StreamRevision >= S.StreamRevision - /// WHERE C.StreamId > @StreamId - /// GROUP BY C.StreamId - ///HAVING MAX(C.StreamRevision) >= MAX(COALESCE(S.StreamRevision, 0)) + @Threshold - /// ORDER BY C.StreamId - /// LIMIT @Limit;. - /// - internal static string GetStreamsRequiringSnapshots { - get { - return ResourceManager.GetString("GetStreamsRequiringSnapshots", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to SELECT StreamId, StreamRevision, CommitId, CommitSequence, CommitStamp, Headers, Payload - /// FROM Commits - /// WHERE Dispatched = 0 - /// ORDER BY CommitStamp - /// LIMIT @Limit OFFSET @Skip;. - /// - internal static string GetUndispatchedCommits { - get { - return ResourceManager.GetString("GetUndispatchedCommits", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to UPDATE Commits - /// SET Dispatched = 1 - /// WHERE StreamId = @StreamId - /// AND CommitSequence = @CommitSequence;. - /// - internal static string MarkCommitAsDispatched { - get { - return ResourceManager.GetString("MarkCommitAsDispatched", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to INSERT - /// INTO Commits - /// ( StreamId, CommitId, CommitSequence, StreamRevision, Items, CommitStamp, Headers, Payload ) - ///VALUES (@StreamId, @CommitId, @CommitSequence, @StreamRevision, @Items, @CommitStamp, @Headers, @Payload);. - /// - internal static string PersistCommit { - get { - return ResourceManager.GetString("PersistCommit", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to DELETE FROM Snapshots; - ///DELETE FROM Commits;. - /// - internal static string PurgeStorage { - get { - return ResourceManager.GetString("PurgeStorage", resourceCulture); - } - } - } -} diff --git a/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/CommonSqlStatements.resx b/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/CommonSqlStatements.resx deleted file mode 100644 index e96132d9f..000000000 --- a/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/CommonSqlStatements.resx +++ /dev/null @@ -1,212 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - INSERT - INTO Snapshots - ( StreamId, StreamRevision, Payload ) -SELECT @StreamId, @StreamRevision, @Payload -/*FROM DUAL*/ - WHERE EXISTS - ( SELECT * - FROM Commits - WHERE StreamId = @StreamId - AND (StreamRevision - Items) <= @StreamRevision ) - AND NOT EXISTS - ( SELECT * - FROM Snapshots - WHERE StreamId = @StreamId - AND StreamRevision = @StreamRevision ); - - - SELECT COUNT(*) - FROM Commits - WHERE StreamId = @StreamId - AND CommitSequence = @CommitSequence - AND CommitId = @CommitId; - - - SELECT StreamId, StreamRevision, CommitId, CommitSequence, CommitStamp, Headers, Payload - FROM Commits - WHERE CommitStamp >= @CommitStamp - ORDER BY CommitStamp, StreamId, StreamRevision - LIMIT @Limit OFFSET @Skip; - - - SELECT StreamId, StreamRevision, CommitId, CommitSequence, CommitStamp, Headers, Payload - FROM Commits - WHERE CommitStamp >= @CommitStampStart - AND CommitStamp < @CommitStampEnd - ORDER BY CommitStamp, StreamId, StreamRevision - LIMIT @Limit OFFSET @Skip; - - - SELECT StreamId, StreamRevision, CommitId, CommitSequence, CommitStamp, Headers, Payload - FROM Commits - WHERE StreamId = @StreamId - AND StreamRevision >= @StreamRevision - AND (StreamRevision - Items) <= @MaxStreamRevision - AND CommitSequence > @CommitSequence - ORDER BY CommitSequence - LIMIT @Limit; - - - SELECT * - FROM Snapshots - WHERE StreamId = @StreamId - AND StreamRevision <= @StreamRevision - ORDER BY StreamRevision DESC - LIMIT 1; - - - SELECT C.StreamId, MAX(C.StreamRevision) AS StreamRevision, MAX(COALESCE(S.StreamRevision, 0)) AS SnapshotRevision - FROM Commits AS C - LEFT OUTER JOIN Snapshots AS S - ON C.StreamId = S.StreamId - AND C.StreamRevision >= S.StreamRevision - WHERE C.StreamId > @StreamId - GROUP BY C.StreamId -HAVING MAX(C.StreamRevision) >= MAX(COALESCE(S.StreamRevision, 0)) + @Threshold - ORDER BY C.StreamId - LIMIT @Limit; - - - SELECT StreamId, StreamRevision, CommitId, CommitSequence, CommitStamp, Headers, Payload - FROM Commits - WHERE Dispatched = 0 - ORDER BY CommitStamp - LIMIT @Limit OFFSET @Skip; - - - UPDATE Commits - SET Dispatched = 1 - WHERE StreamId = @StreamId - AND CommitSequence = @CommitSequence; - - - INSERT - INTO Commits - ( StreamId, CommitId, CommitSequence, StreamRevision, Items, CommitStamp, Headers, Payload ) -VALUES (@StreamId, @CommitId, @CommitSequence, @StreamRevision, @Items, @CommitStamp, @Headers, @Payload); - - - DELETE FROM Snapshots; -DELETE FROM Commits; - - \ No newline at end of file diff --git a/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/DelimitedDbStatement.cs b/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/DelimitedDbStatement.cs deleted file mode 100644 index 9a32da4d8..000000000 --- a/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/DelimitedDbStatement.cs +++ /dev/null @@ -1,36 +0,0 @@ -namespace EventStore.Persistence.SqlPersistence.SqlDialects -{ - using System; - using System.Collections.Generic; - using System.Data; - using System.Linq; - using System.Transactions; - - public class DelimitedDbStatement : CommonDbStatement - { - private const string Delimiter = ";"; - - public DelimitedDbStatement( - ISqlDialect dialect, - TransactionScope scope, - IDbConnection connection, - IDbTransaction transaction) - : base(dialect, scope, connection, transaction) - { - } - - public override int ExecuteNonQuery(string commandText) - { - return SplitCommandText(commandText).Sum(x => base.ExecuteNonQuery(x)); - } - private static IEnumerable SplitCommandText(string delimited) - { - if (string.IsNullOrEmpty(delimited)) - return new string[] { }; - - return delimited.Split(Delimiter.ToCharArray(), StringSplitOptions.RemoveEmptyEntries) - .AsEnumerable().Select(x => x + Delimiter) - .ToArray(); - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/FirebirdSqlDialect.cs b/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/FirebirdSqlDialect.cs deleted file mode 100644 index e404a4080..000000000 --- a/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/FirebirdSqlDialect.cs +++ /dev/null @@ -1,68 +0,0 @@ -namespace EventStore.Persistence.SqlPersistence.SqlDialects -{ - using System.Data; - using System.Transactions; - - public class FirebirdSqlDialect : CommonSqlDialect - { - public override string InitializeStorage - { - get { return FirebirdSqlStatements.InitializeStorage; } - } - public override string AppendSnapshotToCommit - { - get { return base.AppendSnapshotToCommit.Replace("/*FROM DUAL*/", "FROM rdb$database"); } - } - public override string GetSnapshot - { - get { return base.GetSnapshot.Replace("SELECT *", "SELECT FIRST 1 *").Replace("LIMIT 1", string.Empty); } - } - - public override string GetCommitsFromStartingRevision - { - get { return this.Paged(base.GetCommitsFromStartingRevision); } - } - public override string GetCommitsFromInstant - { - get { return this.Paged(base.GetCommitsFromInstant); } - } - public override string GetCommitsFromToInstant - { - get { return this.Paged(base.GetCommitsFromToInstant); } - } - public override string GetStreamsRequiringSnapshots - { - get { return this.Paged(base.GetStreamsRequiringSnapshots); } - } - public override string GetUndispatchedCommits - { - get { return this.Paged(base.GetUndispatchedCommits); } - } - - private string Paged(string query) - { - if (query.Contains(this.Skip)) - return query.Replace("SELECT ", "SELECT FIRST @Limit SKIP @Skip ").Replace("\n LIMIT @Limit OFFSET @Skip;", ";"); - - return query.Replace("SELECT ", "SELECT FIRST @Limit ").Replace("\n LIMIT @Limit;", ";"); - } - - public override IDbStatement BuildStatement( - TransactionScope scope, IDbConnection connection, IDbTransaction transaction) - { - return new FirebirdDbStatement(this, scope, connection, transaction); - } - - private class FirebirdDbStatement : DelimitedDbStatement - { - public FirebirdDbStatement( - ISqlDialect dialect, - TransactionScope scope, - IDbConnection connection, - IDbTransaction transaction) - : base(dialect, scope, connection, transaction) - { - } - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/FirebirdSqlStatements.Designer.cs b/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/FirebirdSqlStatements.Designer.cs deleted file mode 100644 index 6eac3e86f..000000000 --- a/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/FirebirdSqlStatements.Designer.cs +++ /dev/null @@ -1,83 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.225 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace EventStore.Persistence.SqlPersistence.SqlDialects { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class FirebirdSqlStatements { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal FirebirdSqlStatements() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("EventStore.Persistence.SqlPersistence.SqlDialects.FirebirdSqlStatements", typeof(FirebirdSqlStatements).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to CREATE TABLE Commits - ///( - /// StreamId char(16) character set octets NOT NULL, - /// StreamRevision int NOT NULL CHECK (StreamRevision > 0), - /// Items smallint NOT NULL CHECK (Items > 0), - /// CommitId char(16) character set octets NOT NULL, - /// CommitSequence int NOT NULL CHECK (CommitSequence > 0), - /// CommitStamp timestamp NOT NULL, - /// Dispatched char(1) DEFAULT 0 NOT NULL, - /// Headers blob, - /// Payload blob NOT NULL, - /// CONSTRAINT PK_Commits PRIMARY KEY (StreamId, Co [rest of string was truncated]";. - /// - internal static string InitializeStorage { - get { - return ResourceManager.GetString("InitializeStorage", resourceCulture); - } - } - } -} diff --git a/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/FirebirdSqlStatements.resx b/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/FirebirdSqlStatements.resx deleted file mode 100644 index f4f8add8e..000000000 --- a/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/FirebirdSqlStatements.resx +++ /dev/null @@ -1,147 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - CREATE TABLE Commits -( - StreamId char(16) character set octets NOT NULL, - StreamRevision int NOT NULL CHECK (StreamRevision > 0), - Items smallint NOT NULL CHECK (Items > 0), - CommitId char(16) character set octets NOT NULL, - CommitSequence int NOT NULL CHECK (CommitSequence > 0), - CommitStamp timestamp NOT NULL, - Dispatched char(1) DEFAULT 0 NOT NULL, - Headers blob, - Payload blob NOT NULL, - CONSTRAINT PK_Commits PRIMARY KEY (StreamId, CommitSequence) -); -CREATE UNIQUE INDEX IX_Commits_CommitId ON Commits (StreamId, CommitId); -CREATE UNIQUE INDEX IX_Commits_Revisions ON Commits (StreamId, StreamRevision, Items); -CREATE INDEX IX_Commits_Dispatched ON Commits (Dispatched); -CREATE INDEX IX_Commits_Stamp ON Commits (CommitStamp); - -CREATE TABLE Snapshots -( - StreamId char(16) character set octets NOT NULL, - StreamRevision int NOT NULL CHECK (StreamRevision > 0), - Payload blob NOT NULL, - CONSTRAINT PK_Snapshots PRIMARY KEY (StreamId, StreamRevision) -); - - \ No newline at end of file diff --git a/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/MsSqlDialect.cs b/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/MsSqlDialect.cs deleted file mode 100644 index 9fa2b1d21..000000000 --- a/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/MsSqlDialect.cs +++ /dev/null @@ -1,71 +0,0 @@ -namespace EventStore.Persistence.SqlPersistence.SqlDialects -{ - using System; - using System.Data.SqlClient; - - public class MsSqlDialect : CommonSqlDialect - { - private const int UniqueKeyViolation = 2627; - - public override string InitializeStorage - { - get { return MsSqlStatements.InitializeStorage; } - } - public override string GetSnapshot - { - get { return "SET ROWCOUNT 1;\n" + base.GetSnapshot.Replace("LIMIT 1;", ";"); } - } - - public override string GetCommitsFromStartingRevision - { - get { return NaturalPaging(base.GetCommitsFromStartingRevision); } - } - public override string GetCommitsFromInstant - { - get { return CommonTableExpressionPaging(base.GetCommitsFromInstant); } - } - public override string GetCommitsFromToInstant - { - get { return CommonTableExpressionPaging(base.GetCommitsFromToInstant); } - } - public override string GetUndispatchedCommits - { - get { return CommonTableExpressionPaging(base.GetUndispatchedCommits); } - } - public override string GetStreamsRequiringSnapshots - { - get { return NaturalPaging(base.GetStreamsRequiringSnapshots); } - } - - private static string NaturalPaging(string query) - { - return "SET ROWCOUNT @Limit;\n" + RemovePaging(query); - } - private static string CommonTableExpressionPaging(string query) - { - query = RemovePaging(query); - var orderByIndex = query.IndexOf("ORDER BY"); - var orderBy = query.Substring(orderByIndex).Replace(";", string.Empty); - query = query.Substring(0, orderByIndex); - - var fromIndex = query.IndexOf("FROM "); - var from = query.Substring(fromIndex); - var select = query.Substring(0, fromIndex); - - var value = MsSqlStatements.PagedQueryFormat.FormatWith(select, orderBy, from); - return value; - } - private static string RemovePaging(string query) - { - return query - .Replace("\n LIMIT @Limit OFFSET @Skip;", ";") - .Replace("\n LIMIT @Limit;", ";"); - } - - public override bool IsDuplicate(Exception exception) - { - var dbException = exception as SqlException; - return dbException != null && dbException.Number == UniqueKeyViolation; - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/MsSqlStatements.Designer.cs b/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/MsSqlStatements.Designer.cs deleted file mode 100644 index 1c3e92df6..000000000 --- a/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/MsSqlStatements.Designer.cs +++ /dev/null @@ -1,97 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.239 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace EventStore.Persistence.SqlPersistence.SqlDialects { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class MsSqlStatements { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal MsSqlStatements() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("EventStore.Persistence.SqlPersistence.SqlDialects.MsSqlStatements", typeof(MsSqlStatements).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to IF EXISTS(SELECT * FROM sysobjects WHERE name='Commits' AND xtype = 'U') RETURN; - /// - ///CREATE TABLE [dbo].[Commits] - ///( - /// [StreamId] [uniqueidentifier] NOT NULL, - /// [StreamRevision] [int] NOT NULL CHECK ([StreamRevision] > 0), - /// [Items] [tinyint] NOT NULL CHECK ([Items] > 0), - /// [CommitId] [uniqueidentifier] NOT NULL CHECK ([CommitId] != 0x0), - /// [CommitSequence] [int] NOT NULL CHECK ([CommitSequence] > 0), - /// [CommitStamp] [datetime] NOT NULL, - /// [Dispatched] [bit] NOT NULL [rest of string was truncated]";. - /// - internal static string InitializeStorage { - get { - return ResourceManager.GetString("InitializeStorage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to WITH [cte] AS - /// ( {0}, ROW_NUMBER() OVER ({1}) AS [row] {2} ) - /// - ///SELECT * - /// FROM [cte] - /// WHERE [row] BETWEEN @Skip + 1 - /// AND @Limit + @Skip;. - /// - internal static string PagedQueryFormat { - get { - return ResourceManager.GetString("PagedQueryFormat", resourceCulture); - } - } - } -} diff --git a/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/MsSqlStatements.resx b/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/MsSqlStatements.resx deleted file mode 100644 index c2c6edf6c..000000000 --- a/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/MsSqlStatements.resx +++ /dev/null @@ -1,158 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - IF EXISTS(SELECT * FROM sysobjects WHERE name='Commits' AND xtype = 'U') RETURN; - -CREATE TABLE [dbo].[Commits] -( - [StreamId] [uniqueidentifier] NOT NULL, - [StreamRevision] [int] NOT NULL CHECK ([StreamRevision] > 0), - [Items] [tinyint] NOT NULL CHECK ([Items] > 0), - [CommitId] [uniqueidentifier] NOT NULL CHECK ([CommitId] != 0x0), - [CommitSequence] [int] NOT NULL CHECK ([CommitSequence] > 0), - [CommitStamp] [datetime] NOT NULL, - [Dispatched] [bit] NOT NULL DEFAULT (0), - [Headers] [varbinary](MAX) NULL CHECK ([Headers] IS NULL OR DATALENGTH([Headers]) > 0), - [Payload] [varbinary](MAX) NOT NULL CHECK (DATALENGTH([Payload]) > 0), - CONSTRAINT [PK_Commits] PRIMARY KEY CLUSTERED ([StreamId], [CommitSequence]) -); -CREATE UNIQUE NONCLUSTERED INDEX [IX_Commits] ON [dbo].[Commits] ([StreamId], [CommitId]); -CREATE UNIQUE NONCLUSTERED INDEX [IX_Commits_Revisions] ON [dbo].[Commits] ([StreamId], [StreamRevision], [Items]); -CREATE INDEX [IX_Commits_Dispatched] ON [dbo].[Commits] ([Dispatched]); -CREATE INDEX [IX_Commits_Stamp] ON Commits ([CommitStamp]); - -CREATE TABLE [dbo].[Snapshots] -( - [StreamId] [uniqueidentifier] NOT NULL, - [StreamRevision] [int] NOT NULL CHECK ([StreamRevision] > 0), - [Payload] [varbinary](MAX) NOT NULL CHECK (DATALENGTH([Payload]) > 0), - CONSTRAINT [PK_Snapshots] PRIMARY KEY CLUSTERED ([StreamId], [StreamRevision]) -); - - - WITH [cte] AS - ( {0}, ROW_NUMBER() OVER ({1}) AS [row] {2} ) - -SELECT * - FROM [cte] - WHERE [row] BETWEEN @Skip + 1 - AND @Limit + @Skip; - - \ No newline at end of file diff --git a/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/MySqlDialect.cs b/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/MySqlDialect.cs deleted file mode 100644 index 2c28c0c71..000000000 --- a/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/MySqlDialect.cs +++ /dev/null @@ -1,43 +0,0 @@ -namespace EventStore.Persistence.SqlPersistence.SqlDialects -{ - using System; - - public class MySqlDialect : CommonSqlDialect - { - private const int UniqueKeyViolation = 1062; - - public override string InitializeStorage - { - get { return MySqlStatements.InitializeStorage; } - } - public override string AppendSnapshotToCommit - { - get { return base.AppendSnapshotToCommit.Replace("/*FROM DUAL*/", "FROM DUAL"); } - } - public override string MarkCommitAsDispatched - { - get { return base.MarkCommitAsDispatched.Replace("1", "true"); } - } - public override string GetUndispatchedCommits - { - get { return base.GetUndispatchedCommits.Replace("0", "false"); } - } - - public override object CoalesceParameterValue(object value) - { - if (value is Guid) - return ((Guid)value).ToByteArray(); - - if (value is DateTime) - return ((DateTime)value).Ticks; - - return value; - } - - public override bool IsDuplicate(Exception exception) - { - var property = exception.GetType().GetProperty("Number"); - return UniqueKeyViolation == (int)property.GetValue(exception, null); - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/MySqlStatements.Designer.cs b/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/MySqlStatements.Designer.cs deleted file mode 100644 index 7c06e6495..000000000 --- a/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/MySqlStatements.Designer.cs +++ /dev/null @@ -1,83 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.235 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace EventStore.Persistence.SqlPersistence.SqlDialects { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class MySqlStatements { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal MySqlStatements() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("EventStore.Persistence.SqlPersistence.SqlDialects.MySqlStatements", typeof(MySqlStatements).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to CREATE TABLE IF NOT EXISTS Commits - ///( - /// StreamId binary(16) NOT NULL, - /// StreamRevision int NOT NULL CHECK (StreamRevision > 0), - /// Items tinyint NOT NULL CHECK (Items > 0), - /// CommitId binary(16) NOT NULL CHECK (CommitId != 0), - /// CommitSequence int NOT NULL CHECK (CommitSequence > 0), - /// CommitStamp bigint NOT NULL, - /// Dispatched bit NOT NULL DEFAULT 0, - /// Headers blob NULL, - /// Payload blob NOT NULL, - /// CONSTRAINT PK_Commits PRIMARY KEY (StreamId, CommitS [rest of string was truncated]";. - /// - internal static string InitializeStorage { - get { - return ResourceManager.GetString("InitializeStorage", resourceCulture); - } - } - } -} diff --git a/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/MySqlStatements.resx b/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/MySqlStatements.resx deleted file mode 100644 index bdd6b4d94..000000000 --- a/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/MySqlStatements.resx +++ /dev/null @@ -1,147 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - CREATE TABLE IF NOT EXISTS Commits -( - StreamId binary(16) NOT NULL, - StreamRevision int NOT NULL CHECK (StreamRevision > 0), - Items tinyint NOT NULL CHECK (Items > 0), - CommitId binary(16) NOT NULL CHECK (CommitId != 0), - CommitSequence int NOT NULL CHECK (CommitSequence > 0), - CommitStamp bigint NOT NULL, - Dispatched bit NOT NULL DEFAULT 0, - Headers blob NULL, - Payload blob NOT NULL, - CONSTRAINT PK_Commits PRIMARY KEY (StreamId, CommitSequence) -); -CREATE UNIQUE INDEX IX_Commits_CommitId ON Commits (StreamId, CommitId); -CREATE UNIQUE INDEX IX_Commits_Revisions ON Commits (StreamId, StreamRevision, Items); -CREATE INDEX IX_Commits_Dispatched ON Commits (Dispatched); -CREATE INDEX IX_Commits_Stamp ON Commits (CommitStamp); - -CREATE TABLE IF NOT EXISTS Snapshots -( - StreamId binary(16) NOT NULL, - StreamRevision int NOT NULL CHECK (StreamRevision > 0), - Payload blob NOT NULL, - CONSTRAINT PK_Snapshots PRIMARY KEY (StreamId, StreamRevision) -); - - \ No newline at end of file diff --git a/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/NextPageDelegate.cs b/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/NextPageDelegate.cs deleted file mode 100644 index 9f6ccfb53..000000000 --- a/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/NextPageDelegate.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace EventStore.Persistence.SqlPersistence.SqlDialects -{ - using System.Data; - - public delegate void NextPageDelegate(IDbCommand command, IDataRecord current); -} \ No newline at end of file diff --git a/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/OracleDbStatement.cs b/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/OracleDbStatement.cs deleted file mode 100644 index c5c40807a..000000000 --- a/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/OracleDbStatement.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; -using System.Transactions; -using System.Data; - - -namespace EventStore.Persistence.SqlPersistence.SqlDialects -{ - public class OracleDbStatement : CommonDbStatement - { - public OracleDbStatement(ISqlDialect dialect, TransactionScope scope, IDbConnection connection, IDbTransaction transaction) - : base(dialect, scope, connection, transaction) { } - - public override void AddParameter(string name, object value) - { - name = name.Replace('@', ':'); - - if (value is Guid) - base.AddParameter(name, ((Guid)value).ToByteArray()); - else - base.AddParameter(name, value); - } - public override int ExecuteNonQuery(string commandText) - { - try - { - using (var command = this.BuildCommand(commandText)) - return command.ExecuteNonQuery(); - } - catch (Exception e) - { - if (this.Dialect.IsDuplicate(e)) - throw new UniqueKeyViolationException(e.Message, e); - - throw; - } - } - protected override IDbCommand BuildCommand(string statement) - { - var command = base.BuildCommand(statement); - var pi = command.GetType().GetProperty("BindByName"); - if(pi!= null) - pi.SetValue(command, true, null); - return command; - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/OracleNativeDialect.cs b/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/OracleNativeDialect.cs deleted file mode 100644 index d8e56267c..000000000 --- a/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/OracleNativeDialect.cs +++ /dev/null @@ -1,146 +0,0 @@ -namespace EventStore.Persistence.SqlPersistence.SqlDialects -{ - using System; - using System.Data.SqlClient; - - public class OracleNativeDialect : CommonSqlDialect - { - private const int UniqueKeyViolation = -2146232008; - - public override string AppendSnapshotToCommit - { - get { return OracleNativeStatements.AppendSnapshotToCommit; } - } - public override string CommitId - { - get { return MakeOracleParameter(base.CommitId); } - } - public override string CommitSequence - { - get { return MakeOracleParameter(base.CommitSequence); } - } - public override string CommitStamp - { - get { return MakeOracleParameter(base.CommitStamp); } - } - public override string DuplicateCommit - { - get { return OracleNativeStatements.DuplicateCommit; } - } - public override string GetSnapshot - { - get { return OracleNativeStatements.GetSnapshot; } - } - public override string GetCommitsFromStartingRevision - { - get { return AddOuterTrailingCommitSequence(LimitedQuery(OracleNativeStatements.GetCommitsFromStartingRevision)); } - } - public override string GetCommitsFromInstant - { - get { return OraclePaging(OracleNativeStatements.GetCommitsFromInstant); } - } - public override string GetUndispatchedCommits - { - get { return OraclePaging(base.GetUndispatchedCommits); } - } - public override string GetStreamsRequiringSnapshots - { - get { return LimitedQuery(OracleNativeStatements.GetStreamsRequiringSnapshots); } - } - public override string InitializeStorage - { - get { return OracleNativeStatements.InitializeStorage; } - } - public override string Limit - { - get { return MakeOracleParameter(base.Limit); } - } - public override string MarkCommitAsDispatched - { - get { return OracleNativeStatements.MarkCommitAsDispatched; } - } - public override string PersistCommit - { - get { return OracleNativeStatements.PersistCommit; } - } - public override string PurgeStorage - { - get { return OracleNativeStatements.PurgeStorage; } - } - public override string Skip - { - get { return MakeOracleParameter(base.Skip); } - } - public override string StreamId - { - get { return MakeOracleParameter(base.StreamId); } - } - public override string Threshold - { - get { return MakeOracleParameter(base.Threshold); } - } - - private string AddOuterTrailingCommitSequence(string query) - { - return (query.TrimEnd(new[] { ';' }) + "\r\n" + OracleNativeStatements.AddCommitSequence); - } - public override IDbStatement BuildStatement(System.Transactions.TransactionScope scope, System.Data.IDbConnection connection, System.Data.IDbTransaction transaction) - { - return new OracleDbStatement(this, scope, connection, transaction); - } - public override object CoalesceParameterValue(object value) - { - if (value is Guid) - value = ((Guid)value).ToByteArray(); - - return value; - } - private static string ExtractOrderBy(ref string query) - { - var orderByIndex = query.IndexOf("ORDER BY", StringComparison.Ordinal); - string result = query.Substring(orderByIndex).Replace(";", String.Empty); - query = query.Substring(0, orderByIndex); - - return result; - } - public override bool IsDuplicate(Exception exception) - { - return exception.Message.Contains("ORA-00001"); - } - private static string LimitedQuery(string query) - { - query = RemovePaging(query); - if (query.EndsWith(";")) - query = query.TrimEnd(new[] { ';' }); - var value = OracleNativeStatements.LimitedQueryFormat.FormatWith(query); - return value; - } - private static string MakeOracleParameter(string parameterName) - { - return parameterName.Replace('@', ':'); - } - private static string OraclePaging(string query) - { - query = RemovePaging(query); - - var orderBy = ExtractOrderBy(ref query); - - var fromIndex = query.IndexOf("FROM ", StringComparison.Ordinal); - var from = query.Substring(fromIndex); - - var select = query.Substring(0, fromIndex); - - var value = OracleNativeStatements.PagedQueryFormat.FormatWith(select, orderBy, from); - - return value; - } - private static string RemovePaging(string query) - { - return query - .Replace("\n LIMIT @Limit OFFSET @Skip;", ";") - .Replace("\n LIMIT @Limit;", ";") - .Replace("WHERE ROWNUM <= :Limit;", ";") - .Replace("\r\nWHERE ROWNUM <= (:Skip + 1) AND ROWNUM > :Skip", ";"); - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/OracleNativeStatements.Designer.cs b/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/OracleNativeStatements.Designer.cs deleted file mode 100644 index f716035cf..000000000 --- a/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/OracleNativeStatements.Designer.cs +++ /dev/null @@ -1,264 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.269 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace EventStore.Persistence.SqlPersistence.SqlDialects { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class OracleNativeStatements { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal OracleNativeStatements() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("EventStore.Persistence.SqlPersistence.SqlDialects.OracleNativeStatements", typeof(OracleNativeStatements).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to AND CommitSequence > :CommitSequence. - /// - internal static string AddCommitSequence { - get { - return ResourceManager.GetString("AddCommitSequence", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to /*AppendSnapshotToCommit*/ - ///INSERT INTO Snapshots - /// (StreamId, StreamRevision, Payload) - ///SELECT :StreamId, :StreamRevision, :Payload FROM SYS.DUAL - ///WHERE EXISTS - /// ( - /// SELECT * FROM COMMITS - /// WHERE StreamId = :StreamId - /// AND (StreamRevision - Items) <= :StreamRevision - /// ) - /// AND NOT EXISTS - /// ( - /// SELECT * FROM SNAPSHOTS - /// WHERE StreamId = :StreamId - /// And Streamrevision = :Streamrevision - /// ). - /// - internal static string AppendSnapshotToCommit { - get { - return ResourceManager.GetString("AppendSnapshotToCommit", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to /*DuplicateCommit*/ - ///SELECT CAST( COUNT(*) AS NUMBER(8,0) ) - ///FROM Commits - ///WHERE ( - /// StreamId = :StreamId - /// AND CommitSequence = :CommitSequence - /// AND CommitId = :CommitId - ///). - /// - internal static string DuplicateCommit { - get { - return ResourceManager.GetString("DuplicateCommit", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to /*GetCommitsFromInstant*/ - ///SELECT StreamId, StreamRevision, CommitId, CommitSequence, CommitStamp, Headers, Payload - ///FROM Commits - ///WHERE CommitStamp >= :CommitStamp - ///ORDER BY CommitStamp, StreamId, StreamRevision. - /// - internal static string GetCommitsFromInstant { - get { - return ResourceManager.GetString("GetCommitsFromInstant", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to /*GetCommitsFromStartingRevision*/ - ///SELECT StreamId, StreamRevision, CommitId, CommitSequence, CommitStamp, Headers, Payload - ///FROM Commits - ///WHERE StreamId = :StreamId - /// AND StreamRevision >= :StreamRevision - /// AND (StreamRevision - Items) <= :MaxStreamRevision - /// --AND CommitSequence > (:CommitSequence - :CommitSequence) - ///ORDER BY CommitSequence. - /// - internal static string GetCommitsFromStartingRevision { - get { - return ResourceManager.GetString("GetCommitsFromStartingRevision", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to /*GetSnapshot*/ - ///SELECT * - ///FROM Snapshots - ///WHERE StreamId = :StreamId - /// AND StreamRevision <= :StreamRevision - /// AND ROWNUM <= (:Skip + 1) AND ROWNUM > :Skip - ///ORDER BY StreamRevision DESC. - /// - internal static string GetSnapshot { - get { - return ResourceManager.GetString("GetSnapshot", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to /*GetStreamsRequiringSnapshots*/ - ///SELECT StreamId , StreamRevision, SnapshotRevision - ///FROM ( - /// SELECT C.StreamId, MAX(C.StreamRevision) AS StreamRevision, MAX(COALESCE(S.StreamRevision, 0)) AS SnapshotRevision - /// FROM Commits C LEFT OUTER JOIN Snapshots S - /// ON C.StreamId = S.StreamId AND C.StreamRevision >= S.StreamRevision - /// WHERE RAWTOHEX(C.StreamId) > RAWTOHEX(:StreamId) - /// GROUP BY C.StreamId - /// HAVING MAX(C.StreamRevision) >= MAX(COALESCE(S.StreamRevision, 0)) + :Threshold - /// ORDER BY C.StreamId [rest of string was truncated]";. - /// - internal static string GetStreamsRequiringSnapshots { - get { - return ResourceManager.GetString("GetStreamsRequiringSnapshots", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to /*InitializeStorage*/ DECLARE table_count INTEGER; BEGIN SELECT COUNT (OBJECT_ID) INTO table_count FROM USER_OBJECTS WHERE EXISTS (SELECT OBJECT_NAME FROM USER_OBJECTS WHERE (OBJECT_NAME = 'COMMITS' AND OBJECT_TYPE = 'TABLE')); IF table_count = 0 THEN DBMS_OUTPUT.PUT_LINE ('Creating the Commits table'); EXECUTE IMMEDIATE ('CREATE TABLE Commits ( [rest of string was truncated]";. - /// - internal static string InitializeStorage { - get { - return ResourceManager.GetString("InitializeStorage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to /*LimitedQueryFormat*/ - ///SELECT OuterQuery.* FROM ( - /// SELECT InnerQuery.*, ROWNUM AS ROW_NUMBER_VAL FROM ( - /// {0} - /// ) InnerQuery - ///) OuterQuery - ///WHERE ROW_NUMBER_VAL > :Skip AND ROW_NUMBER_VAL <= (:Limit + :Skip). - /// - internal static string LimitedQueryFormat { - get { - return ResourceManager.GetString("LimitedQueryFormat", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to /*MarkCommitAsDispatched*/ - ///UPDATE Commits - ///SET Dispatched = 1 - ///WHERE StreamId = :StreamId - /// AND CommitSequence = :CommitSequence. - /// - internal static string MarkCommitAsDispatched { - get { - return ResourceManager.GetString("MarkCommitAsDispatched", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to /*PagedQueryFormat*/ - ///SELECT * - ///FROM ( {0}, - /// ROW_NUMBER() OVER({1}) AS ROW_NUMBER_VAL - /// {2} - ///) PagedQueryFormat - ///WHERE ROW_NUMBER_VAL > :Skip AND ROW_NUMBER_VAL <= (:Limit + :Skip). - /// - internal static string PagedQueryFormat { - get { - return ResourceManager.GetString("PagedQueryFormat", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to /*PersistCommit*/ - ///INSERT INTO Commits ( - /// StreamId, - /// CommitId, - /// CommitSequence, - /// StreamRevision, - /// Items, - /// CommitStamp, - /// Headers, - /// Payload - ///) - ///VALUES ( - /// :StreamId, - /// :CommitId, - /// :CommitSequence, - /// :StreamRevision, - /// :Items, - /// :CommitStamp, - /// :Headers, - /// :Payload - ///). - /// - internal static string PersistCommit { - get { - return ResourceManager.GetString("PersistCommit", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to /*PurgeStorage*/DECLARE row_count INTEGER;BEGIN SELECT COUNT(1) INTO row_count FROM Snapshots; IF row_count != 0 THEN EXECUTE IMMEDIATE ('TRUNCATE TABLE Snapshots'); ELSE DBMS_OUTPUT.PUT_LINE('The Snapshots table has already been purged.'); END IF; SELECT COUNT(1) INTO row_count FROM Commits; IF row_count != 0 THEN EXECUTE IMMEDIATE ('TRUNCATE TABLE Commits'); ELSE DBMS_OUTPUT.PUT_LINE('The Commits table has already been purged.'); END IF; EXCEPTION WHEN OTHERS THEN DBM [rest of string was truncated]";. - /// - internal static string PurgeStorage { - get { - return ResourceManager.GetString("PurgeStorage", resourceCulture); - } - } - } -} diff --git a/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/OracleNativeStatements.resx b/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/OracleNativeStatements.resx deleted file mode 100644 index 211b3ad70..000000000 --- a/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/OracleNativeStatements.resx +++ /dev/null @@ -1,245 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - AND CommitSequence > :CommitSequence - - - /*AppendSnapshotToCommit*/ -INSERT INTO Snapshots - (StreamId, StreamRevision, Payload) -SELECT :StreamId, :StreamRevision, :Payload FROM SYS.DUAL -WHERE EXISTS - ( - SELECT * FROM COMMITS - WHERE StreamId = :StreamId - AND (StreamRevision - Items) <= :StreamRevision - ) - AND NOT EXISTS - ( - SELECT * FROM SNAPSHOTS - WHERE StreamId = :StreamId - And Streamrevision = :Streamrevision - ) - - - /*DuplicateCommit*/ -SELECT CAST( COUNT(*) AS NUMBER(8,0) ) -FROM Commits -WHERE ( - StreamId = :StreamId - AND CommitSequence = :CommitSequence - AND CommitId = :CommitId -) - - - /*GetCommitsFromInstant*/ -SELECT StreamId, StreamRevision, CommitId, CommitSequence, CommitStamp, Headers, Payload -FROM Commits -WHERE CommitStamp >= :CommitStamp -ORDER BY CommitStamp, StreamId, StreamRevision - - - /*GetCommitsFromStartingRevision*/ -SELECT StreamId, StreamRevision, CommitId, CommitSequence, CommitStamp, Headers, Payload -FROM Commits -WHERE StreamId = :StreamId - AND StreamRevision >= :StreamRevision - AND (StreamRevision - Items) <= :MaxStreamRevision - --AND CommitSequence > (:CommitSequence - :CommitSequence) -ORDER BY CommitSequence - - - /*GetSnapshot*/ -SELECT * -FROM Snapshots -WHERE StreamId = :StreamId - AND StreamRevision <= :StreamRevision - AND ROWNUM <= (:Skip + 1) AND ROWNUM > :Skip -ORDER BY StreamRevision DESC - - - /*GetStreamsRequiringSnapshots*/ -SELECT StreamId , StreamRevision, SnapshotRevision -FROM ( - SELECT C.StreamId, MAX(C.StreamRevision) AS StreamRevision, MAX(COALESCE(S.StreamRevision, 0)) AS SnapshotRevision - FROM Commits C LEFT OUTER JOIN Snapshots S - ON C.StreamId = S.StreamId AND C.StreamRevision >= S.StreamRevision - WHERE RAWTOHEX(C.StreamId) > RAWTOHEX(:StreamId) - GROUP BY C.StreamId - HAVING MAX(C.StreamRevision) >= MAX(COALESCE(S.StreamRevision, 0)) + :Threshold - ORDER BY C.StreamId -) -WHERE ROWNUM <= :Limit; - - - /*InitializeStorage*/ DECLARE table_count INTEGER; BEGIN SELECT COUNT (OBJECT_ID) INTO table_count FROM USER_OBJECTS WHERE EXISTS (SELECT OBJECT_NAME FROM USER_OBJECTS WHERE (OBJECT_NAME = 'COMMITS' AND OBJECT_TYPE = 'TABLE')); IF table_count = 0 THEN DBMS_OUTPUT.PUT_LINE ('Creating the Commits table'); EXECUTE IMMEDIATE ('CREATE TABLE Commits ( StreamId raw(16) NOT NULL, StreamRevision NUMBER(8) CHECK (StreamRevision > 0) NOT NULL, Items NUMBER(8) CHECK (Items > 0) NOT NULL, CommitId raw(16) CHECK (CommitId != HEXTORAW(0)) NOT NULL, CommitSequence NUMBER(8) CHECK (CommitSequence > 0) NOT NULL, CommitStamp DATE NOT NULL, Dispatched NUMBER(1) DEFAULT(0) NOT NULL, Headers BLOB CHECK (Headers IS NULL OR LENGTH(Headers) > 0) NULL, Payload BLOB CHECK (LENGTH(Payload) > 0) NOT NULL, CONSTRAINT PK_Commits PRIMARY KEY (StreamId, CommitSequence) )'); EXECUTE IMMEDIATE ('CREATE UNIQUE INDEX IX_Commits ON Commits (StreamId, CommitId)'); EXECUTE IMMEDIATE ('CREATE UNIQUE INDEX IX_Commits_Revisions ON Commits (StreamId, StreamRevision, Items)'); EXECUTE IMMEDIATE ('CREATE INDEX IX_Commits_Dispatched ON Commits (Dispatched)'); EXECUTE IMMEDIATE ('CREATE INDEX IX_Commits_Stamp ON Commits (CommitStamp)'); ELSE DBMS_OUTPUT.PUT_LINE ( 'The Commits table already exist in the database.' ); END IF; SELECT COUNT (OBJECT_ID) INTO table_count FROM USER_OBJECTS WHERE EXISTS (SELECT OBJECT_NAME FROM USER_OBJECTS WHERE (OBJECT_NAME = 'SNAPSHOTS' AND OBJECT_TYPE = 'TABLE')); IF table_count = 0 THEN DBMS_OUTPUT.PUT_LINE ('Creating the Snapshots table'); EXECUTE IMMEDIATE ('CREATE TABLE Snapshots ( StreamId raw(16) NOT NULL, StreamRevision NUMBER(8) CHECK (StreamRevision > 0) NOT NULL, Payload BLOB CHECK (LENGTH(Payload) > 0) NOT NULL, CONSTRAINT PK_Snapshots PRIMARY KEY (StreamId, StreamRevision) )'); ELSE DBMS_OUTPUT.PUT_LINE ( 'The Snapshots table already exist in the database.' ); RETURN; END IF; EXCEPTION WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE('An unexpected exception has occured. Please re-evaluate the PL/SQL script'); END; - - - /*LimitedQueryFormat*/ -SELECT OuterQuery.* FROM ( - SELECT InnerQuery.*, ROWNUM AS ROW_NUMBER_VAL FROM ( - {0} - ) InnerQuery -) OuterQuery -WHERE ROW_NUMBER_VAL > :Skip AND ROW_NUMBER_VAL <= (:Limit + :Skip) - - - /*MarkCommitAsDispatched*/ -UPDATE Commits -SET Dispatched = 1 -WHERE StreamId = :StreamId - AND CommitSequence = :CommitSequence - - - /*PagedQueryFormat*/ -SELECT * -FROM ( {0}, - ROW_NUMBER() OVER({1}) AS ROW_NUMBER_VAL - {2} -) PagedQueryFormat -WHERE ROW_NUMBER_VAL > :Skip AND ROW_NUMBER_VAL <= (:Limit + :Skip) - - - /*PersistCommit*/ -INSERT INTO Commits ( - StreamId, - CommitId, - CommitSequence, - StreamRevision, - Items, - CommitStamp, - Headers, - Payload -) -VALUES ( - :StreamId, - :CommitId, - :CommitSequence, - :StreamRevision, - :Items, - :CommitStamp, - :Headers, - :Payload -) - - - /*PurgeStorage*/DECLARE row_count INTEGER;BEGIN SELECT COUNT(1) INTO row_count FROM Snapshots; IF row_count != 0 THEN EXECUTE IMMEDIATE ('TRUNCATE TABLE Snapshots'); ELSE DBMS_OUTPUT.PUT_LINE('The Snapshots table has already been purged.'); END IF; SELECT COUNT(1) INTO row_count FROM Commits; IF row_count != 0 THEN EXECUTE IMMEDIATE ('TRUNCATE TABLE Commits'); ELSE DBMS_OUTPUT.PUT_LINE('The Commits table has already been purged.'); END IF; EXCEPTION WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE('An unexpected exception has occured. Please re-evaluate the PL/SQL script');END; - - \ No newline at end of file diff --git a/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/PagedEnumerationCollection.cs b/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/PagedEnumerationCollection.cs deleted file mode 100644 index 110fad95d..000000000 --- a/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/PagedEnumerationCollection.cs +++ /dev/null @@ -1,170 +0,0 @@ -namespace EventStore.Persistence.SqlPersistence.SqlDialects -{ - using System; - using System.Collections; - using System.Collections.Generic; - using System.Data; - using System.Transactions; - using Logging; - - public class PagedEnumerationCollection : IEnumerable, IEnumerator - { - private static readonly ILog Logger = LogFactory.BuildLogger(typeof(PagedEnumerationCollection)); - private readonly IEnumerable disposable = new IDisposable[] { }; - private readonly ISqlDialect dialect; - private readonly IDbCommand command; - private readonly NextPageDelegate nextpage; - private readonly int pageSize; - private readonly TransactionScope scope; - - private IDataReader reader; - private int position; - private IDataRecord current; - private bool disposed; - - public PagedEnumerationCollection( - TransactionScope scope, - ISqlDialect dialect, - IDbCommand command, - NextPageDelegate nextpage, - int pageSize, - params IDisposable[] disposable) - { - this.scope = scope; - this.dialect = dialect; - this.command = command; - this.nextpage = nextpage; - this.pageSize = pageSize; - this.disposable = disposable ?? this.disposable; - } - - public void Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); - } - protected virtual void Dispose(bool disposing) - { - if (!disposing || this.disposed) - return; - - this.disposed = true; - this.position = 0; - this.current = null; - - if (this.reader != null) - this.reader.Dispose(); - - this.reader = null; - - if (this.command != null) - this.command.Dispose(); - - // queries do not modify state and thus calling Complete() on a so-called 'failed' query only - // allows any outer transaction scope to decide the fate of the transaction - if (this.scope != null) - this.scope.Complete(); // caller will dispose scope. - - foreach (var dispose in this.disposable) - dispose.Dispose(); - } - - public virtual IEnumerator GetEnumerator() - { - if (this.disposed) - throw new ObjectDisposedException(Messages.ObjectAlreadyDisposed); - - return this; - } - IEnumerator IEnumerable.GetEnumerator() - { - return this.GetEnumerator(); - } - - bool IEnumerator.MoveNext() - { - if (this.disposed) - throw new ObjectDisposedException(Messages.ObjectAlreadyDisposed); - - if (this.MoveToNextRecord()) - return true; - - Logger.Verbose(Messages.QueryCompleted); - return false; - } - private bool MoveToNextRecord() - { - if (this.pageSize > 0 && this.position >= this.pageSize) - { - this.command.SetParameter(this.dialect.Skip, this.position); - this.nextpage(this.command, this.current); - } - - this.reader = this.reader ?? this.OpenNextPage(); - - if (this.reader.Read()) - return this.IncrementPosition(); - - if (!this.PagingEnabled()) - return false; - - if (!this.PageCompletelyEnumerated()) - return false; - - Logger.Verbose(Messages.EnumeratedRowCount, this.position); - this.reader.Dispose(); - this.reader = this.OpenNextPage(); - - if (this.reader.Read()) - return this.IncrementPosition(); - - return false; - } - - private bool IncrementPosition() - { - this.position++; - return true; - } - - private bool PagingEnabled() - { - return this.pageSize > 0; - } - private bool PageCompletelyEnumerated() - { - return this.position > 0 && 0 == this.position % this.pageSize; - } - private IDataReader OpenNextPage() - { - try - { - return this.command.ExecuteReader(); - } - catch (Exception e) - { - Logger.Debug(Messages.EnumerationThrewException, e.GetType()); - throw new StorageUnavailableException(e.Message, e); - } - } - - public virtual void Reset() - { - throw new NotSupportedException("Forward-only readers."); - } - public virtual IDataRecord Current - { - get - { - if (this.disposed) - throw new ObjectDisposedException(Messages.ObjectAlreadyDisposed); - - return this.current = this.reader; - } - } - object IEnumerator.Current - { - get { return ((IEnumerator)this).Current; } - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/PostgreSqlDialect.cs b/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/PostgreSqlDialect.cs deleted file mode 100644 index b0d101139..000000000 --- a/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/PostgreSqlDialect.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace EventStore.Persistence.SqlPersistence.SqlDialects -{ - public class PostgreSqlDialect : CommonSqlDialect - { - public override string InitializeStorage - { - get { return PostgreSqlStatements.InitializeStorage; } - } - - public override string MarkCommitAsDispatched - { - get { return base.MarkCommitAsDispatched.Replace("1", "true"); } - } - public override string GetUndispatchedCommits - { - get { return base.GetUndispatchedCommits.Replace("0", "false"); } - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/PostgreSqlStatements.Designer.cs b/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/PostgreSqlStatements.Designer.cs deleted file mode 100644 index 95c0db507..000000000 --- a/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/PostgreSqlStatements.Designer.cs +++ /dev/null @@ -1,85 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.225 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace EventStore.Persistence.SqlPersistence.SqlDialects { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class PostgreSqlStatements { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal PostgreSqlStatements() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("EventStore.Persistence.SqlPersistence.SqlDialects.PostgreSqlStatements", typeof(PostgreSqlStatements).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to CREATE TABLE Commits - ///( - /// StreamId uuid NOT NULL, - /// StreamRevision int NOT NULL CHECK (StreamRevision > 0), - /// Items smallint NOT NULL CHECK (Items > 0), - /// CommitId uuid NOT NULL, - /// CommitSequence int NOT NULL CHECK (CommitSequence > 0), - /// CommitStamp timestamp NOT NULL, - /// Dispatched boolean NOT NULL DEFAULT false, - /// Headers bytea NULL, - /// Payload bytea NOT NULL, - /// CONSTRAINT PK_Commits PRIMARY KEY (StreamId, CommitSequence) - ///); - ///CREATE UNIQUE INDEX [rest of string was truncated]";. - /// - internal static string InitializeStorage { - get { - return ResourceManager.GetString("InitializeStorage", resourceCulture); - } - } - } -} diff --git a/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/PostgreSqlStatements.resx b/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/PostgreSqlStatements.resx deleted file mode 100644 index 34852a11d..000000000 --- a/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/PostgreSqlStatements.resx +++ /dev/null @@ -1,147 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - CREATE TABLE Commits -( - StreamId uuid NOT NULL, - StreamRevision int NOT NULL CHECK (StreamRevision > 0), - Items smallint NOT NULL CHECK (Items > 0), - CommitId uuid NOT NULL, - CommitSequence int NOT NULL CHECK (CommitSequence > 0), - CommitStamp timestamp NOT NULL, - Dispatched boolean NOT NULL DEFAULT false, - Headers bytea NULL, - Payload bytea NOT NULL, - CONSTRAINT PK_Commits PRIMARY KEY (StreamId, CommitSequence) -); -CREATE UNIQUE INDEX IX_Commits_CommitId ON Commits (StreamId, CommitId); -CREATE UNIQUE INDEX IX_Commits_Revisions ON Commits (StreamId, StreamRevision, Items); -CREATE INDEX IX_Commits_Dispatched ON Commits (Dispatched); -CREATE INDEX IX_Commits_Stamp ON Commits (CommitStamp); - -CREATE TABLE Snapshots -( - StreamId uuid NOT NULL, - StreamRevision int NOT NULL CHECK (StreamRevision > 0), - Payload bytea NOT NULL, - CONSTRAINT PK_Snapshots PRIMARY KEY (StreamId, StreamRevision) -); - - \ No newline at end of file diff --git a/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/SqlCeDialect.cs b/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/SqlCeDialect.cs deleted file mode 100644 index c9316fc16..000000000 --- a/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/SqlCeDialect.cs +++ /dev/null @@ -1,75 +0,0 @@ -namespace EventStore.Persistence.SqlPersistence.SqlDialects -{ - using System; - using System.Data; - using System.Transactions; - - public class SqlCeDialect : CommonSqlDialect - { - public override string InitializeStorage - { - get { return SqlCeStatements.InitializeStorage; } - } - public override string GetSnapshot - { - get { return base.GetSnapshot.Replace("SELECT *", "SELECT TOP(1) *").Replace("LIMIT 1", string.Empty); } - } - - public override string GetCommitsFromStartingRevision - { - get { return RemovePaging(base.GetCommitsFromStartingRevision); } - } - public override string GetCommitsFromInstant - { - get { return RemovePaging(base.GetCommitsFromInstant); } - } - public override string GetCommitsFromToInstant - { - get { return RemovePaging(base.GetCommitsFromToInstant); } - } - public override string GetStreamsRequiringSnapshots - { - get { return RemovePaging(base.GetStreamsRequiringSnapshots); } - } - public override string GetUndispatchedCommits - { - get { return RemovePaging(base.GetUndispatchedCommits); } - } - private static string RemovePaging(string query) - { - return query - .Replace("\n LIMIT @Limit OFFSET @Skip;", ";") - .Replace("\n LIMIT @Limit;", ";"); - } - - public override bool CanPage - { - get { return false; } - } - - public override bool IsDuplicate(Exception exception) - { - // using reflection to avoid a direct dependency on SQL CE assemblies - var message = exception.Message.ToUpperInvariant(); - return message.Contains("DUPLICATE") || message.Contains("UNIQUE"); - } - - public override IDbStatement BuildStatement( - TransactionScope scope, IDbConnection connection, IDbTransaction transaction) - { - return new SqlCeDbStatement(this, scope, connection, transaction); - } - - private class SqlCeDbStatement : DelimitedDbStatement - { - public SqlCeDbStatement( - ISqlDialect dialect, - TransactionScope scope, - IDbConnection connection, - IDbTransaction transaction) - : base(dialect, scope, connection, transaction) - { - } - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/SqlCeStatements.Designer.cs b/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/SqlCeStatements.Designer.cs deleted file mode 100644 index 5ce52427a..000000000 --- a/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/SqlCeStatements.Designer.cs +++ /dev/null @@ -1,86 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.225 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace EventStore.Persistence.SqlPersistence.SqlDialects { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class SqlCeStatements { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal SqlCeStatements() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("EventStore.Persistence.SqlPersistence.SqlDialects.SqlCeStatements", typeof(SqlCeStatements).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to CREATE TABLE Commits - ///( - /// StreamId uniqueidentifier NOT NULL, - /// StreamRevision int NOT NULL, - /// Items tinyint NOT NULL, - /// CommitId uniqueidentifier NOT NULL, - /// CommitSequence int NOT NULL, - /// CommitStamp datetime NOT NULL, - /// Dispatched bit NOT NULL DEFAULT 0, - /// Headers image NULL, - /// Payload image NOT NULL, - /// CONSTRAINT PK_Commits PRIMARY KEY (StreamId, CommitSequence) - ///); - ///CREATE UNIQUE INDEX IX_Commits ON Commits (StreamId, CommitId); - ///CREATE UNIQUE [rest of string was truncated]";. - /// - internal static string InitializeStorage { - get { - return ResourceManager.GetString("InitializeStorage", resourceCulture); - } - } - } -} diff --git a/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/SqlCeStatements.resx b/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/SqlCeStatements.resx deleted file mode 100644 index 15c924d62..000000000 --- a/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/SqlCeStatements.resx +++ /dev/null @@ -1,147 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - CREATE TABLE Commits -( - StreamId uniqueidentifier NOT NULL, - StreamRevision int NOT NULL, - Items tinyint NOT NULL, - CommitId uniqueidentifier NOT NULL, - CommitSequence int NOT NULL, - CommitStamp datetime NOT NULL, - Dispatched bit NOT NULL DEFAULT 0, - Headers image NULL, - Payload image NOT NULL, - CONSTRAINT PK_Commits PRIMARY KEY (StreamId, CommitSequence) -); -CREATE UNIQUE INDEX IX_Commits ON Commits (StreamId, CommitId); -CREATE UNIQUE INDEX IX_Commits_Revisions ON Commits (StreamId, StreamRevision, Items); -CREATE INDEX IX_Commits_Dispatched ON Commits (Dispatched); -CREATE INDEX IX_Commits_Stamp ON Commits (CommitStamp); - -CREATE TABLE Snapshots -( - StreamId uniqueidentifier NOT NULL, - StreamRevision int NOT NULL, - Payload image NOT NULL, - CONSTRAINT PK_Snapshots PRIMARY KEY (StreamId, StreamRevision) -); - - \ No newline at end of file diff --git a/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/SqliteDialect.cs b/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/SqliteDialect.cs deleted file mode 100644 index 093b0e27b..000000000 --- a/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/SqliteDialect.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace EventStore.Persistence.SqlPersistence.SqlDialects -{ - public class SqliteDialect : CommonSqlDialect - { - public override string InitializeStorage - { - get { return SqliteStatements.InitializeStorage; } - } - - // Sqlite wants all parameters to be a part of the query - public override string GetCommitsFromStartingRevision - { - get { return base.GetCommitsFromStartingRevision.Replace("\n ORDER BY ", "\n AND @Skip = @Skip\nORDER BY "); } - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/SqliteStatements.Designer.cs b/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/SqliteStatements.Designer.cs deleted file mode 100644 index 2ebe37246..000000000 --- a/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/SqliteStatements.Designer.cs +++ /dev/null @@ -1,85 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.225 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace EventStore.Persistence.SqlPersistence.SqlDialects { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class SqliteStatements { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal SqliteStatements() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("EventStore.Persistence.SqlPersistence.SqlDialects.SqliteStatements", typeof(SqliteStatements).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to CREATE TABLE IF NOT EXISTS Commits - ///( - /// StreamId guid NOT NULL, - /// StreamRevision int NOT NULL CHECK (StreamRevision > 0), - /// Items int NOT NULL CHECK (Items > 0), - /// CommitId guid NOT NULL CHECK (CommitId != 0), - /// CommitSequence int NOT NULL CHECK (CommitSequence > 0), - /// CommitStamp datetime NOT NULL, - /// Dispatched bit NOT NULL DEFAULT 0, - /// Headers blob NULL, - /// Payload blob NOT NULL, - /// CONSTRAINT PK_Commits PRIMARY KEY (StreamId, CommitSequence) - ///); - /// [rest of string was truncated]";. - /// - internal static string InitializeStorage { - get { - return ResourceManager.GetString("InitializeStorage", resourceCulture); - } - } - } -} diff --git a/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/SqliteStatements.resx b/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/SqliteStatements.resx deleted file mode 100644 index 2ebdd24a9..000000000 --- a/src/proj/EventStore.Persistence.SqlPersistence/SqlDialects/SqliteStatements.resx +++ /dev/null @@ -1,147 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - CREATE TABLE IF NOT EXISTS Commits -( - StreamId guid NOT NULL, - StreamRevision int NOT NULL CHECK (StreamRevision > 0), - Items int NOT NULL CHECK (Items > 0), - CommitId guid NOT NULL CHECK (CommitId != 0), - CommitSequence int NOT NULL CHECK (CommitSequence > 0), - CommitStamp datetime NOT NULL, - Dispatched bit NOT NULL DEFAULT 0, - Headers blob NULL, - Payload blob NOT NULL, - CONSTRAINT PK_Commits PRIMARY KEY (StreamId, CommitSequence) -); -CREATE UNIQUE INDEX IF NOT EXISTS IX_Commits_CommitId ON Commits (StreamId, CommitId); -CREATE UNIQUE INDEX IF NOT EXISTS IX_Commits_Revisions ON Commits (StreamId, StreamRevision, Items); -CREATE INDEX IF NOT EXISTS IX_Commits_Dispatched ON Commits (Dispatched); -CREATE INDEX IF NOT EXISTS IX_Commits_Stamp ON Commits (CommitStamp); - -CREATE TABLE IF NOT EXISTS Snapshots -( - StreamId guid NOT NULL, - StreamRevision int NOT NULL CHECK (StreamRevision > 0), - Payload blob NOT NULL, - CONSTRAINT PK_Snapshots PRIMARY KEY (StreamId, StreamRevision) -); - - \ No newline at end of file diff --git a/src/proj/EventStore.Persistence.SqlPersistence/SqlPersistenceEngine.cs b/src/proj/EventStore.Persistence.SqlPersistence/SqlPersistenceEngine.cs deleted file mode 100644 index d5b44af9d..000000000 --- a/src/proj/EventStore.Persistence.SqlPersistence/SqlPersistenceEngine.cs +++ /dev/null @@ -1,333 +0,0 @@ -namespace EventStore.Persistence.SqlPersistence -{ - using System; - using System.Collections.Generic; - using System.Data; - using System.Linq; - using System.Threading; - using System.Transactions; - using Logging; - using Persistence; - using Serialization; - - public class SqlPersistenceEngine : IPersistStreams - { - private static readonly ILog Logger = LogFactory.BuildLogger(typeof(SqlPersistenceEngine)); - private static readonly DateTime EpochTime = new DateTime(1970, 1, 1); - private readonly IConnectionFactory connectionFactory; - private readonly ISqlDialect dialect; - private readonly ISerialize serializer; - private readonly TransactionScopeOption scopeOption; - private readonly int pageSize; - private int initialized; - private bool disposed; - - public SqlPersistenceEngine( - IConnectionFactory connectionFactory, - ISqlDialect dialect, - ISerialize serializer, - TransactionScopeOption scopeOption, - int pageSize) - { - if (connectionFactory == null) - throw new ArgumentNullException("connectionFactory"); - - if (dialect == null) - throw new ArgumentNullException("dialect"); - - if (serializer == null) - throw new ArgumentNullException("serializer"); - - if (pageSize < 0) - throw new ArgumentException("pageSize"); - - this.connectionFactory = connectionFactory; - this.dialect = dialect; - this.serializer = serializer; - this.scopeOption = scopeOption; - this.pageSize = pageSize; - - Logger.Debug(Messages.UsingScope, this.scopeOption.ToString()); - } - - public void Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); - } - protected virtual void Dispose(bool disposing) - { - if (!disposing || this.disposed) - return; - - Logger.Debug(Messages.ShuttingDownPersistence); - this.disposed = true; - } - - public virtual void Initialize() - { - if (Interlocked.Increment(ref this.initialized) > 1) - return; - - Logger.Debug(Messages.InitializingStorage); - this.ExecuteCommand(Guid.Empty, statement => - statement.ExecuteWithoutExceptions(this.dialect.InitializeStorage)); - } - - public virtual IEnumerable GetFrom(Guid streamId, int minRevision, int maxRevision) - { - Logger.Debug(Messages.GettingAllCommitsBetween, streamId, minRevision, maxRevision); - return this.ExecuteQuery(streamId, query => - { - var statement = this.dialect.GetCommitsFromStartingRevision; - query.AddParameter(this.dialect.StreamId, streamId); - query.AddParameter(this.dialect.StreamRevision, minRevision); - query.AddParameter(this.dialect.MaxStreamRevision, maxRevision); - query.AddParameter(this.dialect.CommitSequence, 0); - return query.ExecutePagedQuery(statement, - (q, r) => q.SetParameter(this.dialect.CommitSequence, r.CommitSequence())) - .Select(x => x.GetCommit(this.serializer)); - }); - } - - public virtual IEnumerable GetFrom(DateTime start) - { - start = start < EpochTime ? EpochTime : start; - - Logger.Debug(Messages.GettingAllCommitsFrom, start); - return this.ExecuteQuery(Guid.Empty, query => - { - var statement = this.dialect.GetCommitsFromInstant; - query.AddParameter(this.dialect.CommitStamp, start); - return query.ExecutePagedQuery(statement, (q, r) => { }) - .Select(x => x.GetCommit(this.serializer)); - }); - } - - public virtual IEnumerable GetFromTo(DateTime start, DateTime end) - { - start = start < EpochTime ? EpochTime : start; - end = end < EpochTime ? EpochTime : end; - - Logger.Debug(Messages.GettingAllCommitsFromTo, start, end); - return this.ExecuteQuery(Guid.Empty, query => - { - var statement = this.dialect.GetCommitsFromToInstant; - query.AddParameter(this.dialect.CommitStampStart, start); - query.AddParameter(this.dialect.CommitStampEnd, end); - return query.ExecutePagedQuery(statement, (q, r) => { }) - .Select(x => x.GetCommit(this.serializer)); - }); - } - - public virtual void Commit(Commit attempt) - { - try - { - this.PersistCommit(attempt); - Logger.Debug(Messages.CommitPersisted, attempt.CommitId); - } - catch (Exception e) - { - if (!(e is UniqueKeyViolationException)) - throw; - - if (this.DetectDuplicate(attempt)) - { - Logger.Info(Messages.DuplicateCommit); - throw new DuplicateCommitException(e.Message, e); - } - - Logger.Info(Messages.ConcurrentWriteDetected); - throw new ConcurrencyException(e.Message, e); - } - } - private void PersistCommit(Commit attempt) - { - Logger.Debug(Messages.AttemptingToCommit, - attempt.Events.Count, attempt.StreamId, attempt.CommitSequence); - - this.ExecuteCommand(attempt.StreamId, cmd => - { - cmd.AddParameter(this.dialect.StreamId, attempt.StreamId); - cmd.AddParameter(this.dialect.StreamRevision, attempt.StreamRevision); - cmd.AddParameter(this.dialect.Items, attempt.Events.Count); - cmd.AddParameter(this.dialect.CommitId, attempt.CommitId); - cmd.AddParameter(this.dialect.CommitSequence, attempt.CommitSequence); - cmd.AddParameter(this.dialect.CommitStamp, attempt.CommitStamp); - cmd.AddParameter(this.dialect.Headers, this.serializer.Serialize(attempt.Headers)); - cmd.AddParameter(this.dialect.Payload, this.serializer.Serialize(attempt.Events)); - return cmd.ExecuteNonQuery(this.dialect.PersistCommit); - }); - } - private bool DetectDuplicate(Commit attempt) - { - return this.ExecuteCommand(attempt.StreamId, cmd => - { - cmd.AddParameter(this.dialect.StreamId, attempt.StreamId); - cmd.AddParameter(this.dialect.CommitId, attempt.CommitId); - cmd.AddParameter(this.dialect.CommitSequence, attempt.CommitSequence); - var value = cmd.ExecuteScalar(this.dialect.DuplicateCommit); - return (value is long ? (long)value : (int)value) > 0; - }); - } - - public virtual IEnumerable GetUndispatchedCommits() - { - Logger.Debug(Messages.GettingUndispatchedCommits); - return this.ExecuteQuery(Guid.Empty, query => - query.ExecutePagedQuery(this.dialect.GetUndispatchedCommits, (q, r) => { })) - .Select(x => x.GetCommit(this.serializer)) - .ToArray(); // avoid paging - } - public virtual void MarkCommitAsDispatched(Commit commit) - { - Logger.Debug(Messages.MarkingCommitAsDispatched, commit.CommitId); - this.ExecuteCommand(commit.StreamId, cmd => - { - cmd.AddParameter(this.dialect.StreamId, commit.StreamId); - cmd.AddParameter(this.dialect.CommitSequence, commit.CommitSequence); - return cmd.ExecuteWithoutExceptions(this.dialect.MarkCommitAsDispatched); - }); - } - - public virtual IEnumerable GetStreamsToSnapshot(int maxThreshold) - { - Logger.Debug(Messages.GettingStreamsToSnapshot); - return this.ExecuteQuery(Guid.Empty, query => - { - var statement = this.dialect.GetStreamsRequiringSnapshots; - query.AddParameter(this.dialect.StreamId, Guid.Empty); - query.AddParameter(this.dialect.Threshold, maxThreshold); - return query.ExecutePagedQuery(statement, - (q, s) => q.SetParameter(this.dialect.StreamId, this.dialect.CoalesceParameterValue(s.StreamId()))) - .Select(x => x.GetStreamToSnapshot()); - }); - } - public virtual Snapshot GetSnapshot(Guid streamId, int maxRevision) - { - Logger.Debug(Messages.GettingRevision, streamId, maxRevision); - return this.ExecuteQuery(streamId, query => - { - var statement = this.dialect.GetSnapshot; - query.AddParameter(this.dialect.StreamId, streamId); - query.AddParameter(this.dialect.StreamRevision, maxRevision); - return query.ExecuteWithQuery(statement).Select(x => x.GetSnapshot(this.serializer)); - }).FirstOrDefault(); - } - public virtual bool AddSnapshot(Snapshot snapshot) - { - Logger.Debug(Messages.AddingSnapshot, snapshot.StreamId, snapshot.StreamRevision); - return this.ExecuteCommand(snapshot.StreamId, cmd => - { - cmd.AddParameter(this.dialect.StreamId, snapshot.StreamId); - cmd.AddParameter(this.dialect.StreamRevision, snapshot.StreamRevision); - cmd.AddParameter(this.dialect.Payload, this.serializer.Serialize(snapshot.Payload)); - return cmd.ExecuteWithoutExceptions(this.dialect.AppendSnapshotToCommit); - }) > 0; - } - - public virtual void Purge() - { - Logger.Warn(Messages.PurgingStorage); - this.ExecuteCommand(Guid.Empty, cmd => - cmd.ExecuteNonQuery(this.dialect.PurgeStorage)); - } - - protected virtual IEnumerable ExecuteQuery(Guid streamId, Func> query) - { - this.ThrowWhenDisposed(); - - var scope = this.OpenQueryScope(); - IDbConnection connection = null; - IDbTransaction transaction = null; - IDbStatement statement = null; - - try - { - connection = this.connectionFactory.OpenReplica(streamId); - transaction = this.dialect.OpenTransaction(connection); - statement = this.dialect.BuildStatement(scope, connection, transaction); - statement.PageSize = this.pageSize; - - Logger.Verbose(Messages.ExecutingQuery); - return query(statement); - } - catch (Exception e) - { - if (statement != null) - statement.Dispose(); - if (transaction != null) - transaction.Dispose(); - if (connection != null) - connection.Dispose(); - if (scope != null) - scope.Dispose(); - - Logger.Debug(Messages.StorageThrewException, e.GetType()); - if (e is StorageUnavailableException) - throw; - - throw new StorageException(e.Message, e); - } - } - protected virtual TransactionScope OpenQueryScope() - { - return this.OpenCommandScope() ?? new TransactionScope(TransactionScopeOption.Suppress); - } - private void ThrowWhenDisposed() - { - if (!this.disposed) - return; - - Logger.Warn(Messages.AlreadyDisposed); - throw new ObjectDisposedException(Messages.AlreadyDisposed); - } - - protected virtual T ExecuteCommand(Guid streamId, Func command) - { - this.ThrowWhenDisposed(); - - using (var scope = this.OpenCommandScope()) - using (var connection = this.connectionFactory.OpenMaster(streamId)) - using (var transaction = this.dialect.OpenTransaction(connection)) - using (var statement = this.dialect.BuildStatement(scope, connection, transaction)) - { - try - { - Logger.Verbose(Messages.ExecutingCommand); - var rowsAffected = command(statement); - Logger.Verbose(Messages.CommandExecuted, rowsAffected); - - if (transaction != null) - transaction.Commit(); - - if (scope != null) - scope.Complete(); - - return rowsAffected; - } - catch (Exception e) - { - Logger.Debug(Messages.StorageThrewException, e.GetType()); - if (!RecoverableException(e)) - throw new StorageException(e.Message, e); - - Logger.Info(Messages.RecoverableExceptionCompletesScope); - if (scope != null) - scope.Complete(); - - throw; - } - } - } - protected virtual TransactionScope OpenCommandScope() - { - return new TransactionScope(this.scopeOption); - } - private static bool RecoverableException(Exception e) - { - return e is UniqueKeyViolationException || e is StorageUnavailableException; - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Persistence.SqlPersistence/SqlPersistenceFactory.cs b/src/proj/EventStore.Persistence.SqlPersistence/SqlPersistenceFactory.cs deleted file mode 100644 index fc59b6c8b..000000000 --- a/src/proj/EventStore.Persistence.SqlPersistence/SqlPersistenceFactory.cs +++ /dev/null @@ -1,108 +0,0 @@ -namespace EventStore.Persistence.SqlPersistence -{ - using System; - using System.Configuration; - using System.Transactions; - using Serialization; - using SqlDialects; - - public class SqlPersistenceFactory : IPersistenceFactory - { - private const int DefaultPageSize = 128; - private readonly IConnectionFactory connectionFactory; - private readonly ISqlDialect dialect; - private readonly ISerialize serializer; - private readonly TransactionScopeOption scopeOption; - - public SqlPersistenceFactory(string connectionName, ISerialize serializer) - : this(connectionName, serializer, null) - { - } - public SqlPersistenceFactory(string connectionName, ISerialize serializer, ISqlDialect dialect) - : this(serializer, TransactionScopeOption.Suppress, DefaultPageSize) - { - this.connectionFactory = new ConfigurationConnectionFactory(connectionName); - this.dialect = dialect ?? ResolveDialect(new ConfigurationConnectionFactory(connectionName).Settings); - } - public SqlPersistenceFactory(IConnectionFactory factory, ISerialize serializer, ISqlDialect dialect) - : this(factory, serializer, dialect, TransactionScopeOption.Suppress, DefaultPageSize) - { - } - public SqlPersistenceFactory( - IConnectionFactory factory, - ISerialize serializer, - ISqlDialect dialect, - TransactionScopeOption scopeOption, - int pageSize) - : this(serializer, scopeOption, pageSize) - { - if (dialect == null) - throw new ArgumentNullException("dialect"); - - this.connectionFactory = factory; - this.dialect = dialect; - } - private SqlPersistenceFactory(ISerialize serializer, TransactionScopeOption scopeOption, int pageSize) - { - this.serializer = serializer; - this.scopeOption = scopeOption; - - this.PageSize = pageSize; - } - - protected virtual IConnectionFactory ConnectionFactory - { - get { return this.connectionFactory; } - } - protected virtual ISqlDialect Dialect - { - get { return this.dialect; } - } - protected virtual ISerialize Serializer - { - get { return this.serializer; } - } - protected int PageSize { get; set; } - - public virtual IPersistStreams Build() - { - return new SqlPersistenceEngine( - this.ConnectionFactory, this.Dialect, this.Serializer, this.scopeOption, this.PageSize); - } - - protected static ISqlDialect ResolveDialect(ConnectionStringSettings settings) - { - var connectionString = settings.ConnectionString.ToUpperInvariant(); - var providerName = settings.ProviderName.ToUpperInvariant(); - - if (providerName.Contains("MYSQL")) - return new MySqlDialect(); - - if (providerName.Contains("SQLITE")) - return new SqliteDialect(); - - if (providerName.Contains("SQLSERVERCE") || connectionString.Contains(".SDF")) - return new SqlCeDialect(); - - if (providerName.Contains("FIREBIRD")) - return new FirebirdSqlDialect(); - - if (providerName.Contains("POSTGRES") || providerName.Contains("NPGSQL")) - return new PostgreSqlDialect(); - - if (providerName.Contains("FIREBIRD")) - return new FirebirdSqlDialect(); - - if (providerName.Contains("OLEDB") && connectionString.Contains("MICROSOFT.JET")) - return new AccessDialect(); - - if (providerName.Contains("ORACLE") && providerName.Contains("DATAACCESS")) - return new OracleNativeDialect(); - - if (providerName == "SYSTEM.DATA.ORACLECLIENT") - return new OracleNativeDialect(); - - return new MsSqlDialect(); - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Persistence.SqlPersistence/StreamHeadExtensions.cs b/src/proj/EventStore.Persistence.SqlPersistence/StreamHeadExtensions.cs deleted file mode 100644 index e3f0781e8..000000000 --- a/src/proj/EventStore.Persistence.SqlPersistence/StreamHeadExtensions.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace EventStore.Persistence.SqlPersistence -{ - using System.Data; - using Persistence; - - internal static class StreamHeadExtensions - { - private const int StreamIdIndex = 0; - private const int HeadRevisionIndex = 1; - private const int SnapshotRevisionIndex = 2; - - public static StreamHead GetStreamToSnapshot(this IDataRecord record) - { - return new StreamHead( - record[StreamIdIndex].ToGuid(), - record[HeadRevisionIndex].ToInt(), - record[SnapshotRevisionIndex].ToInt()); - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Persistence.SqlPersistence/ThreadScope.cs b/src/proj/EventStore.Persistence.SqlPersistence/ThreadScope.cs deleted file mode 100644 index 5a2df7980..000000000 --- a/src/proj/EventStore.Persistence.SqlPersistence/ThreadScope.cs +++ /dev/null @@ -1,79 +0,0 @@ -namespace EventStore.Persistence.SqlPersistence -{ - using System; - using System.Threading; - using System.Web; - using Logging; - - public class ThreadScope : IDisposable where T : class - { - private readonly ILog logger = LogFactory.BuildLogger(typeof(ThreadScope)); - private readonly HttpContext context = HttpContext.Current; - private readonly string threadKey; - private readonly T current; - private readonly bool rootScope; - private bool disposed; - - public ThreadScope(string key, Func factory) - { - this.threadKey = typeof(ThreadScope).Name + ":[{0}]".FormatWith(key ?? string.Empty); - - var parent = this.Load(); - this.rootScope = parent == null; - this.logger.Debug(Messages.OpeningThreadScope, this.threadKey, this.rootScope); - - this.current = parent ?? factory(); - - if (this.current == null) - throw new ArgumentException(Messages.BadFactoryResult, "factory"); - - if (this.rootScope) - this.Store(this.current); - } - - public void Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); - } - protected virtual void Dispose(bool disposing) - { - if (!disposing || this.disposed) - return; - - this.logger.Debug(Messages.DisposingThreadScope, this.rootScope); - this.disposed = true; - if (!this.rootScope) - return; - - this.logger.Verbose(Messages.CleaningRootThreadScope); - this.Store(null); - - var resource = this.current as IDisposable; - if (resource == null) - return; - - this.logger.Verbose(Messages.DisposingRootThreadScopeResources); - resource.Dispose(); - } - - private T Load() - { - if (this.context != null) - return this.context.Items[this.threadKey] as T; - - return Thread.GetData(Thread.GetNamedDataSlot(this.threadKey)) as T; - } - private void Store(T value) - { - if (this.context != null) - this.context.Items[this.threadKey] = value; - else - Thread.SetData(Thread.GetNamedDataSlot(this.threadKey), value); - } - public T Current - { - get { return this.current; } - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Persistence.SqlPersistence/UniqueKeyViolationException.cs b/src/proj/EventStore.Persistence.SqlPersistence/UniqueKeyViolationException.cs deleted file mode 100644 index dcbd00739..000000000 --- a/src/proj/EventStore.Persistence.SqlPersistence/UniqueKeyViolationException.cs +++ /dev/null @@ -1,48 +0,0 @@ -namespace EventStore.Persistence.SqlPersistence -{ - using System; - using System.Runtime.Serialization; - - /// - /// Indicates that a unique constraint or duplicate key violation occurred. - /// - [Serializable] - public class UniqueKeyViolationException : Exception - { - /// - /// Initializes a new instance of the UniqueKeyViolationException class. - /// - public UniqueKeyViolationException() - { - } - - /// - /// Initializes a new instance of the UniqueKeyViolationException class. - /// - /// The message that describes the error. - public UniqueKeyViolationException(string message) - : base(message) - { - } - - /// - /// Initializes a new instance of the UniqueKeyViolationException class. - /// - /// The message that describes the error. - /// The message that is the cause of the current exception. - public UniqueKeyViolationException(string message, Exception innerException) - : base(message, innerException) - { - } - - /// - /// Initializes a new instance of the UniqueKeyViolationException class. - /// - /// The SerializationInfo that holds the serialized object data of the exception being thrown. - /// The StreamingContext that contains contextual information about the source or destination. - protected UniqueKeyViolationException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Serialization.Json.Wireup/EventStore.Serialization.Json.Wireup.csproj b/src/proj/EventStore.Serialization.Json.Wireup/EventStore.Serialization.Json.Wireup.csproj deleted file mode 100644 index 58918d1c3..000000000 --- a/src/proj/EventStore.Serialization.Json.Wireup/EventStore.Serialization.Json.Wireup.csproj +++ /dev/null @@ -1,74 +0,0 @@ - - - - Debug - AnyCPU - 8.0.30703 - 2.0 - {DEFFE0C3-2988-4C58-9E36-1302842FFDBD} - Library - Properties - EventStore - EventStore.Serialization.Json.Wireup - v4.0 - 512 - true - ..\..\EventStore.snk - ..\..\..\src\ - true - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - Properties\GlobalAssemblyInfo.cs - - - Properties\GlobalSuppressions.cs - - - Properties\VersionAssemblyInfo.cs - - - - - - - Properties\CustomDictionary.xml - - - - - {CFD895BD-7CB2-4811-A6FA-1851DF769B67} - EventStore.Serialization.Json - - - ..\..\..\output\bin\EventStore.dll - - - {03946843-F343-419C-88EF-3E446D08DFA6} - EventStore - - - {421664DB-C18D-4499-ABC1-C9086D525F80} - EventStore.Wireup - - - - - \ No newline at end of file diff --git a/src/proj/EventStore.Serialization.Json.Wireup/Properties/AssemblyInfo.cs b/src/proj/EventStore.Serialization.Json.Wireup/Properties/AssemblyInfo.cs deleted file mode 100644 index cd01007a5..000000000 --- a/src/proj/EventStore.Serialization.Json.Wireup/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,6 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -[assembly: AssemblyTitle("EventStore.Serialization.Json.Wireup")] -[assembly: AssemblyDescription("")] -[assembly: Guid("845cf3c7-c303-473d-8c9c-5a5f491615dd")] \ No newline at end of file diff --git a/src/proj/EventStore.Serialization.Json.Wireup/SerializationWireupExtensions.cs b/src/proj/EventStore.Serialization.Json.Wireup/SerializationWireupExtensions.cs deleted file mode 100644 index a84286097..000000000 --- a/src/proj/EventStore.Serialization.Json.Wireup/SerializationWireupExtensions.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace EventStore -{ - using Serialization; - - public static class WireupExtensions - { - public static SerializationWireup UsingJsonSerialization(this PersistenceWireup wireup) - { - return wireup.UsingCustomSerialization(new JsonSerializer()); - } - - public static SerializationWireup UsingBsonSerialization(this PersistenceWireup wireup) - { - return wireup.UsingCustomSerialization(new BsonSerializer()); - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Serialization.Json/BsonSerializer.cs b/src/proj/EventStore.Serialization.Json/BsonSerializer.cs deleted file mode 100644 index d235eae76..000000000 --- a/src/proj/EventStore.Serialization.Json/BsonSerializer.cs +++ /dev/null @@ -1,38 +0,0 @@ -namespace EventStore.Serialization -{ - using System; - using System.Collections; - using System.IO; - using Logging; - using Newtonsoft.Json.Bson; - - public class BsonSerializer : JsonSerializer - { - private static readonly ILog Logger = LogFactory.BuildLogger(typeof(BsonSerializer)); - - public BsonSerializer(params Type[] knownTypes) - : base(knownTypes) - { - } - - public override void Serialize(Stream output, T graph) - { - var writer = new BsonWriter(output) { DateTimeKindHandling = DateTimeKind.Utc }; - this.Serialize(writer, graph); - } - public override T Deserialize(Stream input) - { - var reader = new BsonReader(input, IsArray(typeof(T)), DateTimeKind.Utc); - return this.Deserialize(reader); - } - private static bool IsArray(Type type) - { - var array = typeof(IEnumerable).IsAssignableFrom(type) - && !typeof(IDictionary).IsAssignableFrom(type); - - Logger.Verbose(Messages.TypeIsArray, type, array); - - return array; - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Serialization.Json/EventStore.Serialization.Json.csproj b/src/proj/EventStore.Serialization.Json/EventStore.Serialization.Json.csproj deleted file mode 100644 index 73376f0d0..000000000 --- a/src/proj/EventStore.Serialization.Json/EventStore.Serialization.Json.csproj +++ /dev/null @@ -1,87 +0,0 @@ - - - - Debug - AnyCPU - 8.0.30703 - 2.0 - {CFD895BD-7CB2-4811-A6FA-1851DF769B67} - Library - Properties - EventStore.Serialization - EventStore.Serialization.Json - v4.0 - 512 - true - ..\..\EventStore.snk - ..\..\..\src\ - true - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - Properties\GlobalAssemblyInfo.cs - - - Properties\GlobalSuppressions.cs - - - Properties\VersionAssemblyInfo.cs - - - - - Messages.resx - True - True - - - - - - Properties\CustomDictionary.xml - - - - - ..\..\..\output\bin\EventStore.dll - - - {03946843-F343-419C-88EF-3E446D08DFA6} - EventStore - - - False - ..\..\packages\Newtonsoft.Json.4.5.7\lib\net40\Newtonsoft.Json.dll - - - - - - ResXFileCodeGenerator - Messages.Designer.cs - Designer - - - - - - - - \ No newline at end of file diff --git a/src/proj/EventStore.Serialization.Json/JsonSerializer.cs b/src/proj/EventStore.Serialization.Json/JsonSerializer.cs deleted file mode 100644 index 737a3f27e..000000000 --- a/src/proj/EventStore.Serialization.Json/JsonSerializer.cs +++ /dev/null @@ -1,82 +0,0 @@ -namespace EventStore.Serialization -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Text; - using Logging; - using Newtonsoft.Json; - using JsonNetSerializer = Newtonsoft.Json.JsonSerializer; - - public class JsonSerializer : ISerialize - { - private static readonly ILog Logger = LogFactory.BuildLogger(typeof(JsonSerializer)); - private readonly JsonNetSerializer untypedSerializer = new JsonNetSerializer - { - TypeNameHandling = TypeNameHandling.Auto, - DefaultValueHandling = DefaultValueHandling.Ignore, - NullValueHandling = NullValueHandling.Ignore - }; - private readonly JsonNetSerializer typedSerializer = new JsonNetSerializer - { - TypeNameHandling = TypeNameHandling.All, - DefaultValueHandling = DefaultValueHandling.Ignore, - NullValueHandling = NullValueHandling.Ignore - }; - private readonly IEnumerable knownTypes = new[] - { - typeof(List), - typeof(Dictionary) - }; - - public JsonSerializer(params Type[] knownTypes) - { - if (knownTypes != null && knownTypes.Length == 0) - knownTypes = null; - - this.knownTypes = knownTypes ?? this.knownTypes; - - foreach (var type in this.knownTypes) - Logger.Debug(Messages.RegisteringKnownType, type); - } - - public virtual void Serialize(Stream output, T graph) - { - Logger.Verbose(Messages.SerializingGraph, typeof(T)); - using (var streamWriter = new StreamWriter(output, Encoding.UTF8)) - this.Serialize(new JsonTextWriter(streamWriter), graph); - } - protected virtual void Serialize(JsonWriter writer, object graph) - { - using (writer) - this.GetSerializer(graph.GetType()).Serialize(writer, graph); - } - - public virtual T Deserialize(Stream input) - { - Logger.Verbose(Messages.DeserializingStream, typeof(T)); - using (var streamReader = new StreamReader(input, Encoding.UTF8)) - return this.Deserialize(new JsonTextReader(streamReader)); - } - protected virtual T Deserialize(JsonReader reader) - { - var type = typeof(T); - - using (reader) - return (T)this.GetSerializer(type).Deserialize(reader, type); - } - - protected virtual JsonNetSerializer GetSerializer(Type typeToSerialize) - { - if (this.knownTypes.Contains(typeToSerialize)) - { - Logger.Verbose(Messages.UsingUntypedSerializer, typeToSerialize); - return this.untypedSerializer; - } - - Logger.Verbose(Messages.UsingTypedSerializer, typeToSerialize); - return this.typedSerializer; - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Serialization.Json/Properties/AssemblyInfo.cs b/src/proj/EventStore.Serialization.Json/Properties/AssemblyInfo.cs deleted file mode 100644 index 56b06e2f6..000000000 --- a/src/proj/EventStore.Serialization.Json/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,6 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -[assembly: AssemblyTitle("EventStore.Serialization.Json")] -[assembly: AssemblyDescription("")] -[assembly: Guid("2cbd6d88-bdd6-400e-ba7a-3988fbb190fd")] \ No newline at end of file diff --git a/src/proj/EventStore.Serialization.Json/packages.config b/src/proj/EventStore.Serialization.Json/packages.config deleted file mode 100644 index 9385fc2f9..000000000 --- a/src/proj/EventStore.Serialization.Json/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/src/proj/EventStore.Serialization.ServiceStack.Wireup/EventStore.Serialization.ServiceStack.Wireup.csproj b/src/proj/EventStore.Serialization.ServiceStack.Wireup/EventStore.Serialization.ServiceStack.Wireup.csproj deleted file mode 100644 index 45dd62dce..000000000 --- a/src/proj/EventStore.Serialization.ServiceStack.Wireup/EventStore.Serialization.ServiceStack.Wireup.csproj +++ /dev/null @@ -1,74 +0,0 @@ - - - - Debug - AnyCPU - 8.0.30703 - 2.0 - {33B78974-7867-4C10-90AD-F31352BF0EEC} - Library - Properties - EventStore - EventStore.Serialization.ServiceStack.Wireup - v4.0 - 512 - false - ..\..\EventStore.snk - ..\..\..\src\ - true - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - Properties\GlobalAssemblyInfo.cs - - - Properties\GlobalSuppressions.cs - - - Properties\VersionAssemblyInfo.cs - - - - - - - Properties\CustomDictionary.xml - - - - - - ..\..\..\output\bin\EventStore.dll - - - {2BA8B905-65D5-4BBA-A76B-58EC49F26018} - EventStore.Serialization.ServiceStack - - - {03946843-F343-419C-88EF-3E446D08DFA6} - EventStore - - - {421664DB-C18D-4499-ABC1-C9086D525F80} - EventStore.Wireup - - - - \ No newline at end of file diff --git a/src/proj/EventStore.Serialization.ServiceStack.Wireup/Properties/AssemblyInfo.cs b/src/proj/EventStore.Serialization.ServiceStack.Wireup/Properties/AssemblyInfo.cs deleted file mode 100644 index 71ebbdfdc..000000000 --- a/src/proj/EventStore.Serialization.ServiceStack.Wireup/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using System.Reflection; -using System.Runtime.InteropServices; - -[assembly: AssemblyTitle("EventStore.Serialization.ServiceStack.Wireup")] -[assembly: AssemblyDescription("")] -[assembly: Guid("27629f6b-6116-4477-8de9-105ccfaa28a7")] - -[assembly: SuppressMessage("Microsoft.Design", "CA2210:AssembliesShouldHaveValidStrongNames", - Justification = "ServiceStack.Text is not signed, therefore this assembly cannot be signed.")] \ No newline at end of file diff --git a/src/proj/EventStore.Serialization.ServiceStack.Wireup/SerializationWireupExtensions.cs b/src/proj/EventStore.Serialization.ServiceStack.Wireup/SerializationWireupExtensions.cs deleted file mode 100644 index 07468ec1a..000000000 --- a/src/proj/EventStore.Serialization.ServiceStack.Wireup/SerializationWireupExtensions.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace EventStore -{ - using Serialization; - - public static class WireupExtensions - { - public static SerializationWireup UsingServiceStackJsonSerialization(this PersistenceWireup wireup) - { - return wireup.UsingCustomSerialization(new ServiceStackJsonSerializer()); - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Serialization.ServiceStack/EventStore.Serialization.ServiceStack.csproj b/src/proj/EventStore.Serialization.ServiceStack/EventStore.Serialization.ServiceStack.csproj deleted file mode 100644 index 5d48beef3..000000000 --- a/src/proj/EventStore.Serialization.ServiceStack/EventStore.Serialization.ServiceStack.csproj +++ /dev/null @@ -1,86 +0,0 @@ - - - - Debug - AnyCPU - 8.0.30703 - 2.0 - {2BA8B905-65D5-4BBA-A76B-58EC49F26018} - Library - Properties - EventStore.Serialization - EventStore.Serialization.ServiceStack - v4.0 - 512 - false - ..\..\EventStore.snk - ..\..\..\src\ - true - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - Properties\GlobalAssemblyInfo.cs - - - Properties\GlobalSuppressions.cs - - - Properties\VersionAssemblyInfo.cs - - - Messages.resx - True - True - - - - - - - Properties\CustomDictionary.xml - - - - - ..\..\..\output\bin\EventStore.dll - - - {03946843-F343-419C-88EF-3E446D08DFA6} - EventStore - - - False - ..\..\packages\ServiceStack.Text.3.9.4\lib\net35\ServiceStack.Text.dll - - - - - - ResXFileCodeGenerator - Messages.Designer.cs - Designer - - - - - - - - \ No newline at end of file diff --git a/src/proj/EventStore.Serialization.ServiceStack/Properties/AssemblyInfo.cs b/src/proj/EventStore.Serialization.ServiceStack/Properties/AssemblyInfo.cs deleted file mode 100644 index c892dc210..000000000 --- a/src/proj/EventStore.Serialization.ServiceStack/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using System.Reflection; -using System.Runtime.InteropServices; - -[assembly: AssemblyTitle("EventStore.Serialization.ServiceStack")] -[assembly: AssemblyDescription("")] -[assembly: Guid("1fcca9eb-2abc-4cf3-bb59-cad2f0485782")] - -[assembly: SuppressMessage("Microsoft.Design", "CA2210:AssembliesShouldHaveValidStrongNames", - Justification = "ServiceStack.Text is not signed, therefore this assembly cannot be signed.")] \ No newline at end of file diff --git a/src/proj/EventStore.Serialization.ServiceStack/ServiceStackJsonSerializer.cs b/src/proj/EventStore.Serialization.ServiceStack/ServiceStackJsonSerializer.cs deleted file mode 100644 index 8f696648e..000000000 --- a/src/proj/EventStore.Serialization.ServiceStack/ServiceStackJsonSerializer.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace EventStore.Serialization -{ - using System.IO; - using Logging; - using ServiceStack.Text; - - public class ServiceStackJsonSerializer : ISerialize - { - private static readonly ILog Logger = LogFactory.BuildLogger(typeof(ServiceStackJsonSerializer)); - - public void Serialize(Stream output, T graph) - { - Logger.Verbose(Messages.SerializingGraph, typeof(T)); - JsonSerializer.SerializeToStream(graph, output); - } - public T Deserialize(Stream input) - { - Logger.Verbose(Messages.DeserializingStream, typeof(T)); - return JsonSerializer.DeserializeFromStream(input); - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Serialization.ServiceStack/packages.config b/src/proj/EventStore.Serialization.ServiceStack/packages.config deleted file mode 100644 index b902cee18..000000000 --- a/src/proj/EventStore.Serialization.ServiceStack/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/src/proj/EventStore.Serialization/BinarySerializer.cs b/src/proj/EventStore.Serialization/BinarySerializer.cs deleted file mode 100644 index a55f923ac..000000000 --- a/src/proj/EventStore.Serialization/BinarySerializer.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace EventStore.Serialization -{ - using System.IO; - using System.Runtime.Serialization; - using System.Runtime.Serialization.Formatters.Binary; - using Logging; - - public class BinarySerializer : ISerialize - { - private static readonly ILog Logger = LogFactory.BuildLogger(typeof(BinarySerializer)); - private readonly IFormatter formatter = new BinaryFormatter(); - - public virtual void Serialize(Stream output, T graph) - { - Logger.Verbose(Messages.SerializingGraph, typeof(T)); - this.formatter.Serialize(output, graph); - } - public virtual T Deserialize(Stream input) - { - Logger.Verbose(Messages.DeserializingStream, typeof(T)); - return (T)this.formatter.Deserialize(input); - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Serialization/ByteStreamDocumentSerializer.cs b/src/proj/EventStore.Serialization/ByteStreamDocumentSerializer.cs deleted file mode 100644 index 2b7120933..000000000 --- a/src/proj/EventStore.Serialization/ByteStreamDocumentSerializer.cs +++ /dev/null @@ -1,44 +0,0 @@ -namespace EventStore.Serialization -{ - using System; - using Logging; - - public class ByteStreamDocumentSerializer : IDocumentSerializer - { - private static readonly ILog Logger = LogFactory.BuildLogger(typeof(ByteStreamDocumentSerializer)); - private readonly ISerialize serializer; - - public ByteStreamDocumentSerializer(ISerialize serializer) - { - this.serializer = serializer; - } - - public object Serialize(T graph) - { - Logger.Verbose(Messages.SerializingGraph, typeof(T)); - return this.serializer.Serialize(graph); - } - public T Deserialize(object document) - { - Logger.Verbose(Messages.DeserializingStream, typeof(T)); - var bytes = FromBase64(document as string) ?? document as byte[]; - return this.serializer.Deserialize(bytes); - } - private static byte[] FromBase64(string value) - { - if (string.IsNullOrEmpty(value)) - return null; - - Logger.Verbose(Messages.InspectingTextStream); - - try - { - return Convert.FromBase64String(value); - } - catch (FormatException) - { - return null; - } - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Serialization/DocumentObjectSerializer.cs b/src/proj/EventStore.Serialization/DocumentObjectSerializer.cs deleted file mode 100644 index fe0349505..000000000 --- a/src/proj/EventStore.Serialization/DocumentObjectSerializer.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace EventStore.Serialization -{ - using Logging; - - public class DocumentObjectSerializer : IDocumentSerializer - { - private static readonly ILog Logger = LogFactory.BuildLogger(typeof(DocumentObjectSerializer)); - - public object Serialize(T graph) - { - Logger.Verbose(Messages.SerializingGraph, typeof(T)); - return graph; - } - public T Deserialize(object document) - { - Logger.Verbose(Messages.DeserializingStream, typeof(T)); - return (T)document; - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Serialization/EventStore.Serialization.csproj b/src/proj/EventStore.Serialization/EventStore.Serialization.csproj deleted file mode 100644 index a3e5d63e5..000000000 --- a/src/proj/EventStore.Serialization/EventStore.Serialization.csproj +++ /dev/null @@ -1,84 +0,0 @@ - - - - Debug - AnyCPU - 8.0.30703 - 2.0 - {A5BF4B86-26F6-418D-BE35-C6CC3A623D27} - Library - Properties - EventStore.Serialization - EventStore.Serialization - v4.0 - 512 - true - ..\..\EventStore.snk - ..\..\..\src\ - true - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - Properties\GlobalAssemblyInfo.cs - - - Properties\GlobalSuppressions.cs - - - Properties\VersionAssemblyInfo.cs - - - - - - - True - True - Messages.resx - - - - - - - - - - - - {03946843-F343-419C-88EF-3E446D08DFA6} - EventStore - - - - - Properties\CustomDictionary.xml - - - - - ResXFileCodeGenerator - Messages.Designer.cs - Designer - - - - - \ No newline at end of file diff --git a/src/proj/EventStore.Serialization/GzipSerializer.cs b/src/proj/EventStore.Serialization/GzipSerializer.cs deleted file mode 100644 index ff84d955c..000000000 --- a/src/proj/EventStore.Serialization/GzipSerializer.cs +++ /dev/null @@ -1,30 +0,0 @@ -namespace EventStore.Serialization -{ - using System.IO; - using System.IO.Compression; - using Logging; - - public class GzipSerializer : ISerialize - { - private static readonly ILog Logger = LogFactory.BuildLogger(typeof(GzipSerializer)); - private readonly ISerialize inner; - - public GzipSerializer(ISerialize inner) - { - this.inner = inner; - } - - public virtual void Serialize(Stream output, T graph) - { - Logger.Verbose(Messages.SerializingGraph, typeof(T)); - using (var compress = new DeflateStream(output, CompressionMode.Compress, true)) - this.inner.Serialize(compress, graph); - } - public virtual T Deserialize(Stream input) - { - Logger.Verbose(Messages.DeserializingStream, typeof(T)); - using (var decompress = new DeflateStream(input, CompressionMode.Decompress, true)) - return this.inner.Deserialize(decompress); - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Serialization/IndisposableStream.cs b/src/proj/EventStore.Serialization/IndisposableStream.cs deleted file mode 100644 index d3f451c6d..000000000 --- a/src/proj/EventStore.Serialization/IndisposableStream.cs +++ /dev/null @@ -1,66 +0,0 @@ -namespace EventStore.Serialization -{ - using System.IO; - - internal class IndisposableStream : Stream - { - private readonly Stream stream; - - public IndisposableStream(Stream stream) - { - this.stream = stream; - } - - protected override void Dispose(bool disposing) - { - // no-op - } - public override void Close() - { - // no-op - } - - public override bool CanRead - { - get { return this.stream.CanRead; } - } - public override bool CanSeek - { - get { return this.stream.CanSeek; } - } - public override bool CanWrite - { - get { return this.stream.CanWrite; } - } - public override long Length - { - get { return this.stream.Length; } - } - public override long Position - { - get { return this.stream.Position; } - set { this.stream.Position = value; } - } - - public override void Flush() - { - this.stream.Flush(); - } - public override long Seek(long offset, SeekOrigin origin) - { - return this.stream.Seek(offset, origin); - } - public override void SetLength(long value) - { - this.stream.SetLength(value); - } - public override int Read(byte[] buffer, int offset, int count) - { - return this.stream.Read(buffer, offset, count); - } - public override void Write(byte[] buffer, int offset, int count) - { - this.stream.Write(buffer, offset, count); - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Serialization/Properties/AssemblyInfo.cs b/src/proj/EventStore.Serialization/Properties/AssemblyInfo.cs deleted file mode 100644 index e666c5225..000000000 --- a/src/proj/EventStore.Serialization/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,6 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -[assembly: AssemblyTitle("EventStore.Serialization")] -[assembly: AssemblyDescription("")] -[assembly: Guid("d479bc4a-87bb-4329-83ed-a04961dac22e")] \ No newline at end of file diff --git a/src/proj/EventStore.Serialization/RijndaelSerializer.cs b/src/proj/EventStore.Serialization/RijndaelSerializer.cs deleted file mode 100644 index 996c21b7c..000000000 --- a/src/proj/EventStore.Serialization/RijndaelSerializer.cs +++ /dev/null @@ -1,73 +0,0 @@ -namespace EventStore.Serialization -{ - using System; - using System.Collections; - using System.IO; - using System.Security.Cryptography; - using Logging; - - public class RijndaelSerializer : ISerialize - { - private static readonly ILog Logger = LogFactory.BuildLogger(typeof(RijndaelSerializer)); - private const int KeyLength = 16; // bytes - private readonly ISerialize inner; - private readonly byte[] encryptionKey; - - public RijndaelSerializer(ISerialize inner, byte[] encryptionKey) - { - if (!KeyIsValid(encryptionKey, KeyLength)) - throw new ArgumentException(Messages.InvalidKeyLength, "encryptionKey"); - - this.encryptionKey = encryptionKey; - this.inner = inner; - } - private static bool KeyIsValid(ICollection key, int length) - { - return key != null && key.Count == length; - } - - public virtual void Serialize(Stream output, T graph) - { - Logger.Verbose(Messages.SerializingGraph, typeof(T)); - - using (var rijndael = new RijndaelManaged()) - { - rijndael.Key = this.encryptionKey; - rijndael.Mode = CipherMode.CBC; - rijndael.GenerateIV(); - - using (var encryptor = rijndael.CreateEncryptor()) - using (var wrappedOutput = new IndisposableStream(output)) - using (var encryptionStream = new CryptoStream(wrappedOutput, encryptor, CryptoStreamMode.Write)) - { - wrappedOutput.Write(rijndael.IV, 0, rijndael.IV.Length); - this.inner.Serialize(encryptionStream, graph); - encryptionStream.Flush(); - encryptionStream.FlushFinalBlock(); - } - } - } - - public virtual T Deserialize(Stream input) - { - Logger.Verbose(Messages.DeserializingStream, typeof(T)); - - using (var rijndael = new RijndaelManaged()) - { - rijndael.Key = this.encryptionKey; - rijndael.IV = GetInitVectorFromStream(input, rijndael.IV.Length); - rijndael.Mode = CipherMode.CBC; - - using (var decryptor = rijndael.CreateDecryptor()) - using (var decryptedStream = new CryptoStream(input, decryptor, CryptoStreamMode.Read)) - return this.inner.Deserialize(decryptedStream); - } - } - private static byte[] GetInitVectorFromStream(Stream encrypted, int initVectorSizeInBytes) - { - var buffer = new byte[initVectorSizeInBytes]; - encrypted.Read(buffer, 0, buffer.Length); - return buffer; - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Wireup/AsynchronousDispatchSchedulerWireup.cs b/src/proj/EventStore.Wireup/AsynchronousDispatchSchedulerWireup.cs deleted file mode 100644 index 3b5dd52e4..000000000 --- a/src/proj/EventStore.Wireup/AsynchronousDispatchSchedulerWireup.cs +++ /dev/null @@ -1,32 +0,0 @@ -namespace EventStore -{ - using System.Transactions; - using Dispatcher; - using Logging; - using Persistence; - - public class AsynchronousDispatchSchedulerWireup : Wireup - { - private static readonly ILog Logger = LogFactory.BuildLogger(typeof(AsynchronousDispatchSchedulerWireup)); - - public AsynchronousDispatchSchedulerWireup(Wireup wireup, IDispatchCommits dispatcher) - : base(wireup) - { - var option = this.Container.Resolve(); - if (option != TransactionScopeOption.Suppress) - Logger.Warn(Messages.SynchronousDispatcherTwoPhaseCommits); - - Logger.Debug(Messages.AsyncDispatchSchedulerRegistered); - this.DispatchTo(dispatcher ?? new NullDispatcher()); - this.Container.Register(c => new AsynchronousDispatchScheduler( - c.Resolve(), c.Resolve())); - } - - public AsynchronousDispatchSchedulerWireup DispatchTo(IDispatchCommits instance) - { - Logger.Debug(Messages.DispatcherRegistered, instance.GetType()); - this.Container.Register(instance); - return this; - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Wireup/AsynchronousDispatchSchedulerWireupExtensions.cs b/src/proj/EventStore.Wireup/AsynchronousDispatchSchedulerWireupExtensions.cs deleted file mode 100644 index 4e3ce5136..000000000 --- a/src/proj/EventStore.Wireup/AsynchronousDispatchSchedulerWireupExtensions.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace EventStore -{ - using Dispatcher; - - public static class AsynchronousDispatchSchedulerWireupExtensions - { - public static AsynchronousDispatchSchedulerWireup UsingAsynchronousDispatchScheduler(this Wireup wireup) - { - return wireup.UsingAsynchronousDispatchScheduler(null); - } - public static AsynchronousDispatchSchedulerWireup UsingAsynchronousDispatchScheduler( - this Wireup wireup, IDispatchCommits dispatcher) - { - return new AsynchronousDispatchSchedulerWireup(wireup, dispatcher); - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Wireup/EventStore.Wireup.csproj b/src/proj/EventStore.Wireup/EventStore.Wireup.csproj deleted file mode 100644 index ca0dd94ef..000000000 --- a/src/proj/EventStore.Wireup/EventStore.Wireup.csproj +++ /dev/null @@ -1,105 +0,0 @@ - - - - Debug - AnyCPU - 8.0.30703 - 2.0 - {421664DB-C18D-4499-ABC1-C9086D525F80} - Library - Properties - EventStore - EventStore.Wireup - v4.0 - 512 - true - ..\..\EventStore.snk - ..\..\..\src\ - true - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - Properties\GlobalAssemblyInfo.cs - - - Properties\GlobalSuppressions.cs - - - Properties\VersionAssemblyInfo.cs - - - - - - - - True - True - Messages.resx - - - - - - - - - - - - - - - - {D6413244-42F5-4233-B347-D0A804B09CC9} - EventStore.Core - - - {DAFD3F38-33F3-4F53-BFEA-44E51BD1E8F1} - EventStore.Persistence.SqlPersistence - - - {A5BF4B86-26F6-418D-BE35-C6CC3A623D27} - EventStore.Serialization - - - {03946843-F343-419C-88EF-3E446D08DFA6} - EventStore - - - - - Designer - ResXFileCodeGenerator - Messages.Designer.cs - - - - - - - - - Properties\CustomDictionary.xml - - - - - \ No newline at end of file diff --git a/src/proj/EventStore.Wireup/EventUpconverterWireup.cs b/src/proj/EventStore.Wireup/EventUpconverterWireup.cs deleted file mode 100644 index 9e54455bf..000000000 --- a/src/proj/EventStore.Wireup/EventUpconverterWireup.cs +++ /dev/null @@ -1,88 +0,0 @@ -namespace EventStore -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Reflection; - using Conversion; - using Logging; - - public class EventUpconverterWireup : Wireup - { - private static readonly ILog Logger = LogFactory.BuildLogger(typeof(EventUpconverterWireup)); - private readonly IDictionary> registered = new Dictionary>(); - private readonly List assembliesToScan = new List(); - - public EventUpconverterWireup(Wireup wireup) : base(wireup) - { - Logger.Debug(Messages.EventUpconverterRegistered); - - this.Container.Register(c => - { - if (this.registered.Count > 0) - return new EventUpconverterPipelineHook(this.registered); - - if (!this.assembliesToScan.Any()) - this.assembliesToScan.AddRange(GetAllAssemblies()); - - var converters = GetConverters(this.assembliesToScan); - return new EventUpconverterPipelineHook(converters); - }); - } - private static IEnumerable GetAllAssemblies() - { - return Assembly.GetCallingAssembly() - .GetReferencedAssemblies() - .Select(Assembly.Load) - .Concat(new[] { Assembly.GetCallingAssembly() }); - } - private static IDictionary> GetConverters(IEnumerable toScan) - { - var c = from a in toScan - from t in a.GetTypes() - where !t.IsAbstract - let i = t.GetInterface(typeof(IUpconvertEvents<,>).FullName) - where i != null - let sourceType = i.GetGenericArguments().First() - let convertMethod = i.GetMethods(BindingFlags.Public | BindingFlags.Instance).First() - let instance = Activator.CreateInstance(t) - select new KeyValuePair>( - sourceType, e => convertMethod.Invoke(instance, new[] { e })); - try - { - return c.ToDictionary(x => x.Key, x => x.Value); - } - catch (ArgumentException e) - { - throw new MultipleConvertersFoundException(e.Message, e); - } - } - - public virtual EventUpconverterWireup WithConvertersFrom(params Assembly[] assemblies) - { - Logger.Debug(Messages.EventUpconvertersLoadedFrom, string.Concat(", ", assemblies)); - this.assembliesToScan.AddRange(assemblies); - return this; - } - public virtual EventUpconverterWireup WithConvertersFromAssemblyContaining(params Type[] converters) - { - var assemblies = converters.Select(c => c.Assembly).Distinct(); - Logger.Debug(Messages.EventUpconvertersLoadedFrom, string.Concat(", ", assemblies)); - this.assembliesToScan.AddRange(assemblies); - return this; - } - - public virtual EventUpconverterWireup AddConverter( - IUpconvertEvents converter) - where TSource : class - where TTarget : class - { - if (converter == null) - throw new ArgumentNullException("converter"); - - this.registered[typeof(TSource)] = @event => converter.Convert(@event as TSource); - - return this; - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Wireup/EventUpconverterWireupExtensions.cs b/src/proj/EventStore.Wireup/EventUpconverterWireupExtensions.cs deleted file mode 100644 index cf4c2cb50..000000000 --- a/src/proj/EventStore.Wireup/EventUpconverterWireupExtensions.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace EventStore -{ - public static class EventUpconverterWireupExtensions - { - public static EventUpconverterWireup UsingEventUpconversion(this Wireup wireup) - { - return new EventUpconverterWireup(wireup); - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Wireup/LoggingWireupExtensions.cs b/src/proj/EventStore.Wireup/LoggingWireupExtensions.cs deleted file mode 100644 index 574a96795..000000000 --- a/src/proj/EventStore.Wireup/LoggingWireupExtensions.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace EventStore -{ - using System; - using Logging; - - public static class LoggingWireupExtensions - { - public static Wireup LogToConsoleWindow(this Wireup wireup) - { - return wireup.LogTo(type => new ConsoleWindowLogger(type)); - } - public static Wireup LogToOutputWindow(this Wireup wireup) - { - return wireup.LogTo(type => new OutputWindowLogger(type)); - } - public static Wireup LogTo(this Wireup wireup, Func logger) - { - LogFactory.BuildLogger = logger; - return wireup; - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Wireup/NanoContainer.cs b/src/proj/EventStore.Wireup/NanoContainer.cs deleted file mode 100644 index c1ad2e3b6..000000000 --- a/src/proj/EventStore.Wireup/NanoContainer.cs +++ /dev/null @@ -1,91 +0,0 @@ -namespace EventStore -{ - using System; - using System.Collections.Generic; - using Logging; - - public class NanoContainer - { - private static readonly ILog Logger = LogFactory.BuildLogger(typeof(NanoContainer)); - private readonly IDictionary registrations = - new Dictionary(); - - public virtual ContainerRegistration Register(Func resolve) - where TService : class - { - Logger.Debug(Messages.RegisteringWireupCallback, typeof(TService)); - var registration = new ContainerRegistration(c => (object)resolve(c)); - this.registrations[typeof(TService)] = registration; - return registration; - } - - public virtual ContainerRegistration Register(TService instance) - { - if (Equals(instance, null)) - throw new ArgumentNullException("instance", Messages.InstanceCannotBeNull); - - if (!typeof(TService).IsValueType && !typeof(TService).IsInterface) - throw new ArgumentException(Messages.TypeMustBeInterface, "instance"); - - Logger.Debug(Messages.RegisteringServiceInstance, typeof(TService)); - var registration = new ContainerRegistration(instance); - this.registrations[typeof(TService)] = registration; - return registration; - } - - public virtual TService Resolve() - { - Logger.Debug(Messages.ResolvingService, typeof(TService)); - - ContainerRegistration registration; - if (this.registrations.TryGetValue(typeof(TService), out registration)) - return (TService)registration.Resolve(this); - - Logger.Debug(Messages.UnableToResolve, typeof(TService)); - return default(TService); - } - } - - public class ContainerRegistration - { - private static readonly ILog Logger = LogFactory.BuildLogger(typeof(ContainerRegistration)); - private readonly Func resolve; - private object instance; - private bool instancePerCall; - - public ContainerRegistration(Func resolve) - { - Logger.Verbose(Messages.AddingWireupCallback); - this.resolve = resolve; - } - public ContainerRegistration(object instance) - { - Logger.Verbose(Messages.AddingWireupRegistration, instance.GetType()); - this.instance = instance; - } - - public virtual ContainerRegistration InstancePerCall() - { - Logger.Verbose(Messages.ConfiguringInstancePerCall); - this.instancePerCall = true; - return this; - } - public virtual object Resolve(NanoContainer container) - { - Logger.Verbose(Messages.ResolvingInstance); - if (this.instancePerCall) - { - Logger.Verbose(Messages.BuildingNewInstance); - return this.resolve(container); - } - - Logger.Verbose(Messages.AttemptingToResolveInstance); - - if (this.instance != null) - return this.instance; - - Logger.Verbose(Messages.BuildingAndStoringNewInstance); - return this.instance = this.resolve(container); - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Wireup/PersistenceWireup.cs b/src/proj/EventStore.Wireup/PersistenceWireup.cs deleted file mode 100644 index acd9697ef..000000000 --- a/src/proj/EventStore.Wireup/PersistenceWireup.cs +++ /dev/null @@ -1,73 +0,0 @@ -namespace EventStore -{ - using System; - using System.Transactions; - using Diagnostics; - using Logging; - using Persistence; - using Serialization; - - public class PersistenceWireup : Wireup - { - private static readonly ILog Logger = LogFactory.BuildLogger(typeof(PersistenceWireup)); - private bool initialize; - private bool tracking; - private string trackingInstanceName; - - public PersistenceWireup(Wireup inner) - : base(inner) - { - this.Container.Register(TransactionScopeOption.Suppress); - } - - public virtual PersistenceWireup WithPersistence(IPersistStreams instance) - { - Logger.Debug(Messages.RegisteringPersistenceEngine, instance.GetType()); - this.With(instance); - return this; - } - protected virtual SerializationWireup WithSerializer(ISerialize serializer) - { - return new SerializationWireup(this, serializer); - } - public virtual PersistenceWireup InitializeStorageEngine() - { - Logger.Debug(Messages.ConfiguringEngineInitialization); - this.initialize = true; - return this; - } - public virtual PersistenceWireup TrackPerformanceInstance(string instanceName) - { - if (instanceName == null) - throw new ArgumentNullException("instanceName", Messages.InstanceCannotBeNull); - - Logger.Debug(Messages.ConfiguringEnginePerformanceTracking); - this.tracking = true; - this.trackingInstanceName = instanceName; - return this; - } - public virtual PersistenceWireup EnlistInAmbientTransaction() - { - Logger.Debug(Messages.ConfiguringEngineEnlistment); - this.Container.Register(TransactionScopeOption.Required); - return this; - } - - public override IStoreEvents Build() - { - Logger.Debug(Messages.BuildingEngine); - var engine = this.Container.Resolve(); - - if (this.initialize) - { - Logger.Debug(Messages.InitializingEngine); - engine.Initialize(); - } - - if (this.tracking) - this.Container.Register(new PerformanceCounterPersistenceEngine(engine, this.trackingInstanceName)); - - return base.Build(); - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Wireup/PersistenceWireupExtensions.cs b/src/proj/EventStore.Wireup/PersistenceWireupExtensions.cs deleted file mode 100644 index b20f82fcc..000000000 --- a/src/proj/EventStore.Wireup/PersistenceWireupExtensions.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace EventStore -{ - using Persistence; - using Persistence.InMemoryPersistence; - - public static class PersistenceWireupExtensions - { - public static PersistenceWireup UsingInMemoryPersistence(this Wireup wireup) - { - wireup.With(new InMemoryPersistenceEngine()); - - return new PersistenceWireup(wireup); - } - public static int Records(this int records) - { - return records; - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Wireup/SerializationWireup.cs b/src/proj/EventStore.Wireup/SerializationWireup.cs deleted file mode 100644 index e79a0ff42..000000000 --- a/src/proj/EventStore.Wireup/SerializationWireup.cs +++ /dev/null @@ -1,36 +0,0 @@ -namespace EventStore -{ - using Logging; - using Serialization; - - public class SerializationWireup : Wireup - { - private static readonly ILog Logger = LogFactory.BuildLogger(typeof(SerializationWireup)); - - public SerializationWireup(Wireup inner, ISerialize serializer) - : base(inner) - { - this.Container.Register(serializer); - } - - public SerializationWireup Compress() - { - Logger.Debug(Messages.ConfiguringCompression); - var wrapped = this.Container.Resolve(); - - Logger.Debug(Messages.WrappingSerializerGZip, wrapped.GetType()); - this.Container.Register(new GzipSerializer(wrapped)); - return this; - } - - public SerializationWireup EncryptWith(byte[] encryptionKey) - { - Logger.Debug(Messages.ConfiguringEncryption); - var wrapped = this.Container.Resolve(); - - Logger.Debug(Messages.WrappingSerializerEncryption, wrapped.GetType()); - this.Container.Register(new RijndaelSerializer(wrapped, encryptionKey)); - return this; - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Wireup/SerializationWireupExtensions.cs b/src/proj/EventStore.Wireup/SerializationWireupExtensions.cs deleted file mode 100644 index fdeac104d..000000000 --- a/src/proj/EventStore.Wireup/SerializationWireupExtensions.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace EventStore -{ - using Serialization; - - public static class SerializationWireupExtensions - { - public static SerializationWireup UsingBinarySerialization(this PersistenceWireup wireup) - { - return wireup.UsingCustomSerialization(new BinarySerializer()); - } - - public static SerializationWireup UsingCustomSerialization(this PersistenceWireup wireup, ISerialize serializer) - { - return new SerializationWireup(wireup, serializer); - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Wireup/SqlPersistenceWireup.cs b/src/proj/EventStore.Wireup/SqlPersistenceWireup.cs deleted file mode 100644 index 40b5f1e25..000000000 --- a/src/proj/EventStore.Wireup/SqlPersistenceWireup.cs +++ /dev/null @@ -1,44 +0,0 @@ -namespace EventStore -{ - using System.Transactions; - using Logging; - using Persistence.SqlPersistence; - using Serialization; - - public class SqlPersistenceWireup : PersistenceWireup - { - private static readonly ILog Logger = LogFactory.BuildLogger(typeof(SqlPersistenceWireup)); - private const int DefaultPageSize = 512; - private int pageSize = DefaultPageSize; - - public SqlPersistenceWireup(Wireup wireup, IConnectionFactory connectionFactory) - : base(wireup) - { - Logger.Debug(Messages.ConnectionFactorySpecified, connectionFactory); - - Logger.Verbose(Messages.AutoDetectDialect); - this.Container.Register(c => null); // auto-detect - - this.Container.Register(c => new SqlPersistenceFactory( - connectionFactory, - c.Resolve(), - c.Resolve(), - c.Resolve(), - this.pageSize).Build()); - } - - public virtual SqlPersistenceWireup WithDialect(ISqlDialect instance) - { - Logger.Debug(Messages.DialectSpecified, instance.GetType()); - this.Container.Register(instance); - return this; - } - - public virtual SqlPersistenceWireup PageEvery(int records) - { - Logger.Debug(Messages.PagingSpecified, records); - this.pageSize = records; - return this; - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Wireup/SqlPersistenceWireupExtensions.cs b/src/proj/EventStore.Wireup/SqlPersistenceWireupExtensions.cs deleted file mode 100644 index bd8881f7f..000000000 --- a/src/proj/EventStore.Wireup/SqlPersistenceWireupExtensions.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace EventStore -{ - using Persistence.SqlPersistence; - - public static class SqlPersistenceWireupExtensions - { - public static SqlPersistenceWireup UsingSqlPersistence(this Wireup wireup, string connectionName) - { - var factory = new ConfigurationConnectionFactory(connectionName); - return wireup.UsingSqlPersistence(factory); - } - public static SqlPersistenceWireup UsingSqlPersistence( - this Wireup wireup, string masterConnectionName, string replicaConnectionName) - { - var factory = new ConfigurationConnectionFactory(masterConnectionName, replicaConnectionName, 1); - return wireup.UsingSqlPersistence(factory); - } - public static SqlPersistenceWireup UsingSqlPersistence(this Wireup wireup, IConnectionFactory factory) - { - return new SqlPersistenceWireup(wireup, factory); - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Wireup/SynchronousDispatchSchedulerWireup.cs b/src/proj/EventStore.Wireup/SynchronousDispatchSchedulerWireup.cs deleted file mode 100644 index 80723426c..000000000 --- a/src/proj/EventStore.Wireup/SynchronousDispatchSchedulerWireup.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace EventStore -{ - using Dispatcher; - using Logging; - using Persistence; - - public class SynchronousDispatchSchedulerWireup : Wireup - { - private static readonly ILog Logger = LogFactory.BuildLogger(typeof(SynchronousDispatchSchedulerWireup)); - - public SynchronousDispatchSchedulerWireup(Wireup wireup, IDispatchCommits dispatcher) - : base(wireup) - { - Logger.Debug(Messages.SyncDispatchSchedulerRegistered); - this.DispatchTo(dispatcher ?? new NullDispatcher()); - this.Container.Register(c => new SynchronousDispatchScheduler( - c.Resolve(), c.Resolve())); - } - - public SynchronousDispatchSchedulerWireup DispatchTo(IDispatchCommits instance) - { - Logger.Debug(Messages.DispatcherRegistered, instance.GetType()); - this.Container.Register(instance); - return this; - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Wireup/SynchronousDispatcherWireupExtensions.cs b/src/proj/EventStore.Wireup/SynchronousDispatcherWireupExtensions.cs deleted file mode 100644 index 5960e1897..000000000 --- a/src/proj/EventStore.Wireup/SynchronousDispatcherWireupExtensions.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace EventStore -{ - using Dispatcher; - - public static class SynchronousDispatcherWireupExtensions - { - public static SynchronousDispatchSchedulerWireup UsingSynchronousDispatchScheduler(this Wireup wireup) - { - return wireup.UsingSynchronousDispatchScheduler(null); - } - - public static SynchronousDispatchSchedulerWireup UsingSynchronousDispatchScheduler( - this Wireup wireup, IDispatchCommits dispatcher) - { - return new SynchronousDispatchSchedulerWireup(wireup, dispatcher); - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore.Wireup/Wireup.cs b/src/proj/EventStore.Wireup/Wireup.cs deleted file mode 100644 index 2bae29517..000000000 --- a/src/proj/EventStore.Wireup/Wireup.cs +++ /dev/null @@ -1,82 +0,0 @@ -namespace EventStore -{ - using System.Collections.Generic; - using System.Linq; - using System.Transactions; - using Conversion; - using Dispatcher; - using Persistence; - using Persistence.InMemoryPersistence; - - public class Wireup - { - private readonly Wireup inner; - private readonly NanoContainer container; - - protected Wireup(NanoContainer container) - { - this.container = container; - } - protected Wireup(Wireup inner) - { - this.inner = inner; - } - - public static Wireup Init() - { - var container = new NanoContainer(); - - container.Register(TransactionScopeOption.Suppress); - container.Register(new InMemoryPersistenceEngine()); - container.Register(BuildEventStore); - - return new Wireup(container); - } - - protected NanoContainer Container - { - get { return this.container ?? this.inner.Container; } - } - - public virtual Wireup With(T instance) where T : class - { - this.Container.Register(instance); - return this; - } - - public virtual Wireup HookIntoPipelineUsing(IEnumerable hooks) - { - return this.HookIntoPipelineUsing((hooks ?? new IPipelineHook[0]).ToArray()); - } - public virtual Wireup HookIntoPipelineUsing(params IPipelineHook[] hooks) - { - ICollection collection = (hooks ?? new IPipelineHook[] { }).Where(x => x != null).ToArray(); - this.Container.Register(collection); - return this; - } - - public virtual IStoreEvents Build() - { - if (this.inner != null) - return this.inner.Build(); - - return this.Container.Resolve(); - } - - private static IStoreEvents BuildEventStore(NanoContainer context) - { - var scopeOption = context.Resolve(); - var concurrency = scopeOption == TransactionScopeOption.Suppress ? new OptimisticPipelineHook() : null; - var scheduler = new DispatchSchedulerPipelineHook(context.Resolve()); - var upconverter = context.Resolve(); - - var hooks = context.Resolve>() ?? new IPipelineHook[0]; - hooks = new IPipelineHook[] { concurrency, scheduler, upconverter } - .Concat(hooks) - .Where(x => x != null) - .ToArray(); - - return new OptimisticEventStore(context.Resolve(), hooks); - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore/Commit.cs b/src/proj/EventStore/Commit.cs deleted file mode 100644 index 01b47388d..000000000 --- a/src/proj/EventStore/Commit.cs +++ /dev/null @@ -1,104 +0,0 @@ -namespace EventStore -{ - using System; - using System.Collections.Generic; - using System.Runtime.Serialization; - - /// - /// Represents a series of events which have been fully committed as a single unit and which apply to the stream indicated. - /// - [DataContract, Serializable] - public class Commit - { - /// - /// Initializes a new instance of the Commit class. - /// - /// The value which uniquely identifies the stream to which the commit belongs. - /// The value which indicates the revision of the most recent event in the stream to which this commit applies. - /// The value which uniquely identifies the commit within the stream. - /// The value which indicates the sequence (or position) in the stream to which this commit applies. - /// The point in time at which the commit was persisted. - /// The metadata which provides additional, unstructured information about this commit. - /// The collection of event messages to be committed as a single unit. - public Commit( - Guid streamId, - int streamRevision, - Guid commitId, - int commitSequence, - DateTime commitStamp, - Dictionary headers, - List events) - :this() - { - this.StreamId = streamId; - this.CommitId = commitId; - this.StreamRevision = streamRevision; - this.CommitSequence = commitSequence; - this.CommitStamp = commitStamp; - this.Headers = headers ?? new Dictionary(); - this.Events = events ?? new List(); - } - - /// - /// Initializes a new instance of the Commit class. - /// - protected Commit() - { - } - - /// - /// Gets the value which uniquely identifies the stream to which the commit belongs. - /// - [DataMember] public virtual Guid StreamId { get; private set; } - - /// - /// Gets the value which indicates the revision of the most recent event in the stream to which this commit applies. - /// - [DataMember] public virtual int StreamRevision { get; private set; } - - /// - /// Gets the value which uniquely identifies the commit within the stream. - /// - [DataMember] public virtual Guid CommitId { get; private set; } - - /// - /// Gets the value which indicates the sequence (or position) in the stream to which this commit applies. - /// - [DataMember] public virtual int CommitSequence { get; private set; } - - /// - /// Gets the point in time at which the commit was persisted. - /// - [DataMember] public virtual DateTime CommitStamp { get; private set; } - - /// - /// Gets the metadata which provides additional, unstructured information about this commit. - /// - [DataMember] public virtual Dictionary Headers { get; private set; } - - /// - /// Gets the collection of event messages to be committed as a single unit. - /// - [DataMember] public virtual List Events { get; private set; } - - /// - /// Determines whether the specified object is equal to the current object. - /// - /// The object to compare with the current object. - /// If the two objects are equal, returns true; otherwise false. - public override bool Equals(object obj) - { - var commit = obj as Commit; - return commit != null && commit.StreamId == this.StreamId && commit.CommitId == this.CommitId; - } - - /// - /// Returns the hash code for this instance. - /// - /// The hash code for this instance. - public override int GetHashCode() - { - return this.StreamId.GetHashCode() ^ this.CommitId.GetHashCode(); - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore/ConcurrencyException.cs b/src/proj/EventStore/ConcurrencyException.cs deleted file mode 100644 index 4ea3d43fc..000000000 --- a/src/proj/EventStore/ConcurrencyException.cs +++ /dev/null @@ -1,48 +0,0 @@ -namespace EventStore -{ - using System; - using System.Runtime.Serialization; - - /// - /// Represents an optimistic concurrency conflict between multiple writers. - /// - [Serializable] - public class ConcurrencyException : Exception - { - /// - /// Initializes a new instance of the ConcurrencyException class. - /// - public ConcurrencyException() - { - } - - /// - /// Initializes a new instance of the ConcurrencyException class. - /// - /// The message that describes the error. - public ConcurrencyException(string message) - : base(message) - { - } - - /// - /// Initializes a new instance of the ConcurrencyException class. - /// - /// The message that describes the error. - /// The message that is the cause of the current exception. - public ConcurrencyException(string message, Exception innerException) - : base(message, innerException) - { - } - - /// - /// Initializes a new instance of the ConcurrencyException class. - /// - /// The SerializationInfo that holds the serialized object data of the exception being thrown. - /// The StreamingContext that contains contextual information about the source or destination. - protected ConcurrencyException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore/Conversion/IUpconvertEvents.cs b/src/proj/EventStore/Conversion/IUpconvertEvents.cs deleted file mode 100644 index 32a3bd452..000000000 --- a/src/proj/EventStore/Conversion/IUpconvertEvents.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace EventStore.Conversion -{ - /// - /// Provides the ability to upconvert an event from one type to another. - /// - /// The source event type from which to convert. - /// The target event type. - public interface IUpconvertEvents - where TSource : class - where TTarget : class - { - /// - /// Converts an event from one type to another. - /// - /// The event to be converted. - /// The converted event. - TTarget Convert(TSource sourceEvent); - } -} \ No newline at end of file diff --git a/src/proj/EventStore/Conversion/MultipleConvertersFoundException.cs b/src/proj/EventStore/Conversion/MultipleConvertersFoundException.cs deleted file mode 100644 index 2f1a72402..000000000 --- a/src/proj/EventStore/Conversion/MultipleConvertersFoundException.cs +++ /dev/null @@ -1,48 +0,0 @@ -namespace EventStore.Conversion -{ - using System; - using System.Runtime.Serialization; - - /// - /// Represents the failure that occurs when there are two or more event converters created for the same source type. - /// - [Serializable] - public class MultipleConvertersFoundException : Exception - { - /// - /// Initializes a new instance of the MultipleConvertersFoundException class. - /// - public MultipleConvertersFoundException() - { - } - - /// - /// Initializes a new instance of the MultipleConvertersFoundException class. - /// - /// The message that describes the error. - public MultipleConvertersFoundException(string message) - : base(message) - { - } - - /// - /// Initializes a new instance of the MultipleConvertersFoundException class. - /// - /// The message that describes the error. - /// The exception that is the cause of the current exception. - public MultipleConvertersFoundException(string message, Exception innerException) - : base(message, innerException) - { - } - - /// - /// Initializes a new instance of the MultipleConvertersFoundException class. - /// - /// The serialization info. - /// The streaming context. - protected MultipleConvertersFoundException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore/Dispatcher/IDispatchCommits.cs b/src/proj/EventStore/Dispatcher/IDispatchCommits.cs deleted file mode 100644 index 32174feb5..000000000 --- a/src/proj/EventStore/Dispatcher/IDispatchCommits.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace EventStore.Dispatcher -{ - using System; - - /// - /// Indicates the ability to dispatch the specified commit to some kind of communications infrastructure. - /// - /// - /// Instances of this class must be designed to be multi-thread safe such that they can be shared between threads. - /// - public interface IDispatchCommits : IDisposable - { - /// - /// Dispatches the commit specified to the messaging infrastructure. - /// - /// The commmit to be dispatched. - void Dispatch(Commit commit); - } -} \ No newline at end of file diff --git a/src/proj/EventStore/Dispatcher/IScheduleDispatches.cs b/src/proj/EventStore/Dispatcher/IScheduleDispatches.cs deleted file mode 100644 index e8fb1b4c5..000000000 --- a/src/proj/EventStore/Dispatcher/IScheduleDispatches.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace EventStore.Dispatcher -{ - using System; - - /// - /// Indicates the ability to schedule the specified commit for delivery--either now or in the future. - /// - /// - /// Instances of this class must be designed to be multi-thread safe such that they can be shared between threads. - /// - public interface IScheduleDispatches : IDisposable - { - /// - /// Schedules the series of messages contained within the commit provided for delivery to all interested parties. - /// - /// The commit representing the series of messages to be dispatched. - void ScheduleDispatch(Commit commit); - } -} \ No newline at end of file diff --git a/src/proj/EventStore/DuplicateCommitException.cs b/src/proj/EventStore/DuplicateCommitException.cs deleted file mode 100644 index 5fa6c47f2..000000000 --- a/src/proj/EventStore/DuplicateCommitException.cs +++ /dev/null @@ -1,48 +0,0 @@ -namespace EventStore -{ - using System; - using System.Runtime.Serialization; - - /// - /// Represents an attempt to commit the same information more than once. - /// - [Serializable] - public class DuplicateCommitException : Exception - { - /// - /// Initializes a new instance of the DuplicateCommitException class. - /// - public DuplicateCommitException() - { - } - - /// - /// Initializes a new instance of the DuplicateCommitException class. - /// - /// The message that describes the error. - public DuplicateCommitException(string message) - : base(message) - { - } - - /// - /// Initializes a new instance of the DuplicateCommitException class. - /// - /// The message that describes the error. - /// The message that is the cause of the current exception. - public DuplicateCommitException(string message, Exception innerException) - : base(message, innerException) - { - } - - /// - /// Initializes a new instance of the DuplicateCommitException class. - /// - /// The SerializationInfo that holds the serialized object data of the exception being thrown. - /// The StreamingContext that contains contextual information about the source or destination. - protected DuplicateCommitException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore/EventMessage.cs b/src/proj/EventStore/EventMessage.cs deleted file mode 100644 index 048bd3668..000000000 --- a/src/proj/EventStore/EventMessage.cs +++ /dev/null @@ -1,31 +0,0 @@ -namespace EventStore -{ - using System; - using System.Collections.Generic; - using System.Runtime.Serialization; - - /// - /// Represents a single element in a stream of events. - /// - [DataContract, Serializable] - public class EventMessage - { - /// - /// Initializes a new instance of the EventMessage class. - /// - public EventMessage() - { - this.Headers = new Dictionary(); - } - - /// - /// Gets the metadata which provides additional, unstructured information about this message. - /// - [DataMember] public Dictionary Headers { get; private set; } - - /// - /// Gets or sets the actual event message body. - /// - [DataMember] public object Body { get; set; } - } -} \ No newline at end of file diff --git a/src/proj/EventStore/EventStore.csproj b/src/proj/EventStore/EventStore.csproj deleted file mode 100644 index b7c4011a8..000000000 --- a/src/proj/EventStore/EventStore.csproj +++ /dev/null @@ -1,92 +0,0 @@ - - - - Debug - AnyCPU - 9.0.30729 - 2.0 - {03946843-F343-419C-88EF-3E446D08DFA6} - Library - Properties - EventStore - EventStore - v4.0 - 512 - true - ..\..\EventStore.snk - ..\..\..\src\ - true - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - bin\Debug\EventStore.xml - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - bin\Release\EventStore.xml - - - - Properties\GlobalAssemblyInfo.cs - - - Properties\GlobalSuppressions.cs - - - Properties\VersionAssemblyInfo.cs - - - Code - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Properties\CustomDictionary.xml - - - - - - - - - - \ No newline at end of file diff --git a/src/proj/EventStore/ExtensionMethods.cs b/src/proj/EventStore/ExtensionMethods.cs deleted file mode 100644 index 8a20c6638..000000000 --- a/src/proj/EventStore/ExtensionMethods.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace EventStore -{ - using System.Globalization; - - /// - /// A set of common methods used through the EventStore. - /// - public static class ExtensionMethods - { - /// - /// Formats the string provided using the values specified. - /// - /// The string to be formated. - /// The values to be embedded into the string. - /// The formatted string. - public static string FormatWith(this string format, params object[] values) - { - return string.Format(CultureInfo.InvariantCulture, format ?? string.Empty, values); - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore/IAccessSnapshots.cs b/src/proj/EventStore/IAccessSnapshots.cs deleted file mode 100644 index 434d1e50c..000000000 --- a/src/proj/EventStore/IAccessSnapshots.cs +++ /dev/null @@ -1,43 +0,0 @@ -namespace EventStore -{ - using System; - using System.Collections.Generic; - using Persistence; - - /// - /// Indicates the ability to get or retrieve a snapshot for a given stream. - /// - /// - /// Instances of this class must be designed to be multi-thread safe such that they can be shared between threads. - /// - public interface IAccessSnapshots - { - /// - /// Gets the most recent snapshot which was taken on or before the revision indicated. - /// - /// The stream to be searched for a snapshot. - /// The maximum revision possible for the desired snapshot. - /// If found, it returns the snapshot; otherwise null is returned. - /// - /// - Snapshot GetSnapshot(Guid streamId, int maxRevision); - - /// - /// Adds the snapshot provided to the stream indicated. - /// - /// The snapshot to save. - /// If the snapshot was added, returns true; otherwise false. - /// - /// - bool AddSnapshot(Snapshot snapshot); - - /// - /// Gets identifiers for all streams whose head and last snapshot revisions differ by at least the threshold specified. - /// - /// The maximum difference between the head and most recent snapshot revisions. - /// The streams for which the head and snapshot revisions differ by at least the threshold specified. - /// - /// - IEnumerable GetStreamsToSnapshot(int maxThreshold); - } -} \ No newline at end of file diff --git a/src/proj/EventStore/ICommitEvents.cs b/src/proj/EventStore/ICommitEvents.cs deleted file mode 100644 index eefdd40be..000000000 --- a/src/proj/EventStore/ICommitEvents.cs +++ /dev/null @@ -1,36 +0,0 @@ -namespace EventStore -{ - using System; - using System.Collections.Generic; - using Persistence; - - /// - /// Indicates the ability to commit events and access events to and from a given stream. - /// - /// - /// Instances of this class must be designed to be multi-thread safe such that they can be shared between threads. - /// - public interface ICommitEvents - { - /// - /// Gets the corresponding commits from the stream indicated starting at the revision specified until the - /// end of the stream sorted in ascending order--from oldest to newest. - /// - /// The stream from which the events will be read. - /// The minimum revision of the stream to be read. - /// The maximum revision of the stream to be read. - /// A series of committed events from the stream specified sorted in ascending order.. - /// - /// - IEnumerable GetFrom(Guid streamId, int minRevision, int maxRevision); - - /// - /// Writes the to-be-commited events provided to the underlying persistence mechanism. - /// - /// The series of events and associated metadata to be commited. - /// - /// - /// - void Commit(Commit attempt); - } -} \ No newline at end of file diff --git a/src/proj/EventStore/IEventStream.cs b/src/proj/EventStore/IEventStream.cs deleted file mode 100644 index c4c4b747f..000000000 --- a/src/proj/EventStore/IEventStream.cs +++ /dev/null @@ -1,71 +0,0 @@ -namespace EventStore -{ - using System; - using System.Collections.Generic; - using Persistence; - - /// - /// Indicates the ability to track a series of events and commit them to durable storage. - /// - /// - /// Instances of this class are single threaded and should not be shared between threads. - /// - public interface IEventStream : IDisposable - { - /// - /// Gets the value which uniquely identifies the stream to which the stream belongs. - /// - Guid StreamId { get; } - - /// - /// Gets the value which indiciates the most recent committed revision of event stream. - /// - int StreamRevision { get; } - - /// - /// Gets the value which indicates the most recent committed sequence identifier of the event stream. - /// - int CommitSequence { get; } - - /// - /// Gets the collection of events which have been successfully persisted to durable storage. - /// - ICollection CommittedEvents { get; } - - /// - /// Gets the collection of committed headers associated with the stream. - /// - IDictionary CommittedHeaders { get; } - - /// - /// Gets the collection of yet-to-be-committed events that have not yet been persisted to durable storage. - /// - ICollection UncommittedEvents { get; } - - /// - /// Gets the collection of yet-to-be-committed headers associated with the uncommitted events. - /// - IDictionary UncommittedHeaders { get; } - - /// - /// Adds the event messages provided to the session to be tracked. - /// - /// The event to be tracked. - void Add(EventMessage uncommittedEvent); - - /// - /// Commits the changes to durable storage. - /// - /// The value which uniquely identifies the commit. - /// - /// - /// - /// - void CommitChanges(Guid commitId); - - /// - /// Clears the uncommitted changes. - /// - void ClearChanges(); - } -} \ No newline at end of file diff --git a/src/proj/EventStore/IPipelineHook.cs b/src/proj/EventStore/IPipelineHook.cs deleted file mode 100644 index fd5d5b7ec..000000000 --- a/src/proj/EventStore/IPipelineHook.cs +++ /dev/null @@ -1,33 +0,0 @@ -namespace EventStore -{ - using System; - - /// - /// Provides the ability to hook into the pipeline of persisting a commit. - /// - /// - /// Instances of this class must be designed to be multi-thread safe such that they can be shared between threads. - /// - public interface IPipelineHook : IDisposable - { - /// - /// Hooks into the selection pipeline just prior to the commit being returned to the caller. - /// - /// The commit to be filtered. - /// If successful, returns a populated commit; otherwise returns null. - Commit Select(Commit committed); - - /// - /// Hooks into the commit pipeline prior to persisting the commit to durable storage. - /// - /// The attempt to be committed. - /// If processing should continue, returns true; otherwise returns false. - bool PreCommit(Commit attempt); - - /// - /// Hooks into the commit pipeline just after the commit has been *successfully* committed to durable storage. - /// - /// The commit which has been persisted. - void PostCommit(Commit committed); - } -} \ No newline at end of file diff --git a/src/proj/EventStore/IStoreEvents.cs b/src/proj/EventStore/IStoreEvents.cs deleted file mode 100644 index 2f74c0a77..000000000 --- a/src/proj/EventStore/IStoreEvents.cs +++ /dev/null @@ -1,49 +0,0 @@ -namespace EventStore -{ - using System; - using Persistence; - - /// - /// Indicates the ability to store and retreive a stream of events. - /// - /// - /// Instances of this class must be designed to be multi-thread safe such that they can be shared between threads. - /// - public interface IStoreEvents : IDisposable - { - /// - /// Creates a new stream. - /// - /// The value which uniquely identifies the stream to be created. - /// An empty stream. - IEventStream CreateStream(Guid streamId); - - /// - /// Reads the stream indicated from the minimum revision specified up to the maximum revision specified or creates - /// an empty stream if no commits are found and a minimum revision of zero is provided. - /// - /// The value which uniquely identifies the stream from which the events will be read. - /// The minimum revision of the stream to be read. - /// The maximum revision of the stream to be read. - /// A series of committed events represented as a stream. - /// - /// - /// - IEventStream OpenStream(Guid streamId, int minRevision, int maxRevision); - - /// - /// Reads the stream indicated from the point of the snapshot forward until the maximum revision specified. - /// - /// The snapshot of the stream to be read. - /// The maximum revision of the stream to be read. - /// A series of committed events represented as a stream. - /// - /// - IEventStream OpenStream(Snapshot snapshot, int maxRevision); - - /// - /// Gets a reference to the underlying persistence engine which allows direct access to persistence operations. - /// - IPersistStreams Advanced { get; } - } -} \ No newline at end of file diff --git a/src/proj/EventStore/Logging/ILog.cs b/src/proj/EventStore/Logging/ILog.cs deleted file mode 100644 index a45ebbb93..000000000 --- a/src/proj/EventStore/Logging/ILog.cs +++ /dev/null @@ -1,53 +0,0 @@ -namespace EventStore.Logging -{ - /// - /// Indicates the ability to log diagnostic information. - /// - /// - /// Object instances which implement this interface must be designed to be multi-thread safe. - /// - public interface ILog - { - /// - /// Logs the most detailed level of diagnostic information. - /// - /// The diagnostic message to be logged. - /// All parameter to be formatted into the message, if any. - void Verbose(string message, params object[] values); - - /// - /// Logs the debug-level diagnostic information. - /// - /// The diagnostic message to be logged. - /// All parameter to be formatted into the message, if any. - void Debug(string message, params object[] values); - - /// - /// Logs important runtime diagnostic information. - /// - /// The diagnostic message to be logged. - /// All parameter to be formatted into the message, if any. - void Info(string message, params object[] values); - - /// - /// Logs diagnostic issues to which attention should be paid. - /// - /// The diagnostic message to be logged. - /// All parameter to be formatted into the message, if any. - void Warn(string message, params object[] values); - - /// - /// Logs application and infrastructure-level errors. - /// - /// The diagnostic message to be logged. - /// All parameter to be formatted into the message, if any. - void Error(string message, params object[] values); - - /// - /// Logs fatal errors which result in process termination. - /// - /// The diagnostic message to be logged. - /// All parameter to be formatted into the message, if any. - void Fatal(string message, params object[] values); - } -} \ No newline at end of file diff --git a/src/proj/EventStore/Logging/LogFactory.cs b/src/proj/EventStore/Logging/LogFactory.cs deleted file mode 100644 index b4317b011..000000000 --- a/src/proj/EventStore/Logging/LogFactory.cs +++ /dev/null @@ -1,46 +0,0 @@ -namespace EventStore.Logging -{ - using System; - - /// - /// Provides the ability to get a new instance of the configured logger. - /// - public static class LogFactory - { - /// - /// Initializes static members of the LogFactory class. - /// - static LogFactory() - { - var logger = new NullLogger(); - BuildLogger = type => logger; - } - - /// - /// Gets or sets the log builder of the configured logger. This should be invoked to return a new logging instance. - /// - public static Func BuildLogger { get; set; } - - private class NullLogger : ILog - { - public void Verbose(string message, params object[] values) - { - } - public void Debug(string message, params object[] values) - { - } - public void Info(string message, params object[] values) - { - } - public void Warn(string message, params object[] values) - { - } - public void Error(string message, params object[] values) - { - } - public void Fatal(string message, params object[] values) - { - } - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore/Persistence/IPersistStreams.cs b/src/proj/EventStore/Persistence/IPersistStreams.cs deleted file mode 100644 index ae43e2928..000000000 --- a/src/proj/EventStore/Persistence/IPersistStreams.cs +++ /dev/null @@ -1,61 +0,0 @@ -namespace EventStore.Persistence -{ - using System; - using System.Collections.Generic; - - /// - /// Indicates the ability to adapt the underlying persistence infrastructure to behave like a stream of events. - /// - /// - /// Instances of this class must be designed to be multi-thread safe such that they can be shared between threads. - /// - public interface IPersistStreams : IDisposable, ICommitEvents, IAccessSnapshots - { - /// - /// Initializes and prepares the storage for use, if not already performed. - /// - /// - /// - void Initialize(); - - /// - /// Gets all commits on or after from the specified starting time. - /// - /// The point in time at which to start. - /// All commits that have occurred on or after the specified starting time. - /// - /// - IEnumerable GetFrom(DateTime start); - - /// - /// Gets all commits on or after from the specified starting time and after the specified end time. - /// - /// The point in time at which to start. - /// The point in time at which to end. - /// All commits that have occurred on or after the specified starting time and before the end time. - /// - /// - IEnumerable GetFromTo(DateTime start, DateTime end); - - /// - /// Gets a set of commits that has not yet been dispatched. - /// - /// The set of commits to be dispatched. - /// - /// - IEnumerable GetUndispatchedCommits(); - - /// - /// Marks the commit specified as dispatched. - /// - /// The commit to be marked as dispatched. - /// - /// - void MarkCommitAsDispatched(Commit commit); - - /// - /// Completely DESTROYS the contents of ANY and ALL streams that have been successfully persisted. Use with caution. - /// - void Purge(); - } -} \ No newline at end of file diff --git a/src/proj/EventStore/Persistence/IPersistenceFactory.cs b/src/proj/EventStore/Persistence/IPersistenceFactory.cs deleted file mode 100644 index 511ddce13..000000000 --- a/src/proj/EventStore/Persistence/IPersistenceFactory.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace EventStore.Persistence -{ - /// - /// Indicates the ability to build a ready-to-use persistence engine. - /// - /// - /// Instances of this class must be designed to be multi-thread safe such that they can be shared between threads. - /// - public interface IPersistenceFactory - { - /// - /// Builds a persistence engine. - /// - /// A ready-to-use persistence engine. - IPersistStreams Build(); - } -} \ No newline at end of file diff --git a/src/proj/EventStore/Persistence/StorageException.cs b/src/proj/EventStore/Persistence/StorageException.cs deleted file mode 100644 index 84d468bce..000000000 --- a/src/proj/EventStore/Persistence/StorageException.cs +++ /dev/null @@ -1,48 +0,0 @@ -namespace EventStore.Persistence -{ - using System; - using System.Runtime.Serialization; - - /// - /// Represents a general failure of the storage engine or persistence infrastructure. - /// - [Serializable] - public class StorageException : Exception - { - /// - /// Initializes a new instance of the StorageException class. - /// - public StorageException() - { - } - - /// - /// Initializes a new instance of the StorageException class. - /// - /// The message that describes the error. - public StorageException(string message) - : base(message) - { - } - - /// - /// Initializes a new instance of the StorageException class. - /// - /// The message that describes the error. - /// The message that is the cause of the current exception. - public StorageException(string message, Exception innerException) - : base(message, innerException) - { - } - - /// - /// Initializes a new instance of the StorageException class. - /// - /// The SerializationInfo that holds the serialized object data of the exception being thrown. - /// The StreamingContext that contains contextual information about the source or destination. - protected StorageException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore/Persistence/StorageUnavailableException.cs b/src/proj/EventStore/Persistence/StorageUnavailableException.cs deleted file mode 100644 index c8cdb1c78..000000000 --- a/src/proj/EventStore/Persistence/StorageUnavailableException.cs +++ /dev/null @@ -1,48 +0,0 @@ -namespace EventStore.Persistence -{ - using System; - using System.Runtime.Serialization; - - /// - /// Indicates that the underlying persistence medium is unavailable or offline. - /// - [Serializable] - public class StorageUnavailableException : StorageException - { - /// - /// Initializes a new instance of the StorageUnavailableException class. - /// - public StorageUnavailableException() - { - } - - /// - /// Initializes a new instance of the StorageUnavailableException class. - /// - /// The message that describes the error. - public StorageUnavailableException(string message) - : base(message) - { - } - - /// - /// Initializes a new instance of the StorageUnavailableException class. - /// - /// The message that describes the error. - /// The message that is the cause of the current exception. - public StorageUnavailableException(string message, Exception innerException) - : base(message, innerException) - { - } - - /// - /// Initializes a new instance of the StorageUnavailableException class. - /// - /// The SerializationInfo that holds the serialized object data of the exception being thrown. - /// The StreamingContext that contains contextual information about the source or destination. - protected StorageUnavailableException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore/Persistence/StreamHead.cs b/src/proj/EventStore/Persistence/StreamHead.cs deleted file mode 100644 index d083b6325..000000000 --- a/src/proj/EventStore/Persistence/StreamHead.cs +++ /dev/null @@ -1,66 +0,0 @@ -namespace EventStore.Persistence -{ - using System; - - /// - /// Indicates the most recent information representing the head of a given stream. - /// - public class StreamHead - { - /// - /// Initializes a new instance of the StreamHead class. - /// - /// The value which uniquely identifies the stream where the last snapshot exceeds the allowed threshold. - /// The value which indicates the revision, length, or number of events committed to the stream. - /// The value which indicates the revision at which the last snapshot was taken. - public StreamHead(Guid streamId, int headRevision, int snapshotRevision) - : this() - { - this.StreamId = streamId; - this.HeadRevision = headRevision; - this.SnapshotRevision = snapshotRevision; - } - - /// - /// Initializes a new instance of the StreamHead class. - /// - protected StreamHead() - { - } - - /// - /// Gets the value which uniquely identifies the stream where the last snapshot exceeds the allowed threshold. - /// - public Guid StreamId { get; private set; } - - /// - /// Gets the value which indicates the revision, length, or number of events committed to the stream. - /// - public int HeadRevision { get; private set; } - - /// - /// Gets the value which indicates the revision at which the last snapshot was taken. - /// - public int SnapshotRevision { get; private set; } - - /// - /// Determines whether the specified object is equal to the current object. - /// - /// The object to compare with the current object. - /// If the two objects are equal, returns true; otherwise false. - public override bool Equals(object obj) - { - var commit = obj as StreamHead; - return commit != null && commit.StreamId == this.StreamId; - } - - /// - /// Returns the hash code for this instance. - /// - /// The hash code for this instance. - public override int GetHashCode() - { - return this.StreamId.GetHashCode(); - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore/Serialization/IDocumentSerializer.cs b/src/proj/EventStore/Serialization/IDocumentSerializer.cs deleted file mode 100644 index dd28a0051..000000000 --- a/src/proj/EventStore/Serialization/IDocumentSerializer.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace EventStore.Serialization -{ - /// - /// Provides the ability to serialize an object graph to and from a document. - /// - /// - /// Instances of this class must be designed to be multi-thread safe such that they can be shared between threads. - /// - public interface IDocumentSerializer - { - /// - /// Serializes the object graph provided into a document. - /// - /// The type of object to be serialized - /// The object graph to be serialized. - /// The document form of the graph provided. - object Serialize(T graph); - - /// - /// Deserializes the document provided into an object graph. - /// - /// The type of object graph. - /// The document to be deserialized. - /// An object graph of the specified type. - T Deserialize(object document); - } -} \ No newline at end of file diff --git a/src/proj/EventStore/Serialization/ISerialize.cs b/src/proj/EventStore/Serialization/ISerialize.cs deleted file mode 100644 index 335720810..000000000 --- a/src/proj/EventStore/Serialization/ISerialize.cs +++ /dev/null @@ -1,29 +0,0 @@ -namespace EventStore.Serialization -{ - using System.IO; - - /// - /// Provides the ability to serialize and deserialize an object graph. - /// - /// - /// Instances of this class must be designed to be multi-thread safe such that they can be shared between threads. - /// - public interface ISerialize - { - /// - /// Serializes the object graph provided and writes a serialized representation to the output stream provided. - /// - /// The type of object to be serialized - /// The stream into which the serialized object graph should be written. - /// The object graph to be serialized. - void Serialize(Stream output, T graph); - - /// - /// Deserializes the stream provided and reconstructs the corresponding object graph. - /// - /// The type of object to be deserialized. - /// The stream of bytes from which the object will be reconstructed. - /// The reconstructed object. - T Deserialize(Stream input); - } -} \ No newline at end of file diff --git a/src/proj/EventStore/Serialization/SerializationExtensions.cs b/src/proj/EventStore/Serialization/SerializationExtensions.cs deleted file mode 100644 index 63adfb0fc..000000000 --- a/src/proj/EventStore/Serialization/SerializationExtensions.cs +++ /dev/null @@ -1,43 +0,0 @@ -namespace EventStore.Serialization -{ - using System.IO; - - /// - /// Implements extension methods that make call to the serialization infrastructure more simple. - /// - public static class SerializationExtensions - { - /// - /// Serializes the object provided. - /// - /// The type of object to be serialized - /// The serializer to use. - /// The object graph to be serialized. - /// A serialized representation of the object graph provided. - public static byte[] Serialize(this ISerialize serializer, T value) - { - using (var stream = new MemoryStream()) - { - serializer.Serialize(stream, value); - return stream.ToArray(); - } - } - - /// - /// Deserializes the array of bytes provided. - /// - /// The type of object to be deserialized. - /// The serializer to use. - /// The serialized array of bytes. - /// The reconstituted object, if any. - public static T Deserialize(this ISerialize serializer, byte[] serialized) - { - serialized = serialized ?? new byte[] { }; - if (serialized.Length == 0) - return default(T); - - using (var stream = new MemoryStream(serialized)) - return serializer.Deserialize(stream); - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore/Snapshot.cs b/src/proj/EventStore/Snapshot.cs deleted file mode 100644 index 18fdf5302..000000000 --- a/src/proj/EventStore/Snapshot.cs +++ /dev/null @@ -1,48 +0,0 @@ -namespace EventStore -{ - using System; - using System.Runtime.Serialization; - - /// - /// Represents a materialized view of a stream at specific revision. - /// - [DataContract, Serializable] - public class Snapshot - { - /// - /// Initializes a new instance of the Snapshot class. - /// - /// The value which uniquely identifies the stream to which the snapshot applies. - /// The position at which the snapshot applies. - /// The snapshot or materialized view of the stream at the revision indicated. - public Snapshot(Guid streamId, int streamRevision, object payload) - : this() - { - this.StreamId = streamId; - this.StreamRevision = streamRevision; - this.Payload = payload; - } - - /// - /// Initializes a new instance of the Snapshot class. - /// - protected Snapshot() - { - } - - /// - /// Gets the value which uniquely identifies the stream to which the snapshot applies. - /// - [DataMember] public virtual Guid StreamId { get; private set; } - - /// - /// Gets the position at which the snapshot applies. - /// - [DataMember] public virtual int StreamRevision { get; private set; } - - /// - /// Gets the snapshot or materialized view of the stream at the revision indicated. - /// - [DataMember] public virtual object Payload { get; private set; } - } -} \ No newline at end of file diff --git a/src/proj/EventStore/StreamNotFoundException.cs b/src/proj/EventStore/StreamNotFoundException.cs deleted file mode 100644 index 1be31dd68..000000000 --- a/src/proj/EventStore/StreamNotFoundException.cs +++ /dev/null @@ -1,48 +0,0 @@ -namespace EventStore -{ - using System; - using System.Runtime.Serialization; - - /// - /// Represents an attempt to retrieve a nonexistent event stream. - /// - [Serializable] - public class StreamNotFoundException : Exception - { - /// - /// Initializes a new instance of the StreamNotFoundException class. - /// - public StreamNotFoundException() - { - } - - /// - /// Initializes a new instance of the StreamNotFoundException class. - /// - /// The message that describes the error. - public StreamNotFoundException(string message) - : base(message) - { - } - - /// - /// Initializes a new instance of the StreamNotFoundException class. - /// - /// The message that describes the error. - /// The message that is the cause of the current exception. - public StreamNotFoundException(string message, Exception innerException) - : base(message, innerException) - { - } - - /// - /// Initializes a new instance of the StreamNotFoundException class. - /// - /// The SerializationInfo that holds the serialized object data of the exception being thrown. - /// The StreamingContext that contains contextual information about the source or destination. - protected StreamNotFoundException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } - } -} \ No newline at end of file diff --git a/src/proj/EventStore/SystemTime.cs b/src/proj/EventStore/SystemTime.cs deleted file mode 100644 index 176fb93b6..000000000 --- a/src/proj/EventStore/SystemTime.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace EventStore -{ - using System; - - /// - /// Provides the ability to override the current moment in time to facilitate testing. - /// Original idea by Ayende Rahien: - /// http://ayende.com/Blog/archive/2008/07/07/Dealing-with-time-in-tests.aspx - /// - public static class SystemTime - { - /// - /// The callback to be used to resolve the current moment in time. - /// - public static Func Resolver; - - /// - /// Gets the current moment in time. - /// - public static DateTime UtcNow - { - get { return Resolver == null ? DateTime.UtcNow : Resolver(); } - } - } -} \ No newline at end of file diff --git a/src/proj/VersionAssemblyInfo.cs b/src/proj/VersionAssemblyInfo.cs deleted file mode 100644 index 50ef0d7df..000000000 --- a/src/proj/VersionAssemblyInfo.cs +++ /dev/null @@ -1,5 +0,0 @@ -using System.Reflection; - -[assembly: AssemblyVersion("3.1.0.0")] -[assembly: AssemblyFileVersion("3.1.0.0")] -//// [assembly: AssemblyInformationalVersion("3.1.0.0 Release")] \ No newline at end of file diff --git a/src/tests/EventStore.Core.UnitTests/ConversionTests/EventUpconverterPipelineHookTests.cs b/src/tests/EventStore.Core.UnitTests/ConversionTests/EventUpconverterPipelineHookTests.cs deleted file mode 100644 index 25b2295c6..000000000 --- a/src/tests/EventStore.Core.UnitTests/ConversionTests/EventUpconverterPipelineHookTests.cs +++ /dev/null @@ -1,180 +0,0 @@ -#pragma warning disable 169 -// ReSharper disable InconsistentNaming - -namespace EventStore.Core.UnitTests.ConversionTests -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Reflection; - using Conversion; - using Machine.Specifications; - using It = Machine.Specifications.It; - - [Subject("EventUpconverterPipelineHook")] - public class when_opening_a_commit_that_does_not_have_convertible_events : using_event_converter - { - static readonly Commit commit = new Commit( - Guid.NewGuid(), 0, Guid.NewGuid(), 0, DateTime.MinValue, null, null); - static Commit converted; - - Establish context = () => - commit.Events.Add(new EventMessage { Body = new NonConvertingEvent() }); - - Because of = () => - converted = eventUpconverter.Select(commit); - - It should_not_be_converted = () => - converted.ShouldBeTheSameAs(commit); - - It should_have_the_same_instance_of_the_event = () => - converted.Events.Single().ShouldEqual(commit.Events.Single()); - } - - [Subject("EventUpconverterPipelineHook")] - public class when_opening_a_commit_that_has_convertible_events : using_event_converter - { - static readonly Commit commit = new Commit( - Guid.NewGuid(), 0, Guid.NewGuid(), 0, DateTime.MinValue, null, null); - static readonly Guid id = Guid.NewGuid(); - static readonly EventMessage eventMessage = new EventMessage - { - Body = new ConvertingEvent(id) - }; - static Commit converted; - - Establish context = () => - commit.Events.Add(eventMessage); - - Because of = () => - converted = eventUpconverter.Select(commit); - - It should_be_of_the_converted_type = () => - converted.Events.Single().Body.GetType().ShouldEqual(typeof(ConvertingEvent3)); - - It should_have_the_same_id_of_the_commited_event = () => - ((ConvertingEvent3)converted.Events.Single().Body).Id.ShouldEqual(id); - } - - [Subject("EventUpconverterPipelineHook")] - public class when_an_event_converter_implements_the_IConvertEvents_interface_explicitly : using_event_converter - { - static readonly Commit commit = new Commit( - Guid.NewGuid(), 0, Guid.NewGuid(), 0, DateTime.MinValue, null, null); - static readonly Guid id = Guid.NewGuid(); - static readonly EventMessage eventMessage = new EventMessage - { - Body = new ConvertingEvent2(id, "FooEvent") - }; - static Commit converted; - - Establish context = () => - commit.Events.Add(eventMessage); - - Because of = () => - converted = eventUpconverter.Select(commit); - - It should_be_of_the_converted_type = () => - converted.Events.Single().Body.GetType().ShouldEqual(typeof(ConvertingEvent3)); - - It should_have_the_same_id_of_the_commited_event = () => - ((ConvertingEvent3)converted.Events.Single().Body).Id.ShouldEqual(id); - } - - public class using_event_converter - { - protected static IEnumerable assemblies; - protected static Dictionary> converters; - protected static EventUpconverterPipelineHook eventUpconverter; - - Establish context = () => - { - assemblies = GetAllAssemblies(); - converters = GetConverters(assemblies); - eventUpconverter = new EventUpconverterPipelineHook(converters); - }; - - private static Dictionary> GetConverters(IEnumerable toScan) - { - var c = from a in toScan - from t in a.GetTypes() - let i = t.GetInterface(typeof(IUpconvertEvents<,>).FullName) - where i != null - let sourceType = i.GetGenericArguments().First() - let convertMethod = i.GetMethods(BindingFlags.Public | BindingFlags.Instance).First() - let instance = Activator.CreateInstance(t) - select new KeyValuePair>( - sourceType, - e => convertMethod.Invoke(instance, new[] { e })); - try - { - return c.ToDictionary(x => x.Key, x => x.Value); - } - catch (ArgumentException ex) - { - throw new MultipleConvertersFoundException(ex.Message, ex); - } - } - - private static IEnumerable GetAllAssemblies() - { - return Assembly.GetCallingAssembly() - .GetReferencedAssemblies() - .Select(Assembly.Load) - .Concat(new[] { Assembly.GetCallingAssembly() }); - } - } - - public class ConvertingEventConverter : IUpconvertEvents - { - public ConvertingEvent2 Convert(ConvertingEvent sourceEvent) - { - return new ConvertingEvent2(sourceEvent.Id, "Temp"); - } - } - public class ExplicitConvertingEventConverter : IUpconvertEvents - { - ConvertingEvent3 IUpconvertEvents.Convert(ConvertingEvent2 sourceEvent) - { - return new ConvertingEvent3(sourceEvent.Id, "Temp", true); - } - } - public class NonConvertingEvent - { - } - public class ConvertingEvent - { - public Guid Id { get; set; } - public ConvertingEvent(Guid id) - { - this.Id = id; - } - } - public class ConvertingEvent2 - { - public Guid Id { get; set; } - public string Name { get; set; } - - public ConvertingEvent2(Guid id, string name) - { - this.Id = id; - this.Name = name; - } - } - public class ConvertingEvent3 - { - public Guid Id { get; set; } - public string Name { get; set; } - public bool ImExplicit { get; set; } - - public ConvertingEvent3(Guid id, string name, bool imExplicit) - { - this.Id = id; - this.Name = name; - this.ImExplicit = imExplicit; - } - } -} - -// ReSharper enable InconsistentNaming -#pragma warning restore 169 \ No newline at end of file diff --git a/src/tests/EventStore.Core.UnitTests/DispatchCommitHookTests.cs b/src/tests/EventStore.Core.UnitTests/DispatchCommitHookTests.cs deleted file mode 100644 index 9fe1671b7..000000000 --- a/src/tests/EventStore.Core.UnitTests/DispatchCommitHookTests.cs +++ /dev/null @@ -1,62 +0,0 @@ -#pragma warning disable 169 -// ReSharper disable InconsistentNaming - -namespace EventStore.Core.UnitTests -{ - using System; - using Dispatcher; - using Machine.Specifications; - using Moq; - using It = Machine.Specifications.It; - - [Subject("DispatchSchedulerPipelinkHook")] - public class when_a_commit_has_been_persisted - { - static readonly Commit commit = new Commit( - Guid.NewGuid(), 0, Guid.NewGuid(), 0, DateTime.MinValue, null, null); - static readonly Mock dispatcher = new Mock(); - static readonly DispatchSchedulerPipelineHook DispatchSchedulerHook = new DispatchSchedulerPipelineHook(dispatcher.Object); - - Establish context = () => - dispatcher.Setup(x => x.ScheduleDispatch(null)); - - Because of = () => - DispatchSchedulerHook.PostCommit(commit); - - It should_invoke_the_configured_dispatcher = () => - dispatcher.Verify(x => x.ScheduleDispatch(commit), Times.Once()); - } - - [Subject("DispatchSchedulerPipelinkHook")] - public class when_the_hook_has_no_dispatcher_configured - { - static readonly Commit commit = new Commit( - Guid.NewGuid(), 0, Guid.NewGuid(), 0, DateTime.MinValue, null, null); - static readonly DispatchSchedulerPipelineHook DispatchSchedulerHook = new DispatchSchedulerPipelineHook(); - static Exception thrown; - - Because of = () => - thrown = Catch.Exception(() => DispatchSchedulerHook.PostCommit(commit)); - - It should_not_throw_an_exception = () => - thrown.ShouldBeNull(); - } - - [Subject("DispatchSchedulerPipelinkHook")] - public class when_a_commit_is_selected - { - static readonly Commit commit = new Commit( - Guid.NewGuid(), 0, Guid.NewGuid(), 0, DateTime.MinValue, null, null); - static readonly DispatchSchedulerPipelineHook DispatchSchedulerHook = new DispatchSchedulerPipelineHook(); - static Commit selected; - - Because of = () => - selected = DispatchSchedulerHook.Select(commit); - - It should_always_return_the_exact_same_commit = () => - ReferenceEquals(selected, commit).ShouldBeTrue(); - } -} - -// ReSharper enable InconsistentNaming -#pragma warning restore 169 \ No newline at end of file diff --git a/src/tests/EventStore.Core.UnitTests/DispatcherTests/AsynchronousDispatcherTests.cs b/src/tests/EventStore.Core.UnitTests/DispatcherTests/AsynchronousDispatcherTests.cs deleted file mode 100644 index 12d1f1ea2..000000000 --- a/src/tests/EventStore.Core.UnitTests/DispatcherTests/AsynchronousDispatcherTests.cs +++ /dev/null @@ -1,109 +0,0 @@ -#pragma warning disable 169 -// ReSharper disable InconsistentNaming - -namespace EventStore.Core.UnitTests.DispatcherTests -{ - using System; - using System.Linq; - using System.Threading; - using Dispatcher; - using Machine.Specifications; - using Moq; - using Persistence; - using It = Machine.Specifications.It; - - [Subject("AsynchronousDispatchScheduler")] - public class when_instantiating_the_asynchronous_dispatch_scheduler - { - static readonly Guid streamId = Guid.NewGuid(); - private static readonly Commit[] commits = - { - new Commit(streamId, 0, Guid.NewGuid(), 0, SystemTime.UtcNow, null, null), - new Commit(streamId, 0, Guid.NewGuid(), 0, SystemTime.UtcNow, null, null) - }; - static readonly Mock dispatcher = new Mock(); - static readonly Mock persistence = new Mock(); - - Establish context = () => - { - persistence.Setup(x => x.Initialize()); - persistence.Setup(x => x.GetUndispatchedCommits()).Returns(commits); - dispatcher.Setup(x => x.Dispatch(commits.First())); - dispatcher.Setup(x => x.Dispatch(commits.Last())); - }; - - Because of = () => - new AsynchronousDispatchScheduler(dispatcher.Object, persistence.Object); - - It should_take_a_few_milliseconds_for_the_other_thread_to_execute = () => - Thread.Sleep(25); // just a precaution because we're doing async tests - - It should_initialize_the_persistence_engine = () => - persistence.Verify(x => x.Initialize(), Times.Once()); - - It should_get_the_set_of_undispatched_commits = () => - persistence.Verify(x => x.GetUndispatchedCommits(), Times.Once()); - - It should_provide_the_commits_to_the_dispatcher = () => - dispatcher.VerifyAll(); - } - - [Subject("AsynchronousDispatchScheduler")] - public class when_asynchronously_scheduling_a_commit_for_dispatch - { - static readonly Commit commit = new Commit(Guid.NewGuid(), 0, Guid.NewGuid(), 0, SystemTime.UtcNow, null, null); - static readonly Mock dispatcher = new Mock(); - static readonly Mock persistence = new Mock(); - static AsynchronousDispatchScheduler dispatchScheduler; - - Establish context = () => - { - dispatcher.Setup(x => x.Dispatch(commit)); - persistence.Setup(x => x.MarkCommitAsDispatched(commit)); - - dispatchScheduler = new AsynchronousDispatchScheduler(dispatcher.Object, persistence.Object); - }; - - Because of = () => - dispatchScheduler.ScheduleDispatch(commit); - - It should_take_a_few_milliseconds_for_the_other_thread_to_execute = () => - Thread.Sleep(25); // just a precaution because we're doing async tests - - It should_provide_the_commit_to_the_dispatcher = () => - dispatcher.Verify(x => x.Dispatch(commit), Times.Once()); - - It should_mark_the_commit_as_dispatched = () => - persistence.Verify(x => x.MarkCommitAsDispatched(commit), Times.Once()); - } - - [Subject("AsynchronousDispatchScheduler")] - public class when_disposing_the_async_dispatch_scheduler - { - static readonly Mock dispatcher = new Mock(); - static readonly Mock persistence = new Mock(); - static AsynchronousDispatchScheduler dispatchScheduler; - - Establish context = () => - { - dispatcher.Setup(x => x.Dispose()); - persistence.Setup(x => x.Dispose()); - dispatchScheduler = new AsynchronousDispatchScheduler(dispatcher.Object, persistence.Object); - }; - - Because of = () => - { - dispatchScheduler.Dispose(); - dispatchScheduler.Dispose(); - }; - - It should_dispose_the_underlying_dispatcher_exactly_once = () => - dispatcher.Verify(x => x.Dispose(), Times.Once()); - - It should_dispose_the_underlying_persistence_infrastructure_exactly_once = () => - dispatcher.Verify(x => x.Dispose(), Times.Once()); - } -} - -// ReSharper enable InconsistentNaming -#pragma warning restore 169 \ No newline at end of file diff --git a/src/tests/EventStore.Core.UnitTests/DispatcherTests/SynchronousDispatcherTests.cs b/src/tests/EventStore.Core.UnitTests/DispatcherTests/SynchronousDispatcherTests.cs deleted file mode 100644 index e701c0abe..000000000 --- a/src/tests/EventStore.Core.UnitTests/DispatcherTests/SynchronousDispatcherTests.cs +++ /dev/null @@ -1,102 +0,0 @@ -#pragma warning disable 169 -// ReSharper disable InconsistentNaming - -namespace EventStore.Core.UnitTests.DispatcherTests -{ - using System; - using System.Linq; - using Dispatcher; - using Machine.Specifications; - using Moq; - using Persistence; - using It = Machine.Specifications.It; - - [Subject("SynchronousDispatchScheduler")] - public class when_instantiating_the_synchronous_dispatch_scheduler - { - static readonly Guid streamId = Guid.NewGuid(); - private static readonly Commit[] commits = - { - new Commit(streamId, 0, Guid.NewGuid(), 0, SystemTime.UtcNow, null, null), - new Commit(streamId, 0, Guid.NewGuid(), 0, SystemTime.UtcNow, null, null) - }; - static readonly Mock dispatcher = new Mock(); - static readonly Mock persistence = new Mock(); - - Establish context = () => - { - persistence.Setup(x => x.Initialize()); - persistence.Setup(x => x.GetUndispatchedCommits()).Returns(commits); - dispatcher.Setup(x => x.Dispatch(commits.First())); - dispatcher.Setup(x => x.Dispatch(commits.Last())); - }; - - Because of = () => - new SynchronousDispatchScheduler(dispatcher.Object, persistence.Object); - - It should_initialize_the_persistence_engine = () => - persistence.Verify(x => x.Initialize(), Times.Once()); - - It should_get_the_set_of_undispatched_commits = () => - persistence.Verify(x => x.GetUndispatchedCommits(), Times.Once()); - - It should_provide_the_commits_to_the_dispatcher = () => - dispatcher.VerifyAll(); - } - - [Subject("SynchronousDispatchScheduler")] - public class when_synchronously_scheduling_a_commit_for_dispatch - { - static readonly Commit commit = new Commit(Guid.NewGuid(), 0, Guid.NewGuid(), 0, SystemTime.UtcNow, null, null); - static readonly Mock dispatcher = new Mock(); - static readonly Mock persistence = new Mock(); - static SynchronousDispatchScheduler dispatchScheduler; - - Establish context = () => - { - dispatcher.Setup(x => x.Dispatch(commit)); - persistence.Setup(x => x.MarkCommitAsDispatched(commit)); - - dispatchScheduler = new SynchronousDispatchScheduler(dispatcher.Object, persistence.Object); - }; - - Because of = () => - dispatchScheduler.ScheduleDispatch(commit); - - It should_provide_the_commit_to_the_dispatcher = () => - dispatcher.Verify(x => x.Dispatch(commit), Times.Once()); - - It should_mark_the_commit_as_dispatched = () => - persistence.Verify(x => x.MarkCommitAsDispatched(commit), Times.Once()); - } - - [Subject("SynchronousDispatchScheduler")] - public class when_disposing_the_synchronous_dispatch_scheduler - { - static readonly Mock dispatcher = new Mock(); - static readonly Mock persistence = new Mock(); - static SynchronousDispatchScheduler dispatchScheduler; - - Establish context = () => - { - dispatcher.Setup(x => x.Dispose()); - persistence.Setup(x => x.Dispose()); - dispatchScheduler = new SynchronousDispatchScheduler(dispatcher.Object, persistence.Object); - }; - - Because of = () => - { - dispatchScheduler.Dispose(); - dispatchScheduler.Dispose(); - }; - - It should_dispose_the_underlying_dispatcher_exactly_once = () => - dispatcher.Verify(x => x.Dispose(), Times.Once()); - - It should_dispose_the_underlying_persistence_infrastructure_exactly_once = () => - dispatcher.Verify(x => x.Dispose(), Times.Once()); - } -} - -// ReSharper enable InconsistentNaming -#pragma warning restore 169 \ No newline at end of file diff --git a/src/tests/EventStore.Core.UnitTests/EventStore.Core.UnitTests.csproj b/src/tests/EventStore.Core.UnitTests/EventStore.Core.UnitTests.csproj deleted file mode 100644 index bc59ad21b..000000000 --- a/src/tests/EventStore.Core.UnitTests/EventStore.Core.UnitTests.csproj +++ /dev/null @@ -1,91 +0,0 @@ - - - - Debug - AnyCPU - 9.0.30729 - 2.0 - {3B5F8277-F29E-4114-AE81-4A4FBF2D7FA5} - Library - Properties - EventStore.Core.UnitTests - EventStore.Core.UnitTests - v4.0 - 512 - false - ..\..\EventStore.snk - ..\..\..\src\ - true - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - Properties\GlobalAssemblyInfo.cs - - - Properties\GlobalSuppressions.cs - - - Properties\VersionAssemblyInfo.cs - - - - - - - - - - - - - - False - ..\..\packages\Machine.Specifications.0.5.8\lib\net40\Machine.Specifications.dll - - - False - ..\..\packages\Machine.Specifications.0.5.8\lib\net40\Machine.Specifications.Clr4.dll - - - ..\..\packages\Moq.4.0.10827\lib\NET40\Moq.dll - - - - - - Properties\CustomDictionary.xml - - - - - {D6413244-42F5-4233-B347-D0A804B09CC9} - EventStore.Core - - - {03946843-F343-419C-88EF-3E446D08DFA6} - EventStore - - - - - - - - \ No newline at end of file diff --git a/src/tests/EventStore.Core.UnitTests/ExtensionMethods.cs b/src/tests/EventStore.Core.UnitTests/ExtensionMethods.cs deleted file mode 100644 index 4d5794ba7..000000000 --- a/src/tests/EventStore.Core.UnitTests/ExtensionMethods.cs +++ /dev/null @@ -1,34 +0,0 @@ -namespace EventStore.Core.UnitTests -{ - using System.Collections; - using System.Collections.Generic; - - internal static class ExtensionMethods - { - public static int Events(this int events) - { - return events; - } - } - - public class EnumerableCounter : IEnumerable - { - private readonly IEnumerable enumerable; - public int GetEnumeratorCallCount { get; private set; } - - public EnumerableCounter(IEnumerable enumerable) - { - this.enumerable = enumerable; - this.GetEnumeratorCallCount = 0; - } - public IEnumerator GetEnumerator() - { - this.GetEnumeratorCallCount++; - return this.enumerable.GetEnumerator(); - } - IEnumerator IEnumerable.GetEnumerator() - { - return this.GetEnumerator(); - } - } -} \ No newline at end of file diff --git a/src/tests/EventStore.Core.UnitTests/OptimisticCommitHookTests.cs b/src/tests/EventStore.Core.UnitTests/OptimisticCommitHookTests.cs deleted file mode 100644 index 947ca78fa..000000000 --- a/src/tests/EventStore.Core.UnitTests/OptimisticCommitHookTests.cs +++ /dev/null @@ -1,214 +0,0 @@ -#pragma warning disable 169 -// ReSharper disable InconsistentNaming - -namespace EventStore.Core.UnitTests -{ - using System; - using System.Linq; - using Machine.Specifications; - using Persistence; - using It = Machine.Specifications.It; - - [Subject("OptimisticCommitHook")] - public class when_committing_with_a_sequence_beyond_the_known_end_of_a_stream : using_commit_hooks - { - const int HeadStreamRevision = 5; - const int HeadCommitSequence = 1; - const int ExpectedNextCommitSequence = HeadCommitSequence + 1; - const int BeyondEndOfStreamCommitSequence = ExpectedNextCommitSequence + 1; - static readonly Commit beyondEndOfStream = BuildCommitStub(HeadStreamRevision + 1, BeyondEndOfStreamCommitSequence); - static readonly Commit alreadyCommitted = BuildCommitStub(HeadStreamRevision, HeadCommitSequence); - static Exception thrown; - - Establish context = () => - hook.PostCommit(alreadyCommitted); - - Because of = () => - thrown = Catch.Exception(() => hook.PreCommit(beyondEndOfStream)); - - It should_throw_a_PersistenceException = () => - thrown.ShouldBeOfType(); - } - - [Subject("OptimisticCommitHook")] - public class when_committing_with_a_revision_beyond_the_known_end_of_a_stream : using_commit_hooks - { - const int HeadCommitSequence = 1; - const int HeadStreamRevision = 1; - const int NumberOfEventsBeingCommitted = 1; - const int ExpectedNextStreamRevision = HeadStreamRevision + 1 + NumberOfEventsBeingCommitted; - const int BeyondEndOfStreamRevision = ExpectedNextStreamRevision + 1; - static readonly Commit alreadyCommitted = BuildCommitStub(HeadStreamRevision, HeadCommitSequence); - static readonly Commit beyondEndOfStream = BuildCommitStub(BeyondEndOfStreamRevision, HeadCommitSequence + 1); - static Exception thrown; - - Establish context = () => - hook.PostCommit(alreadyCommitted); - - Because of = () => - thrown = Catch.Exception(() => hook.PreCommit(beyondEndOfStream)); - - It should_throw_a_PersistenceException = () => - thrown.ShouldBeOfType(); - } - - [Subject("OptimisticCommitHook")] - public class when_committing_with_a_sequence_less_or_equal_to_the_most_recent_sequence_for_the_stream : using_commit_hooks - { - const int HeadStreamRevision = 42; - const int HeadCommitSequence = 42; - const int DupliateCommitSequence = HeadCommitSequence; - static readonly Commit Committed = BuildCommitStub(HeadStreamRevision, HeadCommitSequence); - static readonly Commit Attempt = BuildCommitStub(HeadStreamRevision + 1, DupliateCommitSequence); - - static Exception thrown; - - Establish context = () => - hook.PostCommit(Committed); - - Because of = () => - thrown = Catch.Exception(() => hook.PreCommit(Attempt)); - - It should_throw_a_ConcurrencyException = () => - thrown.ShouldBeOfType(); - } - - [Subject("OptimisticCommitHook")] - public class when_committing_with_a_revision_less_or_equal_to_than_the_most_recent_revision_read_for_the_stream : using_commit_hooks - { - const int HeadStreamRevision = 3; - const int HeadCommitSequence = 2; - const int DuplicateStreamRevision = HeadStreamRevision; - static readonly Commit Committed = BuildCommitStub(HeadStreamRevision, HeadCommitSequence); - static readonly Commit FailedAttempt = BuildCommitStub(DuplicateStreamRevision, HeadCommitSequence + 1); - static Exception thrown; - - Establish context = () => - hook.PostCommit(Committed); - - Because of = () => - thrown = Catch.Exception(() => hook.PreCommit(FailedAttempt)); - - It should_throw_a_ConcurrencyException = () => - thrown.ShouldBeOfType(); - } - - [Subject("OptimisticCommitHook")] - public class when_committing_with_a_commit_sequence_less_than_or_equal_to_the_most_recent_commit_for_the_stream : using_commit_hooks - { - const int DuplicateCommitSequence = 1; - - static readonly Commit SuccessfulAttempt = BuildCommitStub(1, DuplicateCommitSequence); - static readonly Commit FailedAttempt = BuildCommitStub(2, DuplicateCommitSequence); - static Exception thrown; - - Establish context = () => - hook.PostCommit(SuccessfulAttempt); - - Because of = () => - thrown = Catch.Exception(() => hook.PreCommit(FailedAttempt)); - - It should_throw_a_ConcurrencyException = () => - thrown.ShouldBeOfType(); - } - - [Subject("OptimisticCommitHook")] - public class when_committing_with_a_stream_revision_less_than_or_equal_to_the_most_recent_commit_for_the_stream : using_commit_hooks - { - const int DuplicateStreamRevision = 2; - - static readonly Commit SuccessfulAttempt = BuildCommitStub(DuplicateStreamRevision, 1); - static readonly Commit FailedAttempt = BuildCommitStub(DuplicateStreamRevision, 2); - static Exception thrown; - - Establish context = () => - hook.PostCommit(SuccessfulAttempt); - - Because of = () => - thrown = Catch.Exception(() => hook.PreCommit(FailedAttempt)); - - It should_throw_a_ConcurrencyException = () => - thrown.ShouldBeOfType(); - } - - [Subject("CommitTracker")] - public class when_tracking_commits - { - const int MaxStreamsToTrack = 2; - static readonly Guid StreamId = Guid.NewGuid(); - static readonly Commit[] TrackedCommits = new[] - { - BuildCommit(Guid.NewGuid(), Guid.NewGuid()), - BuildCommit(Guid.NewGuid(), Guid.NewGuid()), - BuildCommit(Guid.NewGuid(), Guid.NewGuid()) - }; - - static OptimisticPipelineHook hook; - - Establish context = () => - hook = new OptimisticPipelineHook(MaxStreamsToTrack); - - Because of = () => - { - foreach (var commit in TrackedCommits) - hook.Track(commit); - }; - - It should_only_contain_streams_explicitly_tracked = () => - { - var untracked = BuildCommit(Guid.Empty, TrackedCommits[0].CommitId); - hook.Contains(untracked).ShouldBeFalse(); - }; - - It should_find_tracked_streams = () => - { - var stillTracked = BuildCommit(TrackedCommits.Last().StreamId, TrackedCommits.Last().CommitId); - hook.Contains(stillTracked).ShouldBeTrue(); - }; - - It should_only_track_the_specified_number_of_streams = () => - { - var droppedFromTracking = BuildCommit( - TrackedCommits.First().StreamId, TrackedCommits.First().CommitId); - hook.Contains(droppedFromTracking).ShouldBeFalse(); - }; - - private static Commit BuildCommit(Guid streamId, Guid commitId) - { - return new Commit(streamId, 0, commitId, 0, SystemTime.UtcNow, null, null); - } - } - - public abstract class using_commit_hooks - { - protected static Guid streamId = Guid.NewGuid(); - protected static OptimisticPipelineHook hook; - - Establish context = () => - { - streamId = Guid.NewGuid(); - hook = new OptimisticPipelineHook(); - }; - - Cleanup everything = () => - streamId = Guid.NewGuid(); - - protected static Commit BuildCommitStub(Guid commitId) - { - return new Commit(streamId, 1, commitId, 1, SystemTime.UtcNow, null, null); - } - protected static Commit BuildCommitStub(int streamRevision, int commitSequence) - { - var events = new[] { new EventMessage() }.ToList(); - return new Commit(streamId, streamRevision, Guid.NewGuid(), commitSequence, SystemTime.UtcNow, null, events); - } - protected static Commit BuildCommitStub(Guid commitId, int streamRevision, int commitSequence) - { - var events = new[] { new EventMessage() }.ToList(); - return new Commit(streamId, streamRevision, commitId, commitSequence, SystemTime.UtcNow, null, events); - } - } -} - -// ReSharper enable InconsistentNaming -#pragma warning restore 169 \ No newline at end of file diff --git a/src/tests/EventStore.Core.UnitTests/OptimisticEventStoreTests.cs b/src/tests/EventStore.Core.UnitTests/OptimisticEventStoreTests.cs deleted file mode 100644 index 43a67567e..000000000 --- a/src/tests/EventStore.Core.UnitTests/OptimisticEventStoreTests.cs +++ /dev/null @@ -1,418 +0,0 @@ -#pragma warning disable 169 -// ReSharper disable InconsistentNaming - -namespace EventStore.Core.UnitTests -{ - using System; - using System.Collections.Generic; - using System.Linq; - using Machine.Specifications; - using Moq; - using Persistence; - using It = Machine.Specifications.It; - - [Subject("OptimisticEventStore")] - public class when_creating_a_new_stream : using_persistence - { - static IEventStream stream; - - Because of = () => - stream = store.CreateStream(streamId); - - It should_return_a_new_stream = () => - stream.ShouldNotBeNull(); - - It should_return_a_stream_with_the_correct_stream_identifier = () => - stream.StreamId.ShouldEqual(streamId); - - It should_return_a_stream_with_a_zero_stream_revision = () => - stream.StreamRevision.ShouldEqual(0); - - It should_return_a_stream_with_a_zero_commit_sequence = () => - stream.CommitSequence.ShouldEqual(0); - - It should_return_a_stream_with_no_uncommitted_events = () => - stream.UncommittedEvents.ShouldBeEmpty(); - - It should_return_a_stream_with_no_committed_events = () => - stream.CommittedEvents.ShouldBeEmpty(); - - It should_return_a_stream_with_empty_headers = () => - stream.UncommittedHeaders.ShouldBeEmpty(); - } - - [Subject("OptimisticEventStore")] - public class when_opening_an_empty_stream_starting_at_revision_zero : using_persistence - { - static IEventStream stream; - - Establish context = () => - persistence.Setup(x => x.GetFrom(streamId, 0, 0)).Returns(new Commit[0]); - - Because of = () => - stream = store.OpenStream(streamId, 0, 0); - - It should_return_a_new_stream = () => - stream.ShouldNotBeNull(); - - It should_return_a_stream_with_the_correct_stream_identifier = () => - stream.StreamId.ShouldEqual(streamId); - - It should_return_a_stream_with_a_zero_stream_revision = () => - stream.StreamRevision.ShouldEqual(0); - - It should_return_a_stream_with_a_zero_commit_sequence = () => - stream.CommitSequence.ShouldEqual(0); - - It should_return_a_stream_with_no_uncommitted_events = () => - stream.UncommittedEvents.ShouldBeEmpty(); - - It should_return_a_stream_with_no_committed_events = () => - stream.CommittedEvents.ShouldBeEmpty(); - - It should_return_a_stream_with_empty_headers = () => - stream.UncommittedHeaders.ShouldBeEmpty(); - } - - [Subject("OptimisticEventStore")] - public class when_opening_an_empty_stream_starting_above_revision_zero : using_persistence - { - const int MinRevision = 1; - static Exception thrown; - - Establish context = () => - persistence.Setup(x => x.GetFrom(streamId, MinRevision, int.MaxValue)).Returns(new Commit[0]); - - Because of = () => - thrown = Catch.Exception(() => store.OpenStream(streamId, MinRevision, int.MaxValue)); - - It should_throw_a_StreamNotFoundException = () => - thrown.ShouldBeOfType(); - } - - [Subject("OptimisticEventStore")] - public class when_opening_a_populated_stream : using_persistence - { - const int MinRevision = 17; - const int MaxRevision = 42; - static readonly Commit Committed = BuildCommitStub(MinRevision, 1); - static IEventStream stream; - - Establish context = () => - { - persistence.Setup(x => x.GetFrom(streamId, MinRevision, MaxRevision)).Returns(new[] { Committed }); - pipelineHooks.Add(new Mock()); - pipelineHooks[0].Setup(x => x.Select(Committed)).Returns(Committed); - }; - - Because of = () => - stream = store.OpenStream(streamId, MinRevision, MaxRevision); - - It should_invoke_the_underlying_infrastructure_with_the_values_provided = () => - persistence.Verify(x => x.GetFrom(streamId, MinRevision, MaxRevision), Times.Once()); - - It should_provide_the_commits_to_the_selection_hooks = () => - pipelineHooks.ForEach(x => x.Verify(hook => hook.Select(Committed), Times.Once())); - - It should_return_an_event_stream_containing_the_correct_stream_identifer = () => - stream.StreamId.ShouldEqual(streamId); - } - - [Subject("OptimisticEventStore")] - public class when_opening_a_populated_stream_from_a_snapshot : using_persistence - { - const int MaxRevision = int.MaxValue; - static readonly Snapshot snapshot = new Snapshot(streamId, 42, "snapshot"); - static readonly Commit[] Committed = new[] { BuildCommitStub(42, 0) }; - - Establish context = () => - persistence.Setup(x => x.GetFrom(streamId, 42, MaxRevision)).Returns(Committed); - - Because of = () => - store.OpenStream(snapshot, MaxRevision); - - It should_query_the_underlying_storage_using_the_revision_of_the_snapshot = () => - persistence.Verify(x => x.GetFrom(streamId, 42, MaxRevision), Times.Once()); - } - - [Subject("OptimisticEventStore")] - public class when_opening_a_stream_from_a_snapshot_that_is_at_the_revision_of_the_stream_head : using_persistence - { - const int HeadStreamRevision = 42; - const int HeadCommitSequence = 15; - static readonly Snapshot snapshot = new Snapshot(streamId, HeadStreamRevision, "snapshot"); - static readonly EnumerableCounter Committed = new EnumerableCounter( - new[] { BuildCommitStub(HeadStreamRevision, HeadCommitSequence) }); - static IEventStream stream; - - Establish context = () => - persistence.Setup(x => x.GetFrom(streamId, HeadStreamRevision, int.MaxValue)).Returns(Committed); - - Because of = () => - stream = store.OpenStream(snapshot, int.MaxValue); - - It should_return_a_stream_with_the_correct_stream_identifier = () => - stream.StreamId.ShouldEqual(streamId); - - It should_return_a_stream_with_revision_of_the_stream_head = () => - stream.StreamRevision.ShouldEqual(HeadStreamRevision); - - It should_return_a_stream_with_a_commit_sequence_of_the_stream_head = () => - stream.CommitSequence.ShouldEqual(HeadCommitSequence); - - It should_return_a_stream_with_no_committed_events = () => - stream.CommittedEvents.Count.ShouldEqual(0); - - It should_return_a_stream_with_no_uncommitted_events = () => - stream.UncommittedEvents.Count.ShouldEqual(0); - - It should_only_enumerate_the_set_of_commits_once = () => - Committed.GetEnumeratorCallCount.ShouldEqual(1); - } - - [Subject("OptimisticEventStore")] - public class when_reading_from_revision_zero : using_persistence - { - Establish context = () => - persistence.Setup(x => x.GetFrom(streamId, 0, int.MaxValue)).Returns(new Commit[] { }); - - Because of = () => - ((ICommitEvents)store).GetFrom(streamId, 0, int.MaxValue).ToList(); - - It should_pass_a_revision_range_to_the_persistence_infrastructure = () => - persistence.Verify(x => x.GetFrom(streamId, 0, int.MaxValue), Times.Once()); - } - - [Subject("OptimisticEventStore")] - public class when_reading_up_to_revision_revision_zero : using_persistence - { - static readonly Commit Committed = BuildCommitStub(1, 1); - - Establish context = () => persistence - .Setup(x => x.GetFrom(streamId, 0, int.MaxValue)) - .Returns(new[] { Committed }); - - Because of = () => - store.OpenStream(streamId, 0, 0); - - It should_pass_the_maximum_possible_revision_to_the_persistence_infrastructure = () => - persistence.Verify(x => x.GetFrom(streamId, 0, int.MaxValue), Times.Once()); - } - - [Subject("OptimisticEventStore")] - public class when_reading_from_a_null_snapshot : using_persistence - { - static Exception thrown; - - Because of = () => - thrown = Catch.Exception(() => store.OpenStream(null, int.MaxValue)); - - It should_throw_an_ArgumentNullException = () => - thrown.ShouldBeOfType(); - } - - [Subject("OptimisticEventStore")] - public class when_reading_from_a_snapshot_up_to_revision_revision_zero : using_persistence - { - static readonly Snapshot snapshot = new Snapshot(streamId, 1, "snapshot"); - static readonly Commit Committed = BuildCommitStub(1, 1); - - Establish context = () => persistence - .Setup(x => x.GetFrom(streamId, snapshot.StreamRevision, int.MaxValue)) - .Returns(new[] { Committed }); - - Because of = () => - store.OpenStream(snapshot, 0); - - It should_pass_the_maximum_possible_revision_to_the_persistence_infrastructure = () => - persistence.Verify(x => x.GetFrom(streamId, snapshot.StreamRevision, int.MaxValue), Times.Once()); - } - - [Subject("OptimisticEventStore")] - public class when_committing_a_null_attempt_back_to_the_stream : using_persistence - { - static Exception thrown; - - Because of = () => - thrown = Catch.Exception(() => ((ICommitEvents)store).Commit(null)); - - It should_throw_an_ArgumentNullException = () => - thrown.ShouldBeOfType(); - } - - [Subject("OptimisticEventStore")] - public class when_committing_with_an_unidentified_attempt_back_to_the_stream : using_persistence - { - static readonly Guid emptyIdentifier = Guid.Empty; - static readonly Commit unidentified = BuildCommitStub(emptyIdentifier); - static Exception thrown; - - Because of = () => - thrown = Catch.Exception(() => ((ICommitEvents)store).Commit(unidentified)); - - It should_throw_an_ArgumentException = () => - thrown.ShouldBeOfType(); - } - - [Subject("OptimisticEventStore")] - public class when_the_number_of_commits_is_greater_than_the_number_of_revisions : using_persistence - { - const int StreamRevision = 1; - const int CommitSequence = 2; // should never be greater than StreamRevision. - static readonly Commit corrupt = BuildCommitStub(StreamRevision, CommitSequence); - static Exception thrown; - - Because of = () => - thrown = Catch.Exception(() => ((ICommitEvents)store).Commit(corrupt)); - - It should_throw_a_StorageException = () => - thrown.ShouldBeOfType(); - } - - [Subject("OptimisticEventStore")] - public class when_committing_with_a_nonpositive_commit_sequence_back_to_the_stream : using_persistence - { - const int StreamRevision = 1; - const int InvalidCommitSequence = 0; - static readonly Commit invalidCommitSequence = BuildCommitStub(StreamRevision, InvalidCommitSequence); - static Exception thrown; - - Because of = () => - thrown = Catch.Exception(() => ((ICommitEvents)store).Commit(invalidCommitSequence)); - - It should_throw_an_ArgumentException = () => - thrown.ShouldBeOfType(); - } - - [Subject("OptimisticEventStore")] - public class when_committing_with_a_non_positive_stream_revision_back_to_the_stream : using_persistence - { - const int InvalidStreamRevision = 0; - const int CommitSequence = 1; - static readonly Commit invalidStreamRevision = BuildCommitStub(InvalidStreamRevision, CommitSequence); - static Exception thrown; - - Because of = () => - thrown = Catch.Exception(() => ((ICommitEvents)store).Commit(invalidStreamRevision)); - - It should_throw_an_ArgumentException = () => - thrown.ShouldBeOfType(); - } - - [Subject("OptimisticEventStore")] - public class when_committing_an_empty_attempt_to_a_stream : using_persistence - { - static readonly Commit attemptWithNoEvents = BuildCommitStub(Guid.NewGuid()); - - Establish context = () => - persistence.Setup(x => x.Commit(attemptWithNoEvents)); - - Because of = () => - ((ICommitEvents)store).Commit(attemptWithNoEvents); - - It should_drop_the_commit_provided = () => - persistence.Verify(x => x.Commit(attemptWithNoEvents), Times.AtMost(0)); - } - - [Subject("OptimisticEventStore")] - public class when_committing_with_a_valid_and_populated_attempt_to_a_stream : using_persistence - { - static readonly Commit populatedAttempt = BuildCommitStub(1, 1); - - Establish context = () => - { - persistence.Setup(x => x.Commit(populatedAttempt)); - - pipelineHooks.Add(new Mock()); - pipelineHooks[0].Setup(x => x.PreCommit(populatedAttempt)).Returns(true); - pipelineHooks[0].Setup(x => x.PostCommit(populatedAttempt)); - }; - - Because of = () => - ((ICommitEvents)store).Commit(populatedAttempt); - - It should_provide_the_commit_to_the_precommit_hooks = () => - pipelineHooks.ForEach(x => x.Verify(hook => hook.PreCommit(populatedAttempt), Times.Once())); - - It should_provide_the_commit_attempt_to_the_configured_persistence_mechanism = () => - persistence.Verify(x => x.Commit(populatedAttempt), Times.Once()); - - It should_provide_the_commit_to_the_postcommit_hooks = () => - pipelineHooks.ForEach(x => x.Verify(hook => hook.PostCommit(populatedAttempt), Times.Once())); - } - - [Subject("OptimisticEventStore")] - public class when_a_precommit_hook_rejects_a_commit : using_persistence - { - static readonly Commit attempt = BuildCommitStub(1, 1); - - Establish context = () => - { - pipelineHooks.Add(new Mock()); - pipelineHooks[0].Setup(x => x.PreCommit(attempt)).Returns(false); - }; - - Because of = () => - ((ICommitEvents)store).Commit(attempt); - - It should_not_call_the_underlying_infrastructure = () => - persistence.Verify(x => x.Commit(attempt), Times.Never()); - - It should_not_provide_the_commit_to_the_postcommit_hooks = () => - pipelineHooks.ForEach(x => x.Verify(y => y.PostCommit(attempt), Times.Never())); - } - - [Subject("OptimisticEventStore")] - public class when_accessing_the_underlying_persistence : using_persistence - { - It should_return_a_reference_to_the_underlying_persistence_infrastructure = () => - store.Advanced.ShouldBeTheSameAs(persistence.Object); - } - - [Subject("OptimisticEventStore")] - public class when_disposing_the_event_store : using_persistence - { - Because of = () => - store.Dispose(); - - It should_dispose_the_underlying_persistence = () => - persistence.Verify(x => x.Dispose(), Times.Once()); - } - - public abstract class using_persistence - { - protected static Guid streamId = Guid.NewGuid(); - protected static Mock persistence; - protected static OptimisticEventStore store; - protected static List> pipelineHooks; - - Establish context = () => - { - persistence = new Mock(); - pipelineHooks = new List>(); - - store = new OptimisticEventStore(persistence.Object, pipelineHooks.Select(x => x.Object)); - }; - - Cleanup everything = () => - streamId = Guid.NewGuid(); - - protected static Commit BuildCommitStub(Guid commitId) - { - return new Commit(streamId, 1, commitId, 1, SystemTime.UtcNow, null, null); - } - protected static Commit BuildCommitStub(int streamRevision, int commitSequence) - { - var events = new[] { new EventMessage() }.ToList(); - return new Commit(streamId, streamRevision, Guid.NewGuid(), commitSequence, SystemTime.UtcNow, null, events); - } - protected static Commit BuildCommitStub(Guid commitId, int streamRevision, int commitSequence) - { - var events = new[] { new EventMessage() }.ToList(); - return new Commit(streamId, streamRevision, commitId, commitSequence, SystemTime.UtcNow, null, events); - } - } -} - -// ReSharper enable InconsistentNaming -#pragma warning restore 169 \ No newline at end of file diff --git a/src/tests/EventStore.Core.UnitTests/OptimisticEventStreamTests.cs b/src/tests/EventStore.Core.UnitTests/OptimisticEventStreamTests.cs deleted file mode 100644 index a1515f63d..000000000 --- a/src/tests/EventStore.Core.UnitTests/OptimisticEventStreamTests.cs +++ /dev/null @@ -1,377 +0,0 @@ -#pragma warning disable 169 -// ReSharper disable InconsistentNaming - -namespace EventStore.Core.UnitTests -{ - using System; - using System.Collections.Generic; - using System.Linq; - using Machine.Specifications; - using Moq; - using It = Machine.Specifications.It; - - [Subject("OptimisticEventStream")] - public class when_building_a_stream : on_the_event_stream - { - const int MinRevision = 2; - const int MaxRevision = 7; - static readonly int EachCommitHas = 2.Events(); - static readonly Commit[] Committed = new[] - { - BuildCommitStub(2, 1, EachCommitHas), // 1-2 - BuildCommitStub(4, 2, EachCommitHas), // 3-4 - BuildCommitStub(6, 3, EachCommitHas), // 5-6 - BuildCommitStub(8, 3, EachCommitHas) // 7-8 - }; - - Establish context = () => - { - Committed[0].Headers["Common"] = string.Empty; - Committed[1].Headers["Common"] = string.Empty; - Committed[2].Headers["Common"] = string.Empty; - Committed[3].Headers["Common"] = string.Empty; - Committed[0].Headers["Unique"] = string.Empty; - - persistence.Setup(x => x.GetFrom(streamId, MinRevision, MaxRevision)).Returns(Committed); - }; - - Because of = () => - stream = new OptimisticEventStream(streamId, persistence.Object, MinRevision, MaxRevision); - - It should_have_the_correct_stream_identifier = () => - stream.StreamId.ShouldEqual(streamId); - - It should_have_the_correct_head_stream_revision = () => - stream.StreamRevision.ShouldEqual(MaxRevision); - - It should_have_the_correct_head_commit_sequence = () => - stream.CommitSequence.ShouldEqual(Committed.Last().CommitSequence); - - It should_not_include_events_below_the_minimum_revision_indicated = () => - stream.CommittedEvents.First().ShouldEqual(Committed.First().Events.Last()); - - It should_not_include_events_above_the_maximum_revision_indicated = () => - stream.CommittedEvents.Last().ShouldEqual(Committed.Last().Events.First()); - - It should_have_all_of_the_committed_events_up_to_the_stream_revision_specified = () => - stream.CommittedEvents.Count.ShouldEqual(MaxRevision - MinRevision + 1); - - It should_contain_the_headers_from_the_underlying_commits = () => - stream.CommittedHeaders.Count.ShouldEqual(2); - } - - [Subject("OptimisticEventStream")] - public class when_the_head_event_revision_is_less_than_the_max_desired_revision : on_the_event_stream - { - static readonly int EventsPerCommit = 2.Events(); - static readonly Commit[] Committed = new[] - { - BuildCommitStub(2, 1, EventsPerCommit), // 1-2 - BuildCommitStub(4, 2, EventsPerCommit), // 3-4 - BuildCommitStub(6, 3, EventsPerCommit), // 5-6 - BuildCommitStub(8, 3, EventsPerCommit) // 7-8 - }; - - Establish context = () => - persistence.Setup(x => x.GetFrom(streamId, 0, int.MaxValue)).Returns(Committed); - - Because of = () => - stream = new OptimisticEventStream(streamId, persistence.Object, 0, int.MaxValue); - - It should_set_the_stream_revision_to_the_revision_of_the_most_recent_event = () => - stream.StreamRevision.ShouldEqual(Committed.Last().StreamRevision); - } - - [Subject("OptimisticEventStream")] - public class when_adding_a_null_event_message : on_the_event_stream - { - Because of = () => - stream.Add(null); - - It should_be_ignored = () => - stream.UncommittedEvents.ShouldBeEmpty(); - } - - [Subject("OptimisticEventStream")] - public class when_adding_an_unpopulated_event_message : on_the_event_stream - { - Because of = () => - stream.Add(new EventMessage { Body = null }); - - It should_be_ignored = () => - stream.UncommittedEvents.ShouldBeEmpty(); - } - - [Subject("OptimisticEventStream")] - public class when_adding_a_fully_populated_event_message : on_the_event_stream - { - Because of = () => - stream.Add(new EventMessage { Body = "populated" }); - - It should_add_the_event_to_the_set_of_uncommitted_events = () => - stream.UncommittedEvents.Count.ShouldEqual(1); - } - - [Subject("OptimisticEventStream")] - public class when_adding_multiple_populated_event_messages : on_the_event_stream - { - Because of = () => - { - stream.Add(new EventMessage { Body = "populated" }); - stream.Add(new EventMessage { Body = "also populated" }); - }; - - It should_add_all_of_the_events_provided_to_the_set_of_uncommitted_events = () => - stream.UncommittedEvents.Count.ShouldEqual(2); - } - - [Subject("OptimisticEventStream")] - public class when_adding_a_simple_object_as_an_event_message : on_the_event_stream - { - const string MyEvent = "some event data"; - - Because of = () => - stream.Add(new EventMessage { Body = MyEvent }); - - It should_add_the_uncommited_event_to_the_set_of_uncommitted_events = () => - stream.UncommittedEvents.Count.ShouldEqual(1); - - It should_wrap_the_uncommited_event_in_an_EventMessage_object = () => - stream.UncommittedEvents.First().Body.ShouldEqual(MyEvent); - } - - [Subject("OptimisticEventStream")] - public class when_clearing_any_uncommitted_changes : on_the_event_stream - { - Establish context = () => - stream.Add(new EventMessage { Body = string.Empty }); - - Because of = () => - stream.ClearChanges(); - - It should_clear_all_uncommitted_events = () => - stream.UncommittedEvents.Count.ShouldEqual(0); - } - - [Subject("OptimisticEventStream")] - public class when_committing_an_empty_changeset : on_the_event_stream - { - Because of = () => - stream.CommitChanges(Guid.NewGuid()); - - It should_not_call_the_underlying_infrastructure = () => - persistence.Verify(x => x.Commit(Moq.It.IsAny()), Times.Never()); - - It should_not_increment_the_current_stream_revision = () => - stream.StreamRevision.ShouldEqual(0); - - It should_not_increment_the_current_commit_sequence = () => - stream.CommitSequence.ShouldEqual(0); - } - - [Subject("OptimisticEventStream")] - public class when_committing_any_uncommitted_changes : on_the_event_stream - { - static readonly Guid commitId = Guid.NewGuid(); - static readonly EventMessage uncommitted = new EventMessage { Body = string.Empty }; - static readonly Dictionary headers = new Dictionary { { "key", "value" } }; - static Commit constructed; - - Establish context = () => - { - persistence.Setup(x => x.Commit(Moq.It.IsAny())).Callback(x => constructed = x); - stream.Add(uncommitted); - foreach (var item in headers) - stream.UncommittedHeaders[item.Key] = item.Value; - }; - - Because of = () => - stream.CommitChanges(commitId); - - It should_provide_a_commit_to_the_underlying_infrastructure = () => - persistence.Verify(x => x.Commit(Moq.It.IsAny()), Times.Once()); - - It should_build_the_commit_with_the_correct_stream_identifier = () => - constructed.StreamId.ShouldEqual(streamId); - - It should_build_the_commit_with_the_correct_stream_revision = () => - constructed.StreamRevision.ShouldEqual(DefaultStreamRevision); - - It should_build_the_commit_with_the_correct_commit_identifier = () => - constructed.CommitId.ShouldEqual(commitId); - - It should_build_the_commit_with_an_incremented_commit_sequence = () => - constructed.CommitSequence.ShouldEqual(DefaultCommitSequence); - - It should_build_the_commit_with_the_correct_commit_stamp = () => - SystemTime.UtcNow.ShouldEqual(constructed.CommitStamp); - - It should_build_the_commit_with_the_headers_provided = () => - constructed.Headers[headers.First().Key].ShouldEqual(headers.First().Value); - - It should_build_the_commit_containing_all_uncommitted_events = () => - constructed.Events.Count.ShouldEqual(headers.Count); - - It should_build_the_commit_using_the_event_messages_provided = () => - constructed.Events.First().ShouldEqual(uncommitted); - - It should_contain_a_copy_of_the_headers_provided = () => - constructed.Headers.ShouldNotBeEmpty(); - - It should_update_the_stream_revision = () => - stream.StreamRevision.ShouldEqual(constructed.StreamRevision); - - It should_update_the_commit_sequence = () => - stream.CommitSequence.ShouldEqual(constructed.CommitSequence); - - It should_add_the_uncommitted_events_the_committed_events = () => - stream.CommittedEvents.Last().ShouldEqual(uncommitted); - - It should_clear_the_uncommitted_events_on_the_stream = () => - stream.UncommittedEvents.ShouldBeEmpty(); - - It should_clear_the_uncommitted_headers_on_the_stream = () => - stream.UncommittedHeaders.ShouldBeEmpty(); - - It should_copy_the_uncommitted_headers_to_the_committed_stream_headers = () => - stream.CommittedHeaders.Count.ShouldEqual(headers.Count); - } - - /// - /// This behavior is primarily to support a NoSQL storage solution where CommitId is not being used as the "primary key" - /// in a NoSQL environment, we'll most likely use StreamId + CommitSequence, which also enables optimistic concurrency. - /// - [Subject("OptimisticEventStream")] - public class when_committing_with_an_identifier_that_was_previously_read : on_the_event_stream - { - static readonly Commit[] Committed = new[] { BuildCommitStub(1, 1, 1) }; - static readonly Guid DupliateCommitId = Committed[0].CommitId; - static Exception thrown; - - Establish context = () => - { - persistence - .Setup(x => x.GetFrom(streamId, 0, int.MaxValue)) - .Returns(Committed); - - stream = new OptimisticEventStream( - streamId, persistence.Object, 0, int.MaxValue); - }; - - Because of = () => - thrown = Catch.Exception(() => stream.CommitChanges(DupliateCommitId)); - - It should_throw_a_DuplicateCommitException = () => - thrown.ShouldBeOfType(); - } - - [Subject("OptimisticEventStream")] - public class when_committing_after_another_thread_or_process_has_moved_the_stream_head : on_the_event_stream - { - const int StreamRevision = 1; - private static readonly Commit[] Committed = new[] { BuildCommitStub(1, 1, 1) }; - static readonly EventMessage uncommitted = new EventMessage { Body = string.Empty }; - static readonly Commit[] DiscoveredOnCommit = new[] { BuildCommitStub(3, 2, 2) }; - static Commit constructed; - static Exception thrown; - - Establish context = () => - { - persistence - .Setup(x => x.Commit(Moq.It.IsAny())) - .Throws(new ConcurrencyException()); - persistence - .Setup(x => x.GetFrom(streamId, StreamRevision, int.MaxValue)) - .Returns(Committed); - persistence - .Setup(x => x.GetFrom(streamId, StreamRevision + 1, int.MaxValue)) - .Returns(DiscoveredOnCommit); - - stream = new OptimisticEventStream(streamId, persistence.Object, StreamRevision, int.MaxValue); - stream.Add(uncommitted); - }; - - Because of = () => - thrown = Catch.Exception(() => stream.CommitChanges(Guid.NewGuid())); - - It should_throw_a_ConcurrencyException = () => - thrown.ShouldBeOfType(); - - It should_query_the_underlying_storage_to_discover_the_new_commits = () => - persistence.Verify(x => x.GetFrom(streamId, StreamRevision + 1, int.MaxValue), Times.Once()); - - It should_update_the_stream_revision_accordingly = () => - stream.StreamRevision.ShouldEqual(DiscoveredOnCommit[0].StreamRevision); - - It should_update_the_commit_sequence_accordingly = () => - stream.CommitSequence.ShouldEqual(DiscoveredOnCommit[0].CommitSequence); - - It should_add_the_newly_discovered_committed_events_to_the_set_of_committed_events_accordingly = () => - stream.CommittedEvents.Count.ShouldEqual(DiscoveredOnCommit[0].Events.Count + 1); - } - - [Subject("OptimisticEventStream")] - public class when_attempting_to_invoke_behavior_on_a_disposed_stream : on_the_event_stream - { - static Exception thrown; - - Establish context = () => - stream.Dispose(); - - Because of = () => - thrown = Catch.Exception(() => stream.CommitChanges(Guid.NewGuid())); - - It should_throw_a_ObjectDisposedException = () => - thrown.ShouldBeOfType(); - } - - [Subject("OptimisticEventStream")] - public class when_attempting_to_modify_the_event_collections : on_the_event_stream - { - It should_throw_an_exception_when_adding_to_the_committed_collection = () => - Catch.Exception(() => stream.CommittedEvents.Add(null)).ShouldBeOfType(); - It should_throw_an_exception_when_adding_to_the_uncommitted_collection = () => - Catch.Exception(() => stream.UncommittedEvents.Add(null)).ShouldBeOfType(); - - It should_throw_an_exception_when_clearing_the_committed_collection = () => - Catch.Exception(() => stream.CommittedEvents.Clear()).ShouldBeOfType(); - It should_throw_an_exception_when_clearing_the_uncommitted_collection = () => - Catch.Exception(() => stream.UncommittedEvents.Clear()).ShouldBeOfType(); - - It should_throw_an_exception_when_removing_from_the_committed_collection = () => - Catch.Exception(() => stream.CommittedEvents.Remove(null)).ShouldBeOfType(); - It should_throw_an_exception_when_removing_from_the_uncommitted_collection = () => - Catch.Exception(() => stream.UncommittedEvents.Remove(null)).ShouldBeOfType(); - } - - public abstract class on_the_event_stream - { - protected const int DefaultStreamRevision = 1; - protected const int DefaultCommitSequence = 1; - protected static Guid streamId = Guid.NewGuid(); - protected static OptimisticEventStream stream; - protected static Mock persistence; - - Establish context = () => - { - persistence = new Mock(); - stream = new OptimisticEventStream(streamId, persistence.Object); - SystemTime.Resolver = () => new DateTime(2012, 1, 1, 13, 0, 0); - }; - - Cleanup cleanup = () => - streamId = Guid.NewGuid(); - - protected static Commit BuildCommitStub(int revision, int sequence, int eventCount) - { - var events = new List(eventCount); - for (var i = 0; i < eventCount; i++) - events.Add(new EventMessage()); - - return new Commit(streamId, revision, Guid.NewGuid(), sequence, SystemTime.UtcNow, null, events); - } - } -} - -// ReSharper enable InconsistentNaming -#pragma warning restore 169 \ No newline at end of file diff --git a/src/tests/EventStore.Core.UnitTests/Properties/AssemblyInfo.cs b/src/tests/EventStore.Core.UnitTests/Properties/AssemblyInfo.cs deleted file mode 100644 index 54d37d275..000000000 --- a/src/tests/EventStore.Core.UnitTests/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using System.Reflection; -using System.Runtime.InteropServices; - -[assembly: AssemblyTitle("EventStore.Core.UnitTests")] -[assembly: AssemblyDescription("")] -[assembly: Guid("c866499a-e56f-436d-a58f-2de993c0fa09")] - -[assembly: SuppressMessage("Microsoft.Design", "CA2210:AssembliesShouldHaveValidStrongNames", - Justification = "Machine.Specifications is not signed, therefore this assembly cannot be signed.")] \ No newline at end of file diff --git a/src/tests/EventStore.Core.UnitTests/packages.config b/src/tests/EventStore.Core.UnitTests/packages.config deleted file mode 100644 index 64dd9ce44..000000000 --- a/src/tests/EventStore.Core.UnitTests/packages.config +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/src/tests/EventStore.Persistence.AcceptanceTests/App.config b/src/tests/EventStore.Persistence.AcceptanceTests/App.config deleted file mode 100644 index 44b325655..000000000 --- a/src/tests/EventStore.Persistence.AcceptanceTests/App.config +++ /dev/null @@ -1,73 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/tests/EventStore.Persistence.AcceptanceTests/ConfigurationExtensions.cs b/src/tests/EventStore.Persistence.AcceptanceTests/ConfigurationExtensions.cs deleted file mode 100644 index 6a00916cd..000000000 --- a/src/tests/EventStore.Persistence.AcceptanceTests/ConfigurationExtensions.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace EventStore.Persistence.AcceptanceTests -{ - using System; - using System.Configuration; - using System.Linq; - - internal static class ConfigurationExtensions - { - public static string GetSetting(this string settingName) - { - return GetCommandLineArgument("/" + settingName + ":") - ?? Environment.GetEnvironmentVariable(settingName) - ?? ConfigurationManager.AppSettings[settingName]; - } - private static string GetCommandLineArgument(string settingName) - { - return Environment.GetCommandLineArgs() - .Where(arg => arg.StartsWith(settingName)) - .Select(arg => arg.Replace(settingName, string.Empty)) - .FirstOrDefault(); - } - } -} \ No newline at end of file diff --git a/src/tests/EventStore.Persistence.AcceptanceTests/Engines/AcceptanceTestMongoPersistenceFactory.cs b/src/tests/EventStore.Persistence.AcceptanceTests/Engines/AcceptanceTestMongoPersistenceFactory.cs deleted file mode 100644 index e2ba2e3b2..000000000 --- a/src/tests/EventStore.Persistence.AcceptanceTests/Engines/AcceptanceTestMongoPersistenceFactory.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace EventStore.Persistence.AcceptanceTests.Engines -{ - using MongoPersistence; - using Serialization; - - public class AcceptanceTestMongoPersistenceFactory : MongoPersistenceFactory - { - public AcceptanceTestMongoPersistenceFactory() - : base("Mongo", new DocumentObjectSerializer()) - { - } - protected override string TransformConnectionString(string connectionString) - { - return connectionString - .Replace("[HOST]", "host".GetSetting() ?? "localhost") - .Replace("[PORT]", "port".GetSetting() ?? string.Empty) - .Replace("[DATABASE]", "database".GetSetting() ?? "EventStore") - .Replace("[USER]", "user".GetSetting() ?? string.Empty) - .Replace("[PASSWORD]", "password".GetSetting() ?? string.Empty); - } - } -} \ No newline at end of file diff --git a/src/tests/EventStore.Persistence.AcceptanceTests/Engines/AcceptanceTestRavenPersistenceFactory.cs b/src/tests/EventStore.Persistence.AcceptanceTests/Engines/AcceptanceTestRavenPersistenceFactory.cs deleted file mode 100644 index bd5428c3a..000000000 --- a/src/tests/EventStore.Persistence.AcceptanceTests/Engines/AcceptanceTestRavenPersistenceFactory.cs +++ /dev/null @@ -1,34 +0,0 @@ - - -namespace EventStore.Persistence.AcceptanceTests.Engines -{ - using System.Transactions; - using Serialization; - using Persistence.RavenPersistence; - - public class AcceptanceTestRavenPersistenceFactory : RavenPersistenceFactory - { - public static RavenConfiguration GetDefaultConfig() - { - return new RavenConfiguration - { - Serializer = new DocumentObjectSerializer(), - ScopeOption = TransactionScopeOption.Suppress, - ConsistentQueries = true, // helps tests pass consistently - RequestedPageSize = int.Parse("pageSize".GetSetting() ?? "10"), // smaller values help bring out bugs - MaxServerPageSize = int.Parse("serverPageSize".GetSetting() ?? "1024"), // raven default - ConnectionName = "Raven" - }; - } - - public AcceptanceTestRavenPersistenceFactory() - : base(GetDefaultConfig()) - { - } - - public AcceptanceTestRavenPersistenceFactory(RavenConfiguration config) - : base(config) - { - } - } -} \ No newline at end of file diff --git a/src/tests/EventStore.Persistence.AcceptanceTests/Engines/AcceptanceTestSqlPersistenceFactory.cs b/src/tests/EventStore.Persistence.AcceptanceTests/Engines/AcceptanceTestSqlPersistenceFactory.cs deleted file mode 100644 index 1fe4c30c3..000000000 --- a/src/tests/EventStore.Persistence.AcceptanceTests/Engines/AcceptanceTestSqlPersistenceFactory.cs +++ /dev/null @@ -1,78 +0,0 @@ -namespace EventStore.Persistence.AcceptanceTests.Engines -{ - using System; - using System.Configuration; - using Serialization; - using SqlPersistence; - - public abstract class AcceptanceTestSqlPersistenceFactory : SqlPersistenceFactory - { - protected AcceptanceTestSqlPersistenceFactory(string connectionName) - : this(new TransformConfigConnectionFactory(connectionName), new BinarySerializer(), connectionName) - { - } - private AcceptanceTestSqlPersistenceFactory(IConnectionFactory factory, ISerialize serializer, string connectionName) - : base(factory, serializer, ResolveDialect(new ConfigurationConnectionFactory(connectionName).Settings)) - { - var pageSize = "pageSize".GetSetting(); - - if (!string.IsNullOrEmpty(pageSize)) - this.PageSize = int.Parse(pageSize); - } - } - - public class TransformConfigConnectionFactory : ConfigurationConnectionFactory - { - public TransformConfigConnectionFactory(string connectionName) - : base(connectionName) - { - } - - protected override string BuildConnectionString(Guid streamId, ConnectionStringSettings setting) - { - return setting.ConnectionString - .Replace("[HOST]", "host".GetSetting() ?? "localhost") - .Replace("[PORT]", "port".GetSetting() ?? string.Empty) - .Replace("[DATABASE]", "database".GetSetting() ?? "EventStore") - .Replace("[USER]", "user".GetSetting() ?? string.Empty) - .Replace("[PASSWORD]", "password".GetSetting() ?? string.Empty); - } - } - - public class AcceptanceTestAccessPersistenceFactory : AcceptanceTestSqlPersistenceFactory - { - public AcceptanceTestAccessPersistenceFactory() : base("Access") { } - } - public class AcceptanceTestAmazonRdsPersistenceFactory : AcceptanceTestSqlPersistenceFactory - { - public AcceptanceTestAmazonRdsPersistenceFactory() : base("AmazonRDS") { } - } - public class AcceptanceTestAzurePersistenceFactory : AcceptanceTestSqlPersistenceFactory - { - public AcceptanceTestAzurePersistenceFactory() : base("Azure") { } - } - public class AcceptanceTestFirebirdPersistenceFactory : AcceptanceTestSqlPersistenceFactory - { - public AcceptanceTestFirebirdPersistenceFactory() : base("Firebird") { } - } - public class AcceptanceTestMsSqlPersistenceFactory : AcceptanceTestSqlPersistenceFactory - { - public AcceptanceTestMsSqlPersistenceFactory() : base("MsSql") { } - } - public class AcceptanceTestMySqlPersistenceFactory : AcceptanceTestSqlPersistenceFactory - { - public AcceptanceTestMySqlPersistenceFactory() : base("MySQL") { } - } - public class AcceptanceTestPostgreSqlPersistenceFactory : AcceptanceTestSqlPersistenceFactory - { - public AcceptanceTestPostgreSqlPersistenceFactory() : base("PostgreSQL") { } - } - public class AcceptanceTestSqlCePersistenceFactory : AcceptanceTestSqlPersistenceFactory - { - public AcceptanceTestSqlCePersistenceFactory() : base("SQLCE") { } - } - public class AcceptanceTestSqlitePersistenceFactory : AcceptanceTestSqlPersistenceFactory - { - public AcceptanceTestSqlitePersistenceFactory() : base("SQLite") { } - } -} \ No newline at end of file diff --git a/src/tests/EventStore.Persistence.AcceptanceTests/EventStore.Persistence.AcceptanceTests.csproj b/src/tests/EventStore.Persistence.AcceptanceTests/EventStore.Persistence.AcceptanceTests.csproj deleted file mode 100644 index c1047ddb3..000000000 --- a/src/tests/EventStore.Persistence.AcceptanceTests/EventStore.Persistence.AcceptanceTests.csproj +++ /dev/null @@ -1,146 +0,0 @@ - - - - Debug - AnyCPU - 8.0.30703 - 2.0 - {B56FDCEA-086F-40A2-92E1-867CE506CBE3} - Library - Properties - EventStore.Persistence.AcceptanceTests - EventStore.Persistence.AcceptanceTests - v4.0 - 512 - false - ..\..\EventStore.snk - ..\..\..\src\ - true - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - x86 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - Properties\GlobalAssemblyInfo.cs - - - Properties\GlobalSuppressions.cs - - - Properties\VersionAssemblyInfo.cs - - - - - - - - - - - - - - Properties\CustomDictionary.xml - - - Always - - - PreserveNewest - - - Always - - - Always - - - - - {D6413244-42F5-4233-B347-D0A804B09CC9} - EventStore.Core - - - {32ADD8CE-0F3F-41D8-BFA1-6E5D685E64DD} - EventStore.Persistence.MongoPersistence - - - {F9E7FD69-0818-48CA-9249-5387739E1B6A} - EventStore.Persistence.RavenPersistence - - - {DAFD3F38-33F3-4F53-BFEA-44E51BD1E8F1} - EventStore.Persistence.SqlPersistence - - - {A5BF4B86-26F6-418D-BE35-C6CC3A623D27} - EventStore.Serialization - - - {03946843-F343-419C-88EF-3E446D08DFA6} - EventStore - - - - - False - ..\..\packages\FirebirdSql.Data.FirebirdClient.2.7.7\lib\net40\FirebirdSql.Data.FirebirdClient.dll - - - False - ..\..\packages\Machine.Specifications.0.5.8\lib\net40\Machine.Specifications.dll - - - False - ..\..\packages\Machine.Specifications.0.5.8\lib\net40\Machine.Specifications.Clr4.dll - - - ..\..\packages\Npgsql.2.0.11\lib\Net40\Mono.Security.dll - - - False - ..\..\packages\MySql.Data.6.5.4\lib\net40\MySql.Data.dll - - - ..\..\packages\Npgsql.2.0.11\lib\Net40\Npgsql.dll - - - - - - ..\..\packages\System.Data.SQLite.1.0.81.1\lib\net40\System.Data.SQLite.dll - - - ..\..\packages\System.Data.SQLite.1.0.81.1\lib\net40\System.Data.SQLite.Linq.dll - - - - - - Designer - - - Designer - - - - - - \ No newline at end of file diff --git a/src/tests/EventStore.Persistence.AcceptanceTests/EventStore.mdb b/src/tests/EventStore.Persistence.AcceptanceTests/EventStore.mdb deleted file mode 100644 index 69d30550d..000000000 Binary files a/src/tests/EventStore.Persistence.AcceptanceTests/EventStore.mdb and /dev/null differ diff --git a/src/tests/EventStore.Persistence.AcceptanceTests/EventStore.sdf b/src/tests/EventStore.Persistence.AcceptanceTests/EventStore.sdf deleted file mode 100644 index 9ac319997..000000000 Binary files a/src/tests/EventStore.Persistence.AcceptanceTests/EventStore.sdf and /dev/null differ diff --git a/src/tests/EventStore.Persistence.AcceptanceTests/ExtensionMethods.cs b/src/tests/EventStore.Persistence.AcceptanceTests/ExtensionMethods.cs deleted file mode 100644 index 9c272ce83..000000000 --- a/src/tests/EventStore.Persistence.AcceptanceTests/ExtensionMethods.cs +++ /dev/null @@ -1,58 +0,0 @@ -namespace EventStore.Persistence.AcceptanceTests -{ - using System; - using System.Collections.Generic; - - internal static class ExtensionMethods - { - public static Commit BuildAttempt(this Guid streamId, DateTime now) - { - var messages = new List - { - new EventMessage { Body = new SomeDomainEvent { SomeProperty = "Test" } }, - new EventMessage { Body = new SomeDomainEvent { SomeProperty = "Test2" } }, - }; - - return new Commit( - streamId, - 2, - Guid.NewGuid(), - 1, - now, - new Dictionary { { "A header", "A string value" }, { "Another header", 2 } }, - messages); - } - public static Commit BuildAttempt(this Guid streamId) - { - return streamId.BuildAttempt(SystemTime.UtcNow); - } - public static Commit BuildNextAttempt(this Commit commit) - { - var messages = new List - { - new EventMessage { Body = new SomeDomainEvent { SomeProperty = "Another test" } }, - new EventMessage { Body = new SomeDomainEvent { SomeProperty = "Another test2" } }, - }; - - return new Commit( - commit.StreamId, - commit.StreamRevision + 2, - Guid.NewGuid(), - commit.CommitSequence + 1, - commit.CommitStamp.AddSeconds(1), - new Dictionary(), - messages); - } - - [Serializable] - public class SomeDomainEvent - { - public string SomeProperty { get; set; } - - public override string ToString() - { - return this.SomeProperty; - } - } - } -} \ No newline at end of file diff --git a/src/tests/EventStore.Persistence.AcceptanceTests/PersistenceFactoryScanner.cs b/src/tests/EventStore.Persistence.AcceptanceTests/PersistenceFactoryScanner.cs deleted file mode 100644 index c3212735d..000000000 --- a/src/tests/EventStore.Persistence.AcceptanceTests/PersistenceFactoryScanner.cs +++ /dev/null @@ -1,86 +0,0 @@ -namespace EventStore.Persistence.AcceptanceTests -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Reflection; - - public class PersistenceFactoryScanner - { - private static readonly IDictionary Factories = - new Dictionary(); - - public PersistenceFactoryScanner() - { - if (Factories.Count > 0) - return; - - foreach (var type in GetAssemblyFiles().SelectMany(GetTypes)) - AddFactory(type); - } - - private static IEnumerable GetAssemblyFiles() - { - return Directory.GetFiles(AppDomain.CurrentDomain.BaseDirectory, "*.dll"); - } - - private static IEnumerable GetTypes(string filename) - { - try - { - return Assembly.LoadFrom(filename).GetTypes(); - } - catch (FileLoadException) - { - return new Type[] { }; - } - catch (Exception) - { - return new Type[] { }; - } - } - private static void AddFactory(Type type) - { - if (!typeof(IPersistenceFactory).IsAssignableFrom(type)) - return; - - if (typeof(IPersistenceFactory) == type || type.IsAbstract) - return; - - try - { - var factory = (IPersistenceFactory)Activator.CreateInstance(type); - var key = factory.GetType().Name - .Replace("AcceptanceTest", string.Empty) - .Replace("Factory", string.Empty); - - Factories[key] = factory; - } - catch - { - return; // no-op (added to suppress a warning) - } - } - - public virtual IPersistenceFactory GetFactory() - { - var persistenceEngine = "persistence".GetSetting() ?? "MsSqlPersistence"; - - try - { - return Factories[persistenceEngine]; - } - catch (KeyNotFoundException) - { - var message = "The key '{0}' was not a configured persistence engine.".FormatWith(persistenceEngine); - throw new StorageException(message); - } - } - - public virtual int PageSize - { - get { return int.Parse("pageSize".GetSetting() ?? "0"); } - } - } -} \ No newline at end of file diff --git a/src/tests/EventStore.Persistence.AcceptanceTests/PersistenceTests.cs b/src/tests/EventStore.Persistence.AcceptanceTests/PersistenceTests.cs deleted file mode 100644 index 1253f822b..000000000 --- a/src/tests/EventStore.Persistence.AcceptanceTests/PersistenceTests.cs +++ /dev/null @@ -1,433 +0,0 @@ -#pragma warning disable 169 -// ReSharper disable InconsistentNaming - -namespace EventStore.Persistence.AcceptanceTests -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Threading; - using Diagnostics; - using Machine.Specifications; - using Persistence; - - [Subject("Persistence")] - public class when_a_commit_is_successfully_persisted : using_the_persistence_engine - { - static readonly DateTime now = SystemTime.UtcNow.AddYears(1); - static readonly Commit attempt = streamId.BuildAttempt(now); - static Commit persisted; - - Establish context = () => - persistence.Commit(attempt); - - Because of = () => - persisted = persistence.GetFrom(streamId, 0, int.MaxValue).First(); - - It should_correctly_persist_the_stream_identifier = () => - persisted.StreamId.ShouldEqual(attempt.StreamId); - - It should_correctly_persist_the_stream_stream_revision = () => - persisted.StreamRevision.ShouldEqual(attempt.StreamRevision); - - It should_correctly_persist_the_commit_identifier = () => - persisted.CommitId.ShouldEqual(attempt.CommitId); - - It should_correctly_persist_the_commit_sequence = () => - persisted.CommitSequence.ShouldEqual(attempt.CommitSequence); - - // persistence engines have varying levels of precision with respect to time. - It should_correctly_persist_the_commit_stamp = () => - persisted.CommitStamp.Subtract(now).ShouldBeLessThan(TimeSpan.FromSeconds(1)); - - It should_correctly_persist_the_headers = () => - persisted.Headers.Count.ShouldEqual(attempt.Headers.Count); - - It should_correctly_persist_the_events = () => - persisted.Events.Count.ShouldEqual(attempt.Events.Count); - - It should_add_the_commit_to_the_set_of_undispatched_commits = () => - persistence.GetUndispatchedCommits() - .FirstOrDefault(x => x.CommitId == attempt.CommitId).ShouldNotBeNull(); - - It should_cause_the_stream_to_be_found_in_the_list_of_streams_to_snapshot = () => - persistence.GetStreamsToSnapshot(1) - .FirstOrDefault(x => x.StreamId == streamId).ShouldNotBeNull(); - } - - [Subject("Persistence")] - public class when_reading_from_a_given_revision : using_the_persistence_engine - { - const int LoadFromCommitContainingRevision = 3; - const int UpToCommitWithContainingRevision = 5; - static readonly Commit oldest = streamId.BuildAttempt(); // 2 events, revision 1-2 - static readonly Commit oldest2 = oldest.BuildNextAttempt(); // 2 events, revision 3-4 - static readonly Commit oldest3 = oldest2.BuildNextAttempt(); // 2 events, revision 5-6 - static readonly Commit newest = oldest3.BuildNextAttempt(); // 2 events, revision 7-8 - static Commit[] committed; - - Establish context = () => - { - persistence.Commit(oldest); - persistence.Commit(oldest2); - persistence.Commit(oldest3); - persistence.Commit(newest); - }; - - Because of = () => - committed = persistence.GetFrom(streamId, LoadFromCommitContainingRevision, UpToCommitWithContainingRevision).ToArray(); - - It should_start_from_the_commit_which_contains_the_min_stream_revision_specified = () => - committed.First().CommitId.ShouldEqual(oldest2.CommitId); // contains revision 3 - - It should_read_up_to_the_commit_which_contains_the_max_stream_revision_specified = () => - committed.Last().CommitId.ShouldEqual(oldest3.CommitId); // contains revision 5 - } - - [Subject("Persistence")] - public class when_committing_a_stream_with_the_same_revision : using_the_persistence_engine - { - static readonly IPersistStreams persistence1 = Factory.Build(); - static readonly Commit attempt1 = streamId.BuildAttempt(); - static readonly Commit attempt2 = streamId.BuildAttempt(); - static Exception thrown; - - Establish context = () => - persistence1.Commit(attempt1); - - Because of = () => - thrown = Catch.Exception(() => persistence1.Commit(attempt2)); - - It should_throw_a_ConcurrencyException = () => - thrown.ShouldBeOfType(); - - Cleanup cleanup = () => - persistence1.Dispose(); - } - - [Subject("Persistence")] - public class when_committing_a_stream_with_the_same_sequence : using_the_persistence_engine - { - static readonly IPersistStreams persistence1 = Factory.Build(); - static readonly Commit attempt1 = streamId.BuildAttempt(); - static readonly Commit attempt2 = streamId.BuildAttempt(); - static Exception thrown; - - Establish context = () => - persistence1.Commit(attempt1); - - Because of = () => - thrown = Catch.Exception(() => persistence1.Commit(attempt2)); - - It should_throw_a_ConcurrencyException = () => - thrown.ShouldBeOfType(); - - Cleanup cleanup = () => - persistence1.Dispose(); - } - - [Subject("Persistence")] - public class when_attempting_to_overwrite_a_committed_sequence : using_the_persistence_engine - { - static readonly Commit successfulAttempt = streamId.BuildAttempt(); - static readonly Commit failedAttempt = streamId.BuildAttempt(); - static Exception thrown; - - Establish context = () => - persistence.Commit(successfulAttempt); - - Because of = () => - thrown = Catch.Exception(() => persistence.Commit(failedAttempt)); - - It should_throw_a_ConcurrencyException = () => - thrown.ShouldBeOfType(); - } - - [Subject("Persistence")] - public class when_attempting_to_persist_a_commit_twice : using_the_persistence_engine - { - static readonly Commit attemptTwice = streamId.BuildAttempt(); - static Exception thrown; - - Establish context = () => - persistence.Commit(attemptTwice); - - Because of = () => - thrown = Catch.Exception(() => persistence.Commit(attemptTwice)); - - It should_throw_a_DuplicateCommitException = () => - thrown.ShouldBeOfType(); - } - - [Subject("Persistence")] - public class when_a_commit_has_been_marked_as_dispatched : using_the_persistence_engine - { - static readonly Commit attempt = streamId.BuildAttempt(); - - Establish context = () => - persistence.Commit(attempt); - - Because of = () => - persistence.MarkCommitAsDispatched(attempt); - - It should_no_longer_be_found_in_the_set_of_undispatched_commits = () => - persistence.GetUndispatchedCommits() - .FirstOrDefault(x => x.CommitId == attempt.CommitId).ShouldBeNull(); - } - - [Subject("Persistence")] - public class when_committing_more_events_than_the_configured_page_size : using_the_persistence_engine - { - static readonly int ConfiguredPageSize = FactoryScanner.PageSize; - static readonly int CommitsToPersist = ConfiguredPageSize + 1; - static readonly HashSet committed = new HashSet(); - static readonly ICollection loaded = new LinkedList(); - static Commit attempt = streamId.BuildAttempt(); - - Establish context = () => - { - var attempt = streamId.BuildAttempt(); - for (var i = 0; i < CommitsToPersist; i++) - { - persistence.Commit(attempt); - committed.Add(attempt.CommitId); - attempt = attempt.BuildNextAttempt(); - } - }; - - Because of = () => - persistence.GetFrom(streamId, 0, int.MaxValue).ToList().ForEach(x => loaded.Add(x.CommitId)); - - It should_load_the_same_number_of_commits_which_have_been_persisted = () => - loaded.Count.ShouldEqual(committed.Count); - - It should_load_the_same_commits_which_have_been_persisted = () => - committed.All(x => loaded.Contains(x)).ShouldBeTrue(); // all commits should be found in loaded collection - } - - [Subject("Persistence")] - public class when_saving_a_snapshot : using_the_persistence_engine - { - static readonly Snapshot snapshot = new Snapshot(streamId, 1, "Snapshot"); - static bool added; - - Establish context = () => - persistence.Commit(streamId.BuildAttempt()); - - Because of = () => - added = persistence.AddSnapshot(snapshot); - - It should_indicate_the_snapshot_was_added = () => - added.ShouldBeTrue(); - - It should_be_able_to_retrieve_the_snapshot = () => - persistence.GetSnapshot(streamId, snapshot.StreamRevision).ShouldNotBeNull(); - } - - [Subject("Persistence")] - public class when_retrieving_a_snapshot : using_the_persistence_engine - { - static readonly Snapshot tooFarBack = new Snapshot(streamId, 1, string.Empty); - static readonly Snapshot correct = new Snapshot(streamId, 3, "Snapshot"); - static readonly Snapshot tooFarForward = new Snapshot(streamId, 5, string.Empty); - static Snapshot snapshot; - - Establish context = () => - { - var commit1 = streamId.BuildAttempt(); - var commit2 = commit1.BuildNextAttempt(); - var commit3 = commit2.BuildNextAttempt(); - persistence.Commit(commit1); // rev 1-2 - persistence.Commit(commit2); // rev 3-4 - persistence.Commit(commit3); // rev 5-6 - - persistence.AddSnapshot(tooFarBack); - persistence.AddSnapshot(correct); - persistence.AddSnapshot(tooFarForward); - }; - - Because of = () => - snapshot = persistence.GetSnapshot(streamId, tooFarForward.StreamRevision - 1); - - It should_load_the_most_recent_prior_snapshot = () => - snapshot.StreamRevision.ShouldEqual(correct.StreamRevision); - - It should_have_the_correct_snapshot_payload = () => - snapshot.Payload.ShouldEqual(correct.Payload); - } - - [Subject("Persistence")] - public class when_a_snapshot_has_been_added_to_the_most_recent_commit_of_a_stream : using_the_persistence_engine - { - const string SnapshotData = "snapshot"; - static readonly Commit oldest = streamId.BuildAttempt(); - static readonly Commit oldest2 = oldest.BuildNextAttempt(); - static readonly Commit newest = oldest2.BuildNextAttempt(); - - Establish context = () => - { - persistence.Commit(oldest); - persistence.Commit(oldest2); - persistence.Commit(newest); - }; - - Because of = () => - persistence.AddSnapshot(new Snapshot(streamId, newest.StreamRevision, SnapshotData)); - - It should_no_longer_find_the_stream_in_the_set_of_streams_to_be_snapshot = () => - persistence.GetStreamsToSnapshot(1).Any(x => x.StreamId == streamId).ShouldBeFalse(); - } - - [Subject("Persistence")] - public class when_adding_a_commit_after_a_snapshot : using_the_persistence_engine - { - const int WithinThreshold = 2; - const int OverThreshold = 3; - const string SnapshotData = "snapshot"; - static readonly Commit oldest = streamId.BuildAttempt(); - static readonly Commit oldest2 = oldest.BuildNextAttempt(); - static readonly Commit newest = oldest2.BuildNextAttempt(); - - Establish context = () => - { - persistence.Commit(oldest); - persistence.Commit(oldest2); - persistence.AddSnapshot(new Snapshot(streamId, oldest2.StreamRevision, SnapshotData)); - }; - - Because of = () => - persistence.Commit(newest); - - // Because Raven and Mongo update the stream head asynchronously, occasionally will fail this test - It should_find_the_stream_in_the_set_of_streams_to_be_snapshot_when_within_the_threshold = () => - persistence.GetStreamsToSnapshot(WithinThreshold) - .FirstOrDefault(x => x.StreamId == streamId).ShouldNotBeNull(); - - It should_not_find_the_stream_in_the_set_of_streams_to_be_snapshot_when_over_the_threshold = () => - persistence.GetStreamsToSnapshot(OverThreshold) - .Any(x => x.StreamId == streamId).ShouldBeFalse(); - } - - [Subject("Persistence")] - public class when_reading_all_commits_from_a_particular_point_in_time : using_the_persistence_engine - { - static readonly DateTime now = SystemTime.UtcNow.AddYears(1); - static readonly Commit first = streamId.BuildAttempt(now.AddSeconds(1)); - static readonly Commit second = first.BuildNextAttempt(); - static readonly Commit third = second.BuildNextAttempt(); - static readonly Commit fourth = third.BuildNextAttempt(); - static Commit[] committed; - - Establish context = () => - { - persistence.Commit(first); - persistence.Commit(second); - persistence.Commit(third); - persistence.Commit(fourth); - }; - - Because of = () => - committed = persistence.GetFrom(now).ToArray(); - - It should_return_all_commits_on_or_after_the_point_in_time_specified = () => - committed.Length.ShouldEqual(4); - } - - [Subject("Persistence")] - public class when_paging_over_all_commits_from_a_particular_point_in_time : using_the_persistence_engine - { - static readonly int ConfiguredPageSize = FactoryScanner.PageSize; - static readonly int CommitsToPersist = ConfiguredPageSize + 1; - static readonly DateTime start = SystemTime.UtcNow; - static readonly HashSet committed = new HashSet(); - static readonly ICollection loaded = new LinkedList(); - static Commit attempt = streamId.BuildAttempt(); - - Establish context = () => - { - var attempt = streamId.BuildAttempt(); - for (var i = 0; i < CommitsToPersist; i++) - { - persistence.Commit(attempt); - committed.Add(attempt.CommitId); - attempt = attempt.BuildNextAttempt(); - } - }; - - Because of = () => - persistence.GetFrom(start).ToList().ForEach(x => loaded.Add(x.CommitId)); - - It should_load_the_same_number_of_commits_which_have_been_persisted = () => - loaded.Count.ShouldBeGreaterThanOrEqualTo(committed.Count); // >= because items may be loaded from other tests. - - It should_load_the_same_commits_which_have_been_persisted = () => - committed.All(x => loaded.Contains(x)).ShouldBeTrue(); // all commits should be found in loaded collection - } - - [Subject("Persistence")] - public class when_reading_all_commits_from_the_year_1_AD : using_the_persistence_engine - { - static Exception thrown; - - Because of = () => - thrown = Catch.Exception(() => persistence.GetFrom(DateTime.MinValue).FirstOrDefault()); - - It should_NOT_throw_an_exception = () => - thrown.ShouldBeNull(); - } - - [Subject("Persistence")] - public class when_purging_all_commits : using_the_persistence_engine - { - Establish context = () => - persistence.Commit(streamId.BuildAttempt()); - - Because of = () => - { - Thread.Sleep(50); // 50 ms = enough time for Raven to become consistent - persistence.Purge(); - }; - - It should_not_find_any_commits_stored = () => - persistence.GetFrom(DateTime.MinValue).Count().ShouldEqual(0); - - It should_not_find_any_streams_to_snapshot = () => - persistence.GetStreamsToSnapshot(0).Count().ShouldEqual(0); - - It should_not_find_any_undispatched_commits = () => - persistence.GetUndispatchedCommits().Count().ShouldEqual(0); - } - - [Subject("Persistence")] - public class when_invoking_after_disposal : using_the_persistence_engine - { - static Exception thrown; - - Establish context = () => - persistence.Dispose(); - - It should_throw_an_ObjectDisposedException = () => - Catch.Exception(() => persistence.Commit(streamId.BuildAttempt())).ShouldBeOfType(); - } - - public abstract class using_the_persistence_engine - { - protected static readonly PersistenceFactoryScanner FactoryScanner = new PersistenceFactoryScanner(); - protected static readonly IPersistenceFactory Factory = FactoryScanner.GetFactory(); - protected static Guid streamId = Guid.NewGuid(); - protected static IPersistStreams persistence; - - Establish context = () => - { - persistence = new PerformanceCounterPersistenceEngine(Factory.Build(), "tests"); - persistence.Initialize(); - }; - - Cleanup everything = () => - { - persistence.Dispose(); - persistence = null; - streamId = Guid.NewGuid(); - }; - } -} \ No newline at end of file diff --git a/src/tests/EventStore.Persistence.AcceptanceTests/Properties/AssemblyInfo.cs b/src/tests/EventStore.Persistence.AcceptanceTests/Properties/AssemblyInfo.cs deleted file mode 100644 index c9582ec31..000000000 --- a/src/tests/EventStore.Persistence.AcceptanceTests/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using System.Reflection; -using System.Runtime.InteropServices; - -[assembly: AssemblyTitle("EventStore.Persistence.AcceptanceTests")] -[assembly: AssemblyDescription("")] -[assembly: Guid("bdc77526-556f-47cd-9dc2-9e63205f2dee")] - -[assembly: SuppressMessage("Microsoft.Design", "CA2210:AssembliesShouldHaveValidStrongNames", - Justification = "Machine.Specifications is not signed, therefore this assembly cannot be signed.")] \ No newline at end of file diff --git a/src/tests/EventStore.Persistence.AcceptanceTests/RavenPersistence/RavenPersistencePartitionTests.cs b/src/tests/EventStore.Persistence.AcceptanceTests/RavenPersistence/RavenPersistencePartitionTests.cs deleted file mode 100644 index f324b7554..000000000 --- a/src/tests/EventStore.Persistence.AcceptanceTests/RavenPersistence/RavenPersistencePartitionTests.cs +++ /dev/null @@ -1,198 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using EventStore.Persistence.AcceptanceTests.Engines; -using Machine.Specifications; - -#pragma warning disable 169 -// ReSharper disable InconsistentNaming - -namespace EventStore.Persistence.AcceptanceTests.RavenPersistence -{ - [Subject("RavenPersistence - Partitions")] - public class when_committing_a_stream_with_the_same_id_as_a_stream_in_another_partition : using_raven_persistence_with_partitions - { - static IPersistStreams persistence1, persistence2; - static Commit attempt1, attempt2; - - static Exception thrown; - - Establish context = () => - { - persistence1 = NewEventStoreWithPartition(); - persistence2 = NewEventStoreWithPartition(); - - var now = SystemTime.UtcNow; - attempt1 = streamId.BuildAttempt(now); - attempt2 = streamId.BuildAttempt(now.Subtract(TimeSpan.FromDays(1))); - - persistence1.Commit(attempt1); - }; - - Because of = () => - thrown = Catch.Exception(() => persistence2.Commit(attempt2)); - - It should_succeed = () => - thrown.ShouldBeNull(); - - It should_persist_to_the_correct_partition = () => - { - var stream = persistence2.GetFrom(streamId, 0, int.MaxValue).ToArray(); - stream.ShouldNotBeNull(); - stream.Count().ShouldEqual(1); - stream.First().CommitStamp.ShouldEqual(attempt2.CommitStamp); - }; - - It should_not_affect_the_stream_from_the_other_partition = () => - { - var stream = persistence1.GetFrom(streamId, 0, int.MaxValue).ToArray(); - stream.ShouldNotBeNull(); - stream.Count().ShouldEqual(1); - stream.First().CommitStamp.ShouldEqual(attempt1.CommitStamp); - }; - } - - [Subject("RavenPersistence - Partitions")] - public class when_saving_a_snapshot_in_a_partition : using_raven_persistence_with_partitions - { - static Snapshot snapshot; - static IPersistStreams persistence1, persistence2; - static bool added; - - Establish context = () => - { - snapshot = new Snapshot(streamId, 1, "Snapshot"); - persistence1 = NewEventStoreWithPartition(); - persistence2 = NewEventStoreWithPartition(); - persistence1.Commit(streamId.BuildAttempt()); - }; - Because of = () => - added = persistence1.AddSnapshot(snapshot); - - It should_indicate_the_snapshot_was_added = () => - added.ShouldBeTrue(); - - It should_be_able_to_retrieve_the_snapshot = () => - persistence1.GetSnapshot(streamId, snapshot.StreamRevision).ShouldNotBeNull(); - - It should_not_be_able_to_retrieve_the_snapshot_from_another_partition = () => - persistence2.GetSnapshot(streamId, snapshot.StreamRevision).ShouldBeNull(); - } - - [Subject("RavenPersistence - Partitions")] - public class when_reading_all_commits_from_a_particular_point_in_time_from_a_partition : using_raven_persistence_with_partitions - { - static DateTime now; - static IPersistStreams persistence1, persistence2; - static Commit first, second, third, fourth, fifth; - static Commit[] committed1, committed2; - - Establish context = () => - { - now = SystemTime.UtcNow.AddYears(1); - first = Guid.NewGuid().BuildAttempt(now.AddSeconds(1)); - second = first.BuildNextAttempt(); - third = second.BuildNextAttempt(); - fourth = third.BuildNextAttempt(); - fifth = Guid.NewGuid().BuildAttempt(now.AddSeconds(1)); - - persistence1 = NewEventStoreWithPartition(); - persistence2 = NewEventStoreWithPartition(); - - persistence1.Commit(first); - persistence1.Commit(second); - persistence1.Commit(third); - persistence1.Commit(fourth); - persistence2.Commit(fifth); - }; - - Because of = () => - committed1 = persistence1.GetFrom(now).ToArray(); - - It should_return_all_commits_on_or_after_the_point_in_time_specified = () => - committed1.Length.ShouldEqual(4); - - It should_not_return_commits_from_other_partitions = () => - committed1.Any(c => c.CommitId.Equals(fifth.CommitId)).ShouldBeFalse(); - } - - [Subject("RavenPersistence - Partitions")] - public class when_purging_all_commits : using_raven_persistence_with_partitions - { - static IPersistStreams persistence1, persistence2; - - Establish context = () => - { - persistence1 = NewEventStoreWithPartition(); - persistence2 = NewEventStoreWithPartition(); - - persistence1.Commit(streamId.BuildAttempt()); - persistence2.Commit(streamId.BuildAttempt()); - }; - Because of = () => - { - Thread.Sleep(50); // 50 ms = enough time for Raven to become consistent - persistence1.Purge(); - }; - - It should_purge_all_commits_stored = () => - persistence1.GetFrom(DateTime.MinValue).Count().ShouldEqual(0); - - It should_purge_all_streams_to_snapshot = () => - persistence1.GetStreamsToSnapshot(0).Count().ShouldEqual(0); - - It should_purge_all_undispatched_commits = () => - persistence1.GetUndispatchedCommits().Count().ShouldEqual(0); - - It should_not_purge_all_commits_stored_in_other_partitions = () => - persistence2.GetFrom(DateTime.MinValue).Count().ShouldNotEqual(0); - - It should_not_purge_all_streams_to_snapshot_in_other_partitions = () => - persistence2.GetStreamsToSnapshot(0).Count().ShouldNotEqual(0); - - It should_not_purge_all_undispatched_commits_in_other_partitions = () => - persistence2.GetUndispatchedCommits().Count().ShouldNotEqual(0); - } - - public abstract class using_raven_persistence_with_partitions - { - protected static Guid streamId; - protected static List instantiatedPersistence; - - Establish context = () => - { - streamId = Guid.NewGuid(); - instantiatedPersistence = new List(); - }; - - Cleanup everything = () => - { - foreach (var persistence in instantiatedPersistence) - { - persistence.Dispose(); - } - }; - - protected static IPersistStreams NewEventStoreWithPartition() - { - return NewEventStoreWithPartition(Guid.NewGuid().ToString()); - } - - protected static IPersistStreams NewEventStoreWithPartition(string partition) - { - var config = AcceptanceTestRavenPersistenceFactory.GetDefaultConfig(); - config.Partition = partition; - - var persistence = new AcceptanceTestRavenPersistenceFactory(config).Build(); - persistence.Initialize(); - - instantiatedPersistence.Add(persistence); - - return persistence; - } - } -} - -// ReSharper enable InconsistentNaming -#pragma warning restore 169 \ No newline at end of file diff --git a/src/tests/EventStore.Persistence.AcceptanceTests/packages.config b/src/tests/EventStore.Persistence.AcceptanceTests/packages.config deleted file mode 100644 index e91929edd..000000000 --- a/src/tests/EventStore.Persistence.AcceptanceTests/packages.config +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/src/tests/EventStore.Persistence.AcceptanceTests/x64/SQLite.Interop.dll b/src/tests/EventStore.Persistence.AcceptanceTests/x64/SQLite.Interop.dll deleted file mode 100644 index 01d8a6563..000000000 Binary files a/src/tests/EventStore.Persistence.AcceptanceTests/x64/SQLite.Interop.dll and /dev/null differ diff --git a/src/tests/EventStore.Persistence.AcceptanceTests/x86/SQLite.Interop.dll b/src/tests/EventStore.Persistence.AcceptanceTests/x86/SQLite.Interop.dll deleted file mode 100644 index 06ad1589d..000000000 Binary files a/src/tests/EventStore.Persistence.AcceptanceTests/x86/SQLite.Interop.dll and /dev/null differ diff --git a/src/tests/EventStore.Serialization.AcceptanceTests/App.config b/src/tests/EventStore.Serialization.AcceptanceTests/App.config deleted file mode 100644 index d3f443738..000000000 --- a/src/tests/EventStore.Serialization.AcceptanceTests/App.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/src/tests/EventStore.Serialization.AcceptanceTests/ConfigurationExtensions.cs b/src/tests/EventStore.Serialization.AcceptanceTests/ConfigurationExtensions.cs deleted file mode 100644 index b2697239d..000000000 --- a/src/tests/EventStore.Serialization.AcceptanceTests/ConfigurationExtensions.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace EventStore.Serialization.AcceptanceTests -{ - using System; - using System.Configuration; - using System.Linq; - - internal static class ConfigurationExtensions - { - public static string GetSetting(this string settingName) - { - return GetCommandLineArgument("/" + settingName + ":") - ?? Environment.GetEnvironmentVariable(settingName) - ?? ConfigurationManager.AppSettings[settingName]; - } - private static string GetCommandLineArgument(string settingName) - { - return Environment.GetCommandLineArgs() - .Where(arg => arg.StartsWith(settingName)) - .Select(arg => arg.Replace(settingName, string.Empty)) - .FirstOrDefault(); - } - } -} \ No newline at end of file diff --git a/src/tests/EventStore.Serialization.AcceptanceTests/EventStore.Serialization.AcceptanceTests.csproj b/src/tests/EventStore.Serialization.AcceptanceTests/EventStore.Serialization.AcceptanceTests.csproj deleted file mode 100644 index d0cab299e..000000000 --- a/src/tests/EventStore.Serialization.AcceptanceTests/EventStore.Serialization.AcceptanceTests.csproj +++ /dev/null @@ -1,97 +0,0 @@ - - - - Debug - AnyCPU - 8.0.30703 - 2.0 - {FCCBA30F-738D-4C82-BAE4-906B88399AA2} - Library - Properties - EventStore.Serialization.AcceptanceTests - EventStore.Serialization.AcceptanceTests - v4.0 - 512 - false - ..\..\EventStore.snk - ..\..\..\src\ - true - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - Properties\GlobalAssemblyInfo.cs - - - Properties\GlobalSuppressions.cs - - - Properties\VersionAssemblyInfo.cs - - - - - - - - - - - Properties\CustomDictionary.xml - - - - - {CFD895BD-7CB2-4811-A6FA-1851DF769B67} - EventStore.Serialization.Json - - - {2BA8B905-65D5-4BBA-A76B-58EC49F26018} - EventStore.Serialization.ServiceStack - - - {A5BF4B86-26F6-418D-BE35-C6CC3A623D27} - EventStore.Serialization - - - {03946843-F343-419C-88EF-3E446D08DFA6} - EventStore - - - - - False - ..\..\packages\Machine.Specifications.0.5.8\lib\net40\Machine.Specifications.dll - - - False - ..\..\packages\Machine.Specifications.0.5.8\lib\net40\Machine.Specifications.Clr4.dll - - - - - - - Designer - - - - - - \ No newline at end of file diff --git a/src/tests/EventStore.Serialization.AcceptanceTests/ExtensionMethods.cs b/src/tests/EventStore.Serialization.AcceptanceTests/ExtensionMethods.cs deleted file mode 100644 index ccc78fe12..000000000 --- a/src/tests/EventStore.Serialization.AcceptanceTests/ExtensionMethods.cs +++ /dev/null @@ -1,56 +0,0 @@ -namespace EventStore.Serialization.AcceptanceTests -{ - using System; - using System.Collections.Generic; - using System.Linq; - - internal static class ExtensionMethods - { - public static SimpleMessage Populate(this SimpleMessage message) - { - message = message ?? new SimpleMessage(); - - return new SimpleMessage - { - Id = Guid.NewGuid(), - Count = 1234, - Created = new DateTime(2000, 2, 3, 4, 5, 6, 7).ToUniversalTime(), - Value = message.Value + "Hello, World!", - Contents = { "a", null, string.Empty, "d" } - }; - } - - public static Commit BuildCommit(this Guid streamId) - { - const int StreamRevision = 2; - const int CommitSequence = 2; - var commitId = Guid.NewGuid(); - var headers = new Dictionary { { "Key", "Value" }, { "Key2", (long)1234 }, { "Key3", null } }; - var events = new[] - { - new EventMessage - { - Headers = - { - { "MsgKey1", TimeSpan.MinValue }, - { "MsgKey2", Guid.NewGuid() }, - { "MsgKey3", 1.1M }, - { "MsgKey4", (ushort)1 } - }, - Body = "some value" - }, - new EventMessage - { - Headers = - { - { "MsgKey1", new Uri("http://www.google.com/") }, - { "MsgKey4", "some header" } - }, - Body = new[] { "message body" } - } - }; - - return new Commit(streamId, StreamRevision, commitId, CommitSequence, SystemTime.UtcNow, headers, events.ToList()); - } - } -} \ No newline at end of file diff --git a/src/tests/EventStore.Serialization.AcceptanceTests/Properties/AssemblyInfo.cs b/src/tests/EventStore.Serialization.AcceptanceTests/Properties/AssemblyInfo.cs deleted file mode 100644 index a14f43e8d..000000000 --- a/src/tests/EventStore.Serialization.AcceptanceTests/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using System.Reflection; -using System.Runtime.InteropServices; - -[assembly: AssemblyTitle("EventStore.Serialization.AcceptanceTests")] -[assembly: AssemblyDescription("")] -[assembly: Guid("ae6f4d84-fa58-4f27-8df9-23f5ae462f7c")] - -[assembly: SuppressMessage("Microsoft.Design", "CA2210:AssembliesShouldHaveValidStrongNames", - Justification = "Machine.Specifications is not signed, therefore this assembly cannot be signed.")] \ No newline at end of file diff --git a/src/tests/EventStore.Serialization.AcceptanceTests/SerializationFactory.cs b/src/tests/EventStore.Serialization.AcceptanceTests/SerializationFactory.cs deleted file mode 100644 index 9a6fa73cc..000000000 --- a/src/tests/EventStore.Serialization.AcceptanceTests/SerializationFactory.cs +++ /dev/null @@ -1,33 +0,0 @@ -namespace EventStore.Serialization.AcceptanceTests -{ - using System; - - public class SerializationFactory - { - private static readonly byte[] EncryptionKey = new byte[] - { - 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0x0 - }; - - public virtual ISerialize Build() - { - switch ("serializer".GetSetting()) - { - case "Binary": - return new BinarySerializer(); - case "Gzip": - return new GzipSerializer(new BinarySerializer()); - case "Rijndael": - return new RijndaelSerializer(new BinarySerializer(), EncryptionKey); - case "Json": - return new JsonSerializer(); - case "Bson": - return new BsonSerializer(); - case "ServiceStackJson": - return new JsonSerializer(); - default: - throw new NotSupportedException("The configured serializer is not registered with the SerializationFactory."); - } - } - } -} \ No newline at end of file diff --git a/src/tests/EventStore.Serialization.AcceptanceTests/SerializerTests.cs b/src/tests/EventStore.Serialization.AcceptanceTests/SerializerTests.cs deleted file mode 100644 index 83a304f87..000000000 --- a/src/tests/EventStore.Serialization.AcceptanceTests/SerializerTests.cs +++ /dev/null @@ -1,162 +0,0 @@ -#pragma warning disable 169 -// ReSharper disable InconsistentNaming - -namespace EventStore.Serialization.AcceptanceTests -{ - using System; - using System.Collections.Generic; - using System.Linq; - using Machine.Specifications; - - [Subject("Serialization")] - public class when_serializing_a_simple_message : using_serialization - { - static readonly SimpleMessage Message = new SimpleMessage().Populate(); - static byte[] serialized; - static SimpleMessage deserialized; - - Establish context = () => - serialized = Serializer.Serialize(Message); - - Because of = () => - deserialized = Serializer.Deserialize(serialized); - - It should_deserialize_a_message_which_contains_the_same_Id_as_the_serialized_message = () => - deserialized.Id.ShouldEqual(Message.Id); - - It should_deserialize_a_message_which_contains_the_same_Value_as_the_serialized_message = () => - deserialized.Value.ShouldEqual(Message.Value); - - It should_deserialize_a_message_which_contains_the_same_Created_value_as_the_serialized_message = () => - deserialized.Created.ShouldEqual(Message.Created); - - It should_deserialize_a_message_which_contains_the_same_Count_as_the_serialized_message = () => - deserialized.Count.ShouldEqual(Message.Count); - - It should_deserialize_a_message_which_contains_the_number_of_elements_as_the_serialized_message = () => - deserialized.Contents.Count.ShouldEqual(Message.Contents.Count); - - It should_deserialize_a_message_which_contains_the_same_Contents_as_the_serialized_message = () => - deserialized.Contents.SequenceEqual(Message.Contents).ShouldBeTrue(); - } - - [Subject("Serialization")] - public class when_serializing_a_list_of_event_messages : using_serialization - { - private static readonly List Messages = new List - { - new EventMessage { Body = "some value" }, - new EventMessage { Body = 42 }, - new EventMessage { Body = new SimpleMessage() } - }; - static byte[] serialized; - static List deserialized; - - Establish context = () => - serialized = Serializer.Serialize(Messages); - - Because of = () => - deserialized = Serializer.Deserialize>(serialized); - - It should_deserialize_the_same_number_of_event_messages_as_it_serialized = () => - Messages.Count.ShouldEqual(deserialized.Count); - - It should_deserialize_the_the_complex_types_within_the_event_messages = () => - deserialized.Last().Body.ShouldBeOfType(); - } - - [Subject("Serialization")] - public class when_serializing_a_list_of_commit_headers : using_serialization - { - private static readonly Dictionary Headers = new Dictionary - { - { "HeaderKey", "SomeValue" }, - { "AnotherKey", 42 }, - { "AndAnotherKey", Guid.NewGuid() }, - { "LastKey", new SimpleMessage() } - }; - static byte[] serialized; - static Dictionary deserialized; - - Establish context = () => - serialized = Serializer.Serialize(Headers); - - Because of = () => - deserialized = Serializer.Deserialize>(serialized); - - It should_deserialize_the_same_number_of_event_messages_as_it_serialized = () => - Headers.Count.ShouldEqual(deserialized.Count); - - It should_deserialize_the_the_complex_types_within_the_event_messages = () => - deserialized.Last().Value.ShouldBeOfType(); - } - - [Subject("Serialization")] - public class when_serializing_a_commit_message : using_serialization - { - static readonly Commit Message = Guid.NewGuid().BuildCommit(); - static byte[] serialized; - static Commit deserialized; - - Establish context = () => - serialized = Serializer.Serialize(Message); - - Because of = () => - deserialized = Serializer.Deserialize(serialized); - - It should_deserialize_a_commit_which_contains_the_same_StreamId_as_the_serialized_commit = () => - deserialized.StreamId.ShouldEqual(Message.StreamId); - - It should_deserialize_a_commit_which_contains_the_same_CommitId_as_the_serialized_commit = () => - deserialized.CommitId.ShouldEqual(Message.CommitId); - - It should_deserialize_a_commit_which_contains_the_same_StreamRevision_as_the_serialized_commit = () => - deserialized.StreamRevision.ShouldEqual(Message.StreamRevision); - - It should_deserialize_a_commit_which_contains_the_same_CommitSequence_as_the_serialized_commit = () => - deserialized.CommitSequence.ShouldEqual(Message.CommitSequence); - - It should_deserialize_a_commit_which_contains_the_same_number_of_headers_as_the_serialized_commit = () => - deserialized.Headers.Count.ShouldEqual(Message.Headers.Count); - - It should_deserialize_a_commit_which_contains_the_same_headers_as_the_serialized_commit = () => - { - foreach (var header in deserialized.Headers) - header.Value.ShouldEqual(Message.Headers[header.Key]); - - deserialized.Headers.Values.SequenceEqual(Message.Headers.Values); - }; - - It should_deserialize_a_commit_which_contains_the_same_number_of_events_as_the_serialized_commit = () => - deserialized.Events.Count.ShouldEqual(Message.Events.Count); - } - - [Subject("Serialization")] - public class when_serializing_an_untyped_payload_on_a_snapshot : using_serialization - { - static readonly IDictionary> Payload = new Dictionary>(); - static readonly Snapshot Snapshot = new Snapshot(Guid.NewGuid(), 42, Payload); - static byte[] serialized; - static Snapshot deserialized; - - Establish context = () => - serialized = Serializer.Serialize(Snapshot); - - Because of = () => - deserialized = Serializer.Deserialize(serialized); - - It should_correctly_deserialize_the_untyped_payload_contents = () => - deserialized.Payload.ShouldEqual(Snapshot.Payload); - - It should_correctly_deserialize_the_untyped_payload_type = () => - deserialized.Payload.ShouldBeOfType(Snapshot.Payload.GetType()); - } - - public abstract class using_serialization - { - protected static readonly ISerialize Serializer = new SerializationFactory().Build(); - } -} - -// ReSharper enable InconsistentNaming -#pragma warning restore 169 \ No newline at end of file diff --git a/src/tests/EventStore.Serialization.AcceptanceTests/SimpleMessage.cs b/src/tests/EventStore.Serialization.AcceptanceTests/SimpleMessage.cs deleted file mode 100644 index b6e4e7ac0..000000000 --- a/src/tests/EventStore.Serialization.AcceptanceTests/SimpleMessage.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace EventStore.Serialization.AcceptanceTests -{ - using System; - using System.Collections.Generic; - using System.Diagnostics.CodeAnalysis; - - [Serializable] - public class SimpleMessage - { - public SimpleMessage() - { - this.Contents = new List(); - } - - public Guid Id { get; set; } - public DateTime Created { get; set; } - public string Value { get; set; } - public int Count { get; set; } - - [SuppressMessage("Microsoft.Design", "CA1002:DoNotExposeGenericLists", - Justification = "This is an acceptance test DTO and the structure doesn't really matter.")] - public List Contents { get; private set; } - } -} \ No newline at end of file diff --git a/src/tests/EventStore.Serialization.AcceptanceTests/packages.config b/src/tests/EventStore.Serialization.AcceptanceTests/packages.config deleted file mode 100644 index c1bb9d2b1..000000000 --- a/src/tests/EventStore.Serialization.AcceptanceTests/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/src/tests_dotnetcore.runsettings b/src/tests_dotnetcore.runsettings new file mode 100644 index 000000000..a14d14a9a --- /dev/null +++ b/src/tests_dotnetcore.runsettings @@ -0,0 +1,6 @@ + + + + true + + \ No newline at end of file