├── .gitignore ├── LICENSE ├── README.md ├── StringDB.sln ├── addons ├── StringDB.Ceras │ ├── CerasTransformer.cs │ ├── CerasTransformerExtensions.cs │ └── StringDB.Ceras.csproj └── addons.md ├── appveyor.yml ├── examples └── 01_GettingStarted │ ├── 01_GettingStarted.csproj │ └── Program.cs ├── icons ├── banner_ad.png ├── insert_range.svg ├── single_inserts.svg └── tiny.png ├── src ├── StringDB.PerformanceNumbers │ ├── ClassCastOrStructCast.cs │ ├── InsertRangeFileSize.cs │ ├── Program.cs │ ├── SingleInsertFileSize.cs │ ├── StringDB.PerformanceNumbers.csproj │ └── YieldOrLinq.cs └── StringDB │ ├── DatabaseExtensions.cs │ ├── Databases │ ├── BaseDatabase.cs │ ├── BufferedDatabase.cs │ ├── CacheDatabase.cs │ ├── IODatabase.cs │ ├── MemoryDatabase.cs │ ├── ReadOnlyDatabase.cs │ ├── ThreadLockDatabase.cs │ ├── TransformDatabase.cs │ └── WriteOnlyDatabase.cs │ ├── Fluency │ ├── BufferedDatabaseExtensions.cs │ ├── CacheDatabaseExtensions.cs │ ├── DatabaseBuilder.cs │ ├── DatabaseIODeviceBuilder.cs │ ├── DatabaseIODeviceBuilderExtensions.cs │ ├── IODatabaseExtensions.cs │ ├── IODatabaseOptions.cs │ ├── MemoryDatabaseExtensions.cs │ ├── ReadOnlyDatabaseExtensions.cs │ ├── ThreadLockDatabaseExtensions.cs │ ├── TransformDatabaseExtensions.cs │ └── WriteOnlyDatabaseExtensions.cs │ ├── GlobalSuppressions.cs │ ├── IDatabase.cs │ ├── IDatabaseLayer.cs │ ├── ILazyLoader.cs │ ├── IO │ ├── ByteBuffer.cs │ ├── Compatibility │ │ ├── StringDB10_0_0LowlevelDatabaseIODevice.cs │ │ └── StringDB5_0_0LowlevelDatabaseIODevice.cs │ ├── DatabaseIODevice.cs │ ├── DatabaseItem.cs │ ├── IDatabaseIODevice.cs │ ├── ILowlevelDatabaseIODevice.cs │ ├── IOptimalToken.cs │ ├── IOptimalTokenSource.cs │ ├── LowLevelDatabaseItem.cs │ ├── NextItemPeek.cs │ ├── OptimalTokenSource.cs │ ├── StoneVaultIODevice.cs │ ├── StreamCacheMonitor.cs │ └── StringDBVersion.cs │ ├── ITransformer.cs │ ├── LazyLoaderAwaiter.cs │ ├── LazyLoaderExtensions.cs │ ├── LazyLoaders │ ├── CachedLoader.cs │ ├── IOLoader.cs │ ├── ThreadLockLoader.cs │ ├── TransformLoader.cs │ └── ValueLoader.cs │ ├── StringDB.csproj │ ├── StringDatabase.cs │ └── Transformers │ ├── NoneTransformer.cs │ ├── ReverseTransformer.cs │ ├── ReverseTransformerExtensions.cs │ └── StringTransformer.cs └── tests └── StringDB.Tests ├── DatabaseExtensionTests.cs ├── DatabaseIODeviceTests.cs ├── DatabaseTests ├── BaseDatabaseTests.cs ├── BufferedDatabaseTests.cs ├── CacheDatabaseTests.cs ├── IODatabaseTests.cs ├── MemoryDatabaseTests.cs ├── ReadOnlyDatabaseTests.cs ├── StringDatabaseTests.cs ├── ThreadLockTests.cs ├── TransformerDatabaseTests.cs └── WriteOnlyDatabaseTests.cs ├── FluencyTests.cs ├── GlobalSuppressions.cs ├── IMockDatabase.cs ├── IntegrationTests.cs ├── LazyItem.cs ├── LazyLoaderAwaiterTests.cs ├── Mocks ├── IntToStringTransformer.cs ├── MockBaseDatabase.cs ├── MockDatabase.cs ├── MockDatabaseExtensions.cs ├── MockDatabaseIODevice.cs └── MockTransformer.cs ├── NotThoroughTests └── 5_0_0.cs ├── OptimalTokenTests ├── DatabaseIODeviceOptimalTokenTests.cs ├── OptimalEnumerationTests.cs └── OptimalTokenTests.cs ├── StoneVaultIODeviceTests.cs ├── StringDB.Tests.csproj ├── StringDB10_0_0Tests.cs └── TransformerTests ├── NoneTransformerTests.cs ├── ReverseTransformerTests.cs └── StringTransformerTests.cs /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | gitmv.bat 7 | 8 | # User-specific files 9 | *.rsuser 10 | *.suo 11 | *.user 12 | *.userosscache 13 | *.sln.docstates 14 | 15 | # User-specific files (MonoDevelop/Xamarin Studio) 16 | *.userprefs 17 | 18 | # Build results 19 | [Dd]ebug/ 20 | [Dd]ebugPublic/ 21 | [Rr]elease/ 22 | [Rr]eleases/ 23 | x64/ 24 | x86/ 25 | [Aa][Rr][Mm]/ 26 | [Aa][Rr][Mm]64/ 27 | bld/ 28 | [Bb]in/ 29 | [Oo]bj/ 30 | [Ll]og/ 31 | 32 | # Visual Studio 2015/2017 cache/options directory 33 | .vs/ 34 | # Uncomment if you have tasks that create the project's static files in wwwroot 35 | #wwwroot/ 36 | 37 | # Visual Studio 2017 auto generated files 38 | Generated\ Files/ 39 | 40 | # MSTest test Results 41 | [Tt]est[Rr]esult*/ 42 | [Bb]uild[Ll]og.* 43 | 44 | # NUNIT 45 | *.VisualState.xml 46 | TestResult.xml 47 | 48 | # Build Results of an ATL Project 49 | [Dd]ebugPS/ 50 | [Rr]eleasePS/ 51 | dlldata.c 52 | 53 | # Benchmark Results 54 | BenchmarkDotNet.Artifacts/ 55 | 56 | # .NET Core 57 | project.lock.json 58 | project.fragment.lock.json 59 | artifacts/ 60 | 61 | # StyleCop 62 | StyleCopReport.xml 63 | 64 | # Files built by Visual Studio 65 | *_i.c 66 | *_p.c 67 | *_h.h 68 | *.ilk 69 | *.meta 70 | *.obj 71 | *.iobj 72 | *.pch 73 | *.pdb 74 | *.ipdb 75 | *.pgc 76 | *.pgd 77 | *.rsp 78 | *.sbr 79 | *.tlb 80 | *.tli 81 | *.tlh 82 | *.tmp 83 | *.tmp_proj 84 | *_wpftmp.csproj 85 | *.log 86 | *.vspscc 87 | *.vssscc 88 | .builds 89 | *.pidb 90 | *.svclog 91 | *.scc 92 | 93 | # Chutzpah Test files 94 | _Chutzpah* 95 | 96 | # Visual C++ cache files 97 | ipch/ 98 | *.aps 99 | *.ncb 100 | *.opendb 101 | *.opensdf 102 | *.sdf 103 | *.cachefile 104 | *.VC.db 105 | *.VC.VC.opendb 106 | 107 | # Visual Studio profiler 108 | *.psess 109 | *.vsp 110 | *.vspx 111 | *.sap 112 | 113 | # Visual Studio Trace Files 114 | *.e2e 115 | 116 | # TFS 2012 Local Workspace 117 | $tf/ 118 | 119 | # Guidance Automation Toolkit 120 | *.gpState 121 | 122 | # ReSharper is a .NET coding add-in 123 | _ReSharper*/ 124 | *.[Rr]e[Ss]harper 125 | *.DotSettings.user 126 | 127 | # JustCode is a .NET coding add-in 128 | .JustCode 129 | 130 | # TeamCity is a build add-in 131 | _TeamCity* 132 | 133 | # DotCover is a Code Coverage Tool 134 | *.dotCover 135 | 136 | # AxoCover is a Code Coverage Tool 137 | .axoCover/* 138 | !.axoCover/settings.json 139 | 140 | # Visual Studio code coverage results 141 | *.coverage 142 | *.coveragexml 143 | 144 | # NCrunch 145 | _NCrunch_* 146 | .*crunch*.local.xml 147 | nCrunchTemp_* 148 | 149 | # MightyMoose 150 | *.mm.* 151 | AutoTest.Net/ 152 | 153 | # Web workbench (sass) 154 | .sass-cache/ 155 | 156 | # Installshield output folder 157 | [Ee]xpress/ 158 | 159 | # DocProject is a documentation generator add-in 160 | DocProject/buildhelp/ 161 | DocProject/Help/*.HxT 162 | DocProject/Help/*.HxC 163 | DocProject/Help/*.hhc 164 | DocProject/Help/*.hhk 165 | DocProject/Help/*.hhp 166 | DocProject/Help/Html2 167 | DocProject/Help/html 168 | 169 | # Click-Once directory 170 | publish/ 171 | 172 | # Publish Web Output 173 | *.[Pp]ublish.xml 174 | *.azurePubxml 175 | # Note: Comment the next line if you want to checkin your web deploy settings, 176 | # but database connection strings (with potential passwords) will be unencrypted 177 | *.pubxml 178 | *.publishproj 179 | 180 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 181 | # checkin your Azure Web App publish settings, but sensitive information contained 182 | # in these scripts will be unencrypted 183 | PublishScripts/ 184 | 185 | # NuGet Packages 186 | *.nupkg 187 | # The packages folder can be ignored because of Package Restore 188 | **/[Pp]ackages/* 189 | # except build/, which is used as an MSBuild target. 190 | !**/[Pp]ackages/build/ 191 | # Uncomment if necessary however generally it will be regenerated when needed 192 | #!**/[Pp]ackages/repositories.config 193 | # NuGet v3's project.json files produces more ignorable files 194 | *.nuget.props 195 | *.nuget.targets 196 | 197 | # Microsoft Azure Build Output 198 | csx/ 199 | *.build.csdef 200 | 201 | # Microsoft Azure Emulator 202 | ecf/ 203 | rcf/ 204 | 205 | # Windows Store app package directories and files 206 | AppPackages/ 207 | BundleArtifacts/ 208 | Package.StoreAssociation.xml 209 | _pkginfo.txt 210 | *.appx 211 | 212 | # Visual Studio cache files 213 | # files ending in .cache can be ignored 214 | *.[Cc]ache 215 | # but keep track of directories ending in .cache 216 | !?*.[Cc]ache/ 217 | 218 | # Others 219 | ClientBin/ 220 | ~$* 221 | *~ 222 | *.dbmdl 223 | *.dbproj.schemaview 224 | *.jfm 225 | *.pfx 226 | *.publishsettings 227 | orleans.codegen.cs 228 | 229 | # Including strong name files can present a security risk 230 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 231 | #*.snk 232 | 233 | # Since there are multiple workflows, uncomment next line to ignore bower_components 234 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 235 | #bower_components/ 236 | # ASP.NET Core default setup: bower directory is configured as wwwroot/lib/ and bower restore is true 237 | **/wwwroot/lib/ 238 | 239 | # RIA/Silverlight projects 240 | Generated_Code/ 241 | 242 | # Backup & report files from converting an old project file 243 | # to a newer Visual Studio version. Backup files are not needed, 244 | # because we have git ;-) 245 | _UpgradeReport_Files/ 246 | Backup*/ 247 | UpgradeLog*.XML 248 | UpgradeLog*.htm 249 | ServiceFabricBackup/ 250 | *.rptproj.bak 251 | 252 | # SQL Server files 253 | *.mdf 254 | *.ldf 255 | *.ndf 256 | 257 | # Business Intelligence projects 258 | *.rdl.data 259 | *.bim.layout 260 | *.bim_*.settings 261 | *.rptproj.rsuser 262 | *- Backup*.rdl 263 | 264 | # Microsoft Fakes 265 | FakesAssemblies/ 266 | 267 | # GhostDoc plugin setting file 268 | *.GhostDoc.xml 269 | 270 | # Node.js Tools for Visual Studio 271 | .ntvs_analysis.dat 272 | node_modules/ 273 | 274 | # Visual Studio 6 build log 275 | *.plg 276 | 277 | # Visual Studio 6 workspace options file 278 | *.opt 279 | 280 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 281 | *.vbw 282 | 283 | # Visual Studio LightSwitch build output 284 | **/*.HTMLClient/GeneratedArtifacts 285 | **/*.DesktopClient/GeneratedArtifacts 286 | **/*.DesktopClient/ModelManifest.xml 287 | **/*.Server/GeneratedArtifacts 288 | **/*.Server/ModelManifest.xml 289 | _Pvt_Extensions 290 | 291 | # Paket dependency manager 292 | .paket/paket.exe 293 | paket-files/ 294 | 295 | # FAKE - F# Make 296 | .fake/ 297 | 298 | # JetBrains Rider 299 | .idea/ 300 | *.sln.iml 301 | 302 | # CodeRush personal settings 303 | .cr/personal 304 | 305 | # Python Tools for Visual Studio (PTVS) 306 | __pycache__/ 307 | *.pyc 308 | 309 | # Cake - Uncomment if you are using it 310 | # tools/** 311 | # !tools/packages.config 312 | 313 | # Tabs Studio 314 | *.tss 315 | 316 | # Telerik's JustMock configuration file 317 | *.jmconfig 318 | 319 | # BizTalk build output 320 | *.btp.cs 321 | *.btm.cs 322 | *.odx.cs 323 | *.xsd.cs 324 | 325 | # OpenCover UI analysis results 326 | OpenCover/ 327 | 328 | # Azure Stream Analytics local run output 329 | ASALocalRun/ 330 | 331 | # MSBuild Binary and Structured Log 332 | *.binlog 333 | 334 | # NVidia Nsight GPU debugger configuration file 335 | *.nvuser 336 | 337 | # MFractors (Xamarin productivity tool) working folder 338 | .mfractor/ 339 | 340 | # Local History for Visual Studio 341 | .localhistory/ 342 | 343 | # BeatPulse healthcheck temp database 344 | healthchecksdb -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2018-2019 SirJosh3917 2 | 3 | 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: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # StringDB 2 |
3 | StringDB 4 | 5 | [![Build Status][badge_appveyor_build_image]][badge_appveyor_build_page] 6 | [![Test Status][badge_tests_image]][link_codecov] 7 | [![Nuget Version][badge_nuget_version_image]][link_nuget] 8 | [![Nuget Downloads][badge_nuget_downloads_image]][link_nuget] 9 | 10 |
11 | 12 | [```Install-Package StringDB```][link_nuget] 13 | 14 | ## Introduction 15 | 16 | StringDB is a key/value pair store with a friendly API to use as little RAM and space as possible. 17 | 18 | Verify the claims for yourself: 19 | 20 | - [Api][section_api] 21 | - [Tiny][section_tiny] 22 | 23 | ## Api 24 | 25 | Enumerate over a database and it's values, the fastest, by enumerating over it optimally 26 | ```cs 27 | using var db = new DatabaseBuilder() 28 | .UseIODatabase(StringDBVersion.Latest, "database.db", out var optimalTokenSource) 29 | .WithBuffer(1000) 30 | .WithTranform(StringTransformation.Default, StringTransformation.Default); 31 | 32 | foreach (var (key, value) in db.EnumerateOptimally(optimalTokenSource)) 33 | { 34 | // do something with the key and value 35 | } 36 | ``` 37 | 38 | Use fluent extensions to create a database: 39 | 40 | ```cs 41 | using IDatabase db = new DatabaseBuilder() 42 | .UseIODatabase(StringDBVersion.Latest, "database.db") 43 | .WithBuffer(1000) 44 | .WithTransform(StringTransformation.Default, StringTransformation.Default); 45 | 46 | using IDatabase memDb = new DatabaseBuilder() 47 | .UseMemoryDatabase(); 48 | ``` 49 | 50 | Use the IDatabase interface to interface with databases 51 | 52 | ```cs 53 | void InsertGreeting(IDatabase database, string user) 54 | { 55 | database.Insert(user, $"Greetings, {user}!"); 56 | } 57 | ``` 58 | 59 | And inherit BaseDatabase to create your own IDatabases with minimal effort 60 | 61 | ```cs 62 | public class TestDatabase : BaseDatabase 63 | { 64 | private class LazyValue : ILazyLoader 65 | { 66 | private readonly string _value; 67 | public LazyValue(int value) => _value = value.ToString(); 68 | public string Load() => _value; 69 | public void Dispose() {} 70 | } 71 | 72 | public override void Dispose() {} 73 | 74 | protected override void InsertRange(KeyValuePair[] items) 75 | { 76 | foreach(var item in items) 77 | { 78 | Console.WriteLine($"{item.Key}: {item.Value}"); 79 | } 80 | } 81 | 82 | protected override IEnumerable>> Evaluate() 83 | { 84 | for(var i = 0; i < int.MaxValue) 85 | { 86 | yield return KeyValuePair.Create(i, new LazyValue(i)); 87 | } 88 | } 89 | } 90 | ``` 91 | 92 | ## Tiny ![icon_tiny] 93 | 94 | StringDB is *tiny*. Use *tiny* amounts of RAM, and *tiny* amounts of space. 95 | 96 | ### [StringDB 10.0.0 file size: single inserts, 128 byte keys, 1024 byte values][source_insert_test] 97 | 98 | ![Chart][icon_chart_single_inserts] 99 | 100 | | Inserts | Size (in KB, 1000 bytes) | Absolute Minimum Size Possible | StringDB Overhead Percentage | 101 | | --- | --- | --- | --- | 102 | | 1 | 1.172 KB | 1.152 KB | 1.706485% | 103 | | 50 | 58.208 KB | 57.6 KB | 1.04453% | 104 | | 100 | 116.408 KB | 115.2 KB | 1.037729% | 105 | 106 | This chart shows the size of a StringDB file after multiple *single inserts*. Every key is 128 bytes long, and every value is 1024 bytes long. By doing single inserts, file size is dramatically affected due to the additional overhead for the index chain. 107 | 108 | ### [StringDB 10.0.0 file size: insert range, 128 byte keys, 1024 byte values][source_insertrange_test] 109 | 110 | ![Chart][icon_chart_insert_range] 111 | 112 | | Elements in Insert Range | Size (in KB, 1000 bytes) | Absolute Minimum Size Possible | StringDB Overhead Percentage | 113 | | --- | --- | --- | --- | 114 | | 1 | 1.172 KB | 1.152 KB | 1.706485% | 115 | | 50 | 57.963 KB | 57.6 KB | 0.626262% | 116 | | 100 | 115.913 KB | 115.2 KB | 0.615117% | 117 | 118 | This chart shows the size of a StringDB file after a single insert range with the amount of items specified. 119 | 120 | ## Addons 121 | 122 | Official addon support will be maintained for [these libraries.][link_addons] 123 | 124 | ## Issues welcomed! 125 | 126 | Don't be afraid to make an issue about anything and everything! 127 | 128 | - Is there something weird with how databases are created? Submit an issue! 129 | - Is there something that you wish you could do but can't? Submit an issue! 130 | - Is this library not suitable for your purposes? Submit an isssue! 131 | - Want it to do something? Submit an issue! 132 | 133 | It's an honour to have you use this library, and feedback is needed to make this the greatest it can be. 134 | 135 | Need immediate assistence? [Join the discord!](discord) 136 | 137 | [icon_banner_ad]: ./icons/banner_ad.png 138 | [icon_tiny]: ./icons/tiny.png 139 | [icon_chart_single_inserts]: ./icons/single_inserts.svg 140 | [icon_chart_insert_range]: ./icons/insert_range.svg 141 | 142 | [badge_appveyor_build_image]: https://img.shields.io/appveyor/ci/SirJosh3917/StringDB/master.svg?style=flat-square 143 | [badge_tests_image]: https://img.shields.io/codecov/c/github/SirJosh3917/StringDB/master.svg?style=flat-square 144 | [badge_nuget_version_image]: https://img.shields.io/nuget/v/StringDB.svg?style=flat-square 145 | [badge_nuget_downloads_image]: https://img.shields.io/nuget/dt/StringDB.svg?style=flat-square 146 | 147 | [badge_appveyor_build_page]: https://ci.appveyor.com/project/sirjosh3917/stringdb 148 | 149 | [link_nuget]: https://www.nuget.org/packages/StringDB 150 | [link_addons]: ./addons/addons.md 151 | [link_codecov]: https://codecov.io/gh/SirJosh3917/StringDB 152 | 153 | [section_tiny]: #tiny- 154 | [section_api]: #api 155 | 156 | [source_insert_test]: ./src/StringDB.PerformanceNumbers/SingleInsertFileSize.cs 157 | [source_insertrange_test]: ./src/StringDB.PerformanceNumbers/InsertRangeFileSize.cs 158 | 159 | [discord]: https://discord.gg/wVcnkKJ 160 | -------------------------------------------------------------------------------- /StringDB.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29001.49 5 | MinimumVisualStudioVersion = 15.0.26124.0 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StringDB", "src\StringDB\StringDB.csproj", "{0D4250DC-5B7F-4DED-9311-C19EC460BD07}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StringDB.Tests", "tests\StringDB.Tests\StringDB.Tests.csproj", "{4DC0410C-243F-4586-8286-58C8859FAC67}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StringDB.PerformanceNumbers", "src\StringDB.PerformanceNumbers\StringDB.PerformanceNumbers.csproj", "{6EDA5430-F663-487C-9C3C-B9F5D351DD41}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StringDB.Ceras", "addons\StringDB.Ceras\StringDB.Ceras.csproj", "{549EAB71-DE37-4E34-898F-A53FA5AAD573}" 13 | EndProject 14 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "addons", "addons", "{C054D45E-5919-486F-A808-AE1835EBCB4A}" 15 | EndProject 16 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "examples", "examples", "{5219A34D-17CE-471B-BFFB-CEDE30FBCCAE}" 17 | EndProject 18 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "01_GettingStarted", "examples\01_GettingStarted\01_GettingStarted.csproj", "{ECA7066C-C416-4F4D-992D-AE3F73908746}" 19 | EndProject 20 | Global 21 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 22 | Debug|Any CPU = Debug|Any CPU 23 | Debug|x64 = Debug|x64 24 | Debug|x86 = Debug|x86 25 | Release|Any CPU = Release|Any CPU 26 | Release|x64 = Release|x64 27 | Release|x86 = Release|x86 28 | EndGlobalSection 29 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 30 | {0D4250DC-5B7F-4DED-9311-C19EC460BD07}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {0D4250DC-5B7F-4DED-9311-C19EC460BD07}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {0D4250DC-5B7F-4DED-9311-C19EC460BD07}.Debug|x64.ActiveCfg = Debug|Any CPU 33 | {0D4250DC-5B7F-4DED-9311-C19EC460BD07}.Debug|x64.Build.0 = Debug|Any CPU 34 | {0D4250DC-5B7F-4DED-9311-C19EC460BD07}.Debug|x86.ActiveCfg = Debug|Any CPU 35 | {0D4250DC-5B7F-4DED-9311-C19EC460BD07}.Debug|x86.Build.0 = Debug|Any CPU 36 | {0D4250DC-5B7F-4DED-9311-C19EC460BD07}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {0D4250DC-5B7F-4DED-9311-C19EC460BD07}.Release|Any CPU.Build.0 = Release|Any CPU 38 | {0D4250DC-5B7F-4DED-9311-C19EC460BD07}.Release|x64.ActiveCfg = Release|Any CPU 39 | {0D4250DC-5B7F-4DED-9311-C19EC460BD07}.Release|x64.Build.0 = Release|Any CPU 40 | {0D4250DC-5B7F-4DED-9311-C19EC460BD07}.Release|x86.ActiveCfg = Release|Any CPU 41 | {0D4250DC-5B7F-4DED-9311-C19EC460BD07}.Release|x86.Build.0 = Release|Any CPU 42 | {4DC0410C-243F-4586-8286-58C8859FAC67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 43 | {4DC0410C-243F-4586-8286-58C8859FAC67}.Debug|Any CPU.Build.0 = Debug|Any CPU 44 | {4DC0410C-243F-4586-8286-58C8859FAC67}.Debug|x64.ActiveCfg = Debug|Any CPU 45 | {4DC0410C-243F-4586-8286-58C8859FAC67}.Debug|x64.Build.0 = Debug|Any CPU 46 | {4DC0410C-243F-4586-8286-58C8859FAC67}.Debug|x86.ActiveCfg = Debug|Any CPU 47 | {4DC0410C-243F-4586-8286-58C8859FAC67}.Debug|x86.Build.0 = Debug|Any CPU 48 | {4DC0410C-243F-4586-8286-58C8859FAC67}.Release|Any CPU.ActiveCfg = Release|Any CPU 49 | {4DC0410C-243F-4586-8286-58C8859FAC67}.Release|Any CPU.Build.0 = Release|Any CPU 50 | {4DC0410C-243F-4586-8286-58C8859FAC67}.Release|x64.ActiveCfg = Release|Any CPU 51 | {4DC0410C-243F-4586-8286-58C8859FAC67}.Release|x64.Build.0 = Release|Any CPU 52 | {4DC0410C-243F-4586-8286-58C8859FAC67}.Release|x86.ActiveCfg = Release|Any CPU 53 | {4DC0410C-243F-4586-8286-58C8859FAC67}.Release|x86.Build.0 = Release|Any CPU 54 | {6EDA5430-F663-487C-9C3C-B9F5D351DD41}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 55 | {6EDA5430-F663-487C-9C3C-B9F5D351DD41}.Debug|Any CPU.Build.0 = Debug|Any CPU 56 | {6EDA5430-F663-487C-9C3C-B9F5D351DD41}.Debug|x64.ActiveCfg = Debug|Any CPU 57 | {6EDA5430-F663-487C-9C3C-B9F5D351DD41}.Debug|x64.Build.0 = Debug|Any CPU 58 | {6EDA5430-F663-487C-9C3C-B9F5D351DD41}.Debug|x86.ActiveCfg = Debug|Any CPU 59 | {6EDA5430-F663-487C-9C3C-B9F5D351DD41}.Debug|x86.Build.0 = Debug|Any CPU 60 | {6EDA5430-F663-487C-9C3C-B9F5D351DD41}.Release|Any CPU.ActiveCfg = Release|Any CPU 61 | {6EDA5430-F663-487C-9C3C-B9F5D351DD41}.Release|Any CPU.Build.0 = Release|Any CPU 62 | {6EDA5430-F663-487C-9C3C-B9F5D351DD41}.Release|x64.ActiveCfg = Release|Any CPU 63 | {6EDA5430-F663-487C-9C3C-B9F5D351DD41}.Release|x64.Build.0 = Release|Any CPU 64 | {6EDA5430-F663-487C-9C3C-B9F5D351DD41}.Release|x86.ActiveCfg = Release|Any CPU 65 | {6EDA5430-F663-487C-9C3C-B9F5D351DD41}.Release|x86.Build.0 = Release|Any CPU 66 | {549EAB71-DE37-4E34-898F-A53FA5AAD573}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 67 | {549EAB71-DE37-4E34-898F-A53FA5AAD573}.Debug|Any CPU.Build.0 = Debug|Any CPU 68 | {549EAB71-DE37-4E34-898F-A53FA5AAD573}.Debug|x64.ActiveCfg = Debug|Any CPU 69 | {549EAB71-DE37-4E34-898F-A53FA5AAD573}.Debug|x64.Build.0 = Debug|Any CPU 70 | {549EAB71-DE37-4E34-898F-A53FA5AAD573}.Debug|x86.ActiveCfg = Debug|Any CPU 71 | {549EAB71-DE37-4E34-898F-A53FA5AAD573}.Debug|x86.Build.0 = Debug|Any CPU 72 | {549EAB71-DE37-4E34-898F-A53FA5AAD573}.Release|Any CPU.ActiveCfg = Release|Any CPU 73 | {549EAB71-DE37-4E34-898F-A53FA5AAD573}.Release|Any CPU.Build.0 = Release|Any CPU 74 | {549EAB71-DE37-4E34-898F-A53FA5AAD573}.Release|x64.ActiveCfg = Release|Any CPU 75 | {549EAB71-DE37-4E34-898F-A53FA5AAD573}.Release|x64.Build.0 = Release|Any CPU 76 | {549EAB71-DE37-4E34-898F-A53FA5AAD573}.Release|x86.ActiveCfg = Release|Any CPU 77 | {549EAB71-DE37-4E34-898F-A53FA5AAD573}.Release|x86.Build.0 = Release|Any CPU 78 | {ECA7066C-C416-4F4D-992D-AE3F73908746}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 79 | {ECA7066C-C416-4F4D-992D-AE3F73908746}.Debug|Any CPU.Build.0 = Debug|Any CPU 80 | {ECA7066C-C416-4F4D-992D-AE3F73908746}.Debug|x64.ActiveCfg = Debug|Any CPU 81 | {ECA7066C-C416-4F4D-992D-AE3F73908746}.Debug|x64.Build.0 = Debug|Any CPU 82 | {ECA7066C-C416-4F4D-992D-AE3F73908746}.Debug|x86.ActiveCfg = Debug|Any CPU 83 | {ECA7066C-C416-4F4D-992D-AE3F73908746}.Debug|x86.Build.0 = Debug|Any CPU 84 | {ECA7066C-C416-4F4D-992D-AE3F73908746}.Release|Any CPU.ActiveCfg = Release|Any CPU 85 | {ECA7066C-C416-4F4D-992D-AE3F73908746}.Release|Any CPU.Build.0 = Release|Any CPU 86 | {ECA7066C-C416-4F4D-992D-AE3F73908746}.Release|x64.ActiveCfg = Release|Any CPU 87 | {ECA7066C-C416-4F4D-992D-AE3F73908746}.Release|x64.Build.0 = Release|Any CPU 88 | {ECA7066C-C416-4F4D-992D-AE3F73908746}.Release|x86.ActiveCfg = Release|Any CPU 89 | {ECA7066C-C416-4F4D-992D-AE3F73908746}.Release|x86.Build.0 = Release|Any CPU 90 | EndGlobalSection 91 | GlobalSection(SolutionProperties) = preSolution 92 | HideSolutionNode = FALSE 93 | EndGlobalSection 94 | GlobalSection(NestedProjects) = preSolution 95 | {549EAB71-DE37-4E34-898F-A53FA5AAD573} = {C054D45E-5919-486F-A808-AE1835EBCB4A} 96 | {ECA7066C-C416-4F4D-992D-AE3F73908746} = {5219A34D-17CE-471B-BFFB-CEDE30FBCCAE} 97 | EndGlobalSection 98 | GlobalSection(ExtensibilityGlobals) = postSolution 99 | SolutionGuid = {33F9EBBB-1CB1-4C36-9F2B-D2684AFA8371} 100 | EndGlobalSection 101 | EndGlobal 102 | -------------------------------------------------------------------------------- /addons/StringDB.Ceras/CerasTransformer.cs: -------------------------------------------------------------------------------- 1 | using Ceras; 2 | 3 | using JetBrains.Annotations; 4 | 5 | using System.Runtime.CompilerServices; 6 | 7 | namespace StringDB.Ceras 8 | { 9 | /// 10 | /// Use Ceras as a serializer and deserializer for byte arrays. 11 | /// 12 | /// The type of object to store. 13 | [PublicAPI] 14 | public class CerasTransformer : ITransformer 15 | { 16 | /// 17 | /// A default, global instance of this kind of transformer 18 | /// that uses the CerasSerializer at . 19 | /// 20 | public static CerasTransformer Default { get; } = new CerasTransformer(CerasTransformerExtensions.CerasInstance); 21 | 22 | private readonly CerasSerializer _ceras; 23 | 24 | /// 25 | /// Creates a new CerasTransformer. 26 | /// 27 | /// The serializer to use. 28 | public CerasTransformer([NotNull] CerasSerializer ceras) => _ceras = ceras; 29 | 30 | /// 31 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 32 | public T TransformPre(byte[] pre) => _ceras.Deserialize(pre); 33 | 34 | /// 35 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 36 | public byte[] TransformPost(T post) => _ceras.Serialize(post); 37 | } 38 | } -------------------------------------------------------------------------------- /addons/StringDB.Ceras/CerasTransformerExtensions.cs: -------------------------------------------------------------------------------- 1 | using Ceras; 2 | 3 | using JetBrains.Annotations; 4 | 5 | using StringDB.Databases; 6 | using StringDB.Fluency; 7 | 8 | using System.Runtime.CompilerServices; 9 | 10 | namespace StringDB.Ceras 11 | { 12 | /// 13 | /// Fluent extensions to apply to byte array based databases. 14 | /// 15 | [PublicAPI] 16 | public static class CerasTransformerExtensions 17 | { 18 | /// 19 | /// A global instance of a default CerasSerializer instance to use. 20 | /// 21 | public static CerasSerializer CerasInstance { get; } = new CerasSerializer(); 22 | 23 | /// 24 | /// Use Ceras with a database. Uses as a serializer. 25 | /// 26 | /// The type of key. 27 | /// The type of value. 28 | /// The database to apply Ceras to. 29 | /// A that has a applied to it. 30 | [NotNull] 31 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 32 | public static IDatabase WithCeras 33 | ( 34 | [NotNull] this IDatabase database 35 | ) 36 | => database.WithTransform 37 | ( 38 | CerasTransformer.Default, 39 | CerasTransformer.Default 40 | ); 41 | 42 | /// 43 | /// Use Ceras with a database. 44 | /// 45 | /// The type of key. 46 | /// The type of value. 47 | /// The database to use. 48 | /// The serializer to use. 49 | /// A that has a applied to it. 50 | [NotNull] 51 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 52 | public static IDatabase WithCeras 53 | ( 54 | [NotNull] this IDatabase database, 55 | [NotNull] CerasSerializer serializer 56 | ) 57 | => database.WithTransform 58 | ( 59 | new CerasTransformer(serializer), 60 | new CerasTransformer(serializer) 61 | ); 62 | } 63 | } -------------------------------------------------------------------------------- /addons/StringDB.Ceras/StringDB.Ceras.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net45;netstandard2.0 5 | 10.0.2 6 | SirJosh3917 7 | Integrate StringDB with Ceras easier. 8 | 9 | https://github.com/SirJosh3917/StringDB/tree/master/src/StringDB.Ceras 10 | 11 | https://rawcdn.githack.com/SirJosh3917/StringDB/master/icons/banner_ad.png 12 | https://github.com/SirJosh3917/StringDB/ 13 | Ceras, StringDB 14 | true 15 | Updated Ceras to 4.0.38 & use StringDB 10.0.2 16 | MIT 17 | true 18 | true 19 | snupkg 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /addons/addons.md: -------------------------------------------------------------------------------- 1 | # Addons 2 | 3 | StringDB will officially maintain support for integration with some libraries. 4 | 5 | - [StringDB.Ceras](https://www.nuget.org/packages/StringDB.Ceras/10.0.1) -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | os: Visual Studio 2017 2 | clone_depth: 1 3 | deploy: off 4 | 5 | branches: 6 | only: 7 | - master 8 | 9 | assembly_info: 10 | assembly_informational_version: "{version} - CI (AppVeyor, branch: {branch})" 11 | 12 | configuration: 13 | - Release 14 | 15 | init: 16 | - cmd: git config --global core.autocrlf true 17 | 18 | before_build: 19 | - dotnet restore 20 | - nuget install OpenCover -Version 4.6.519 -OutputDirectory packages 21 | - nuget install Codecov -Version 1.0.3 -OutputDirectory packages 22 | 23 | build_script: 24 | - cmd: dotnet build -c %CONFIGURATION% -f net45 "src\StringDB\StringDB.csproj" 25 | - cmd: dotnet build -c %CONFIGURATION% -f netstandard2.0 "src\StringDB\StringDB.csproj" 26 | 27 | test_script: 28 | - ps: | 29 | if ($env:CONFIGURATION -eq 'Release') 30 | { 31 | dotnet test tests\StringDB.Tests\ /p:CollectCoverage=true /p:CoverletOutputFormat=opencover 32 | 33 | packages\Codecov.1.0.3\tools\codecov.exe -f "tests\StringDB.Tests\coverage.opencover.xml" 34 | } 35 | 36 | artifacts: 37 | - path: 'src\StringDB\bin\%CONFIGURATION%\*.nupkg' 38 | name: StringDB.zip -------------------------------------------------------------------------------- /examples/01_GettingStarted/01_GettingStarted.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp2.2 6 | _01_GettingStarted 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/01_GettingStarted/Program.cs: -------------------------------------------------------------------------------- 1 | using StringDB; 2 | 3 | using System; 4 | 5 | namespace _01_GettingStarted 6 | { 7 | // Just an extremely simple example using the StringDatabase helper 8 | 9 | internal static class Program 10 | { 11 | private static void Main() 12 | { 13 | // this is the most simplest way to use StringDB 14 | // this creates a database in RAM 15 | using (var db = StringDatabase.Create()) 16 | { 17 | // we can insert stuff 18 | db.Insert("key", "Hello, World!"); 19 | 20 | // get that value 21 | var value = db.Get("key"); 22 | 23 | // write it to the console 24 | Console.WriteLine(value); 25 | } 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /icons/banner_ad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monoclex/StringDB/387d6ff31792aee715bd280cc0f2f8f234610e50/icons/banner_ad.png -------------------------------------------------------------------------------- /icons/tiny.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monoclex/StringDB/387d6ff31792aee715bd280cc0f2f8f234610e50/icons/tiny.png -------------------------------------------------------------------------------- /src/StringDB.PerformanceNumbers/ClassCastOrStructCast.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Attributes; 2 | 3 | namespace StringDB.PerformanceNumbers 4 | { 5 | // | Method | Mean | Error | StdDev | 6 | // |------- |---------:|----------:|----------:| 7 | // | Class | 3.393 ns | 0.0572 ns | 0.0507 ns | 8 | // | Struct | 3.872 ns | 0.0790 ns | 0.0739 ns | 9 | 10 | public interface IThing 11 | { 12 | int Number { get; } 13 | } 14 | 15 | public class ClassThing : IThing 16 | { 17 | public int Number => 1; 18 | } 19 | 20 | public struct StructThing : IThing 21 | { 22 | public int Number => 1; 23 | } 24 | 25 | public class ClassCastOrStructCast 26 | { 27 | [Benchmark] 28 | public IThing Class() 29 | { 30 | return new ClassThing(); 31 | } 32 | 33 | [Benchmark] 34 | public IThing Struct() 35 | { 36 | return new StructThing(); 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /src/StringDB.PerformanceNumbers/InsertRangeFileSize.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | 6 | namespace StringDB.PerformanceNumbers 7 | { 8 | public class InsertRangeFileSize 9 | { 10 | public void Run() 11 | { 12 | void Insert(IDatabase db, int c) 13 | { 14 | var kvp = GenerateKeyValuePair(128, 1024); 15 | 16 | db.InsertRange(Enumerable.Repeat(kvp, c).ToArray()); 17 | } 18 | 19 | var size1 = GetSizeAfter(1, Insert); 20 | var size2 = GetSizeAfter(50, Insert); 21 | var size3 = GetSizeAfter(100, Insert); 22 | 23 | Console.WriteLine($"Size after 1 elements in an insert range: {size1}"); 24 | Console.WriteLine($"Size after 50 elements in an insert range: {size2}"); 25 | Console.WriteLine($"Size after 100 elements in an insert range: {size3}"); 26 | } 27 | 28 | public static long GetSizeAfter(int kvps, Action, int> action) 29 | { 30 | using (var ms = new MemoryStream()) 31 | using (var db = StringDatabase.Create(ms)) 32 | { 33 | action(db, kvps); 34 | 35 | return ms.Length; 36 | } 37 | } 38 | 39 | public static KeyValuePair GenerateKeyValuePair(int keySize, int valueSize) 40 | { 41 | var key = new string('X', keySize); 42 | var value = new string('X', valueSize); 43 | 44 | return new KeyValuePair(key, value); 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /src/StringDB.PerformanceNumbers/Program.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Running; 2 | using System; 3 | using System.Diagnostics; 4 | using System.Threading.Tasks; 5 | 6 | namespace StringDB.PerformanceNumbers 7 | { 8 | public static class Program 9 | { 10 | public enum BenchmarkToRun 11 | { 12 | YieldOrLinq, 13 | ClassCastOrStructCast 14 | } 15 | 16 | private static async Task Main() 17 | { 18 | new SingleInsertFileSize().Run(); 19 | new InsertRangeFileSize().Run(); 20 | 21 | const BenchmarkToRun benchmark = BenchmarkToRun.ClassCastOrStructCast; 22 | 23 | var summary = BenchmarkRunner.Run(GetBenchmarkType(benchmark)); 24 | 25 | Console.WriteLine(summary); 26 | 27 | Console.ReadLine(); 28 | } 29 | 30 | private static Type GetBenchmarkType(BenchmarkToRun benchmark) 31 | { 32 | switch (benchmark) 33 | { 34 | case BenchmarkToRun.YieldOrLinq: return typeof(YieldOrLinq); 35 | case BenchmarkToRun.ClassCastOrStructCast: return typeof(ClassCastOrStructCast); 36 | default: throw new Exception(""); 37 | } 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /src/StringDB.PerformanceNumbers/SingleInsertFileSize.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace StringDB.PerformanceNumbers 5 | { 6 | public class SingleInsertFileSize 7 | { 8 | public void Run() 9 | { 10 | void Insert(IDatabase db) => DoWrite(db, 128, 1024); 11 | 12 | var size1 = GetSizeAfter(1, Insert); 13 | var size2 = GetSizeAfter(50, Insert); 14 | var size3 = GetSizeAfter(100, Insert); 15 | 16 | Console.WriteLine($"Size after 1 single insert: {size1}"); 17 | Console.WriteLine($"Size after 50 single inserts: {size2}"); 18 | Console.WriteLine($"Size after 100 single inserts: {size3}"); 19 | } 20 | 21 | public static long GetSizeAfter(int times, Action> action) 22 | { 23 | using (var ms = new MemoryStream()) 24 | using (var db = StringDatabase.Create(ms)) 25 | { 26 | for (var i = 0; i < times; i++) 27 | { 28 | action(db); 29 | } 30 | 31 | return ms.Length; 32 | } 33 | } 34 | 35 | public static void DoWrite(IDatabase db, int keySize, int valueSize) 36 | { 37 | var key = new string('X', keySize); 38 | var value = new string('X', valueSize); 39 | 40 | db.Insert(key, value); 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /src/StringDB.PerformanceNumbers/StringDB.PerformanceNumbers.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp2.2 6 | latest 7 | true 8 | true 9 | snupkg 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/StringDB.PerformanceNumbers/YieldOrLinq.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Attributes; 2 | 3 | using System; 4 | using System.Collections; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | 8 | namespace StringDB.PerformanceNumbers 9 | { 10 | /* 11 | * | Method | Mean | Error | StdDev | Ratio | RatioSD | 12 | * |------------------ |----------:|----------:|----------:|------:|--------:| 13 | * | BenchmarkLinq | 213.34 ns | 2.0946 ns | 1.9593 ns | 2.35 | 0.03 | 14 | * | BenchmarkYield | 90.93 ns | 0.8003 ns | 0.7094 ns | 1.00 | 0.00 | 15 | * | CustomIEnumerator | 77.81 ns | 1.1261 ns | 1.0534 ns | 0.86 | 0.01 | 16 | */ 17 | 18 | public class YieldOrLinq 19 | { 20 | public void PrintResults() 21 | { 22 | Console.WriteLine("linq:"); 23 | foreach (var item in UseLinq()) 24 | { 25 | Console.WriteLine(item); 26 | } 27 | 28 | Console.WriteLine("yield"); 29 | foreach (var item in UseYield()) 30 | { 31 | Console.WriteLine(item); 32 | } 33 | 34 | Console.WriteLine("custom"); 35 | foreach (var item in new CustomEnumerator(Evaluate())) 36 | { 37 | Console.WriteLine(item); 38 | } 39 | } 40 | 41 | [Benchmark] 42 | public void BenchmarkLinq() 43 | { 44 | foreach (var item in UseLinq()) 45 | { 46 | } 47 | } 48 | 49 | [Benchmark(Baseline = true)] 50 | public void BenchmarkYield() 51 | { 52 | foreach (var item in UseYield()) 53 | { 54 | } 55 | } 56 | 57 | [Benchmark] 58 | public void CustomIEnumerator() 59 | { 60 | foreach (var item in new CustomEnumerator(Evaluate())) 61 | { 62 | } 63 | } 64 | 65 | public IEnumerable UseYield() 66 | { 67 | foreach (var item in Evaluate()) 68 | { 69 | if (item.Item2 == 2) 70 | { 71 | yield return item.Item1; 72 | } 73 | } 74 | } 75 | 76 | public IEnumerable UseLinq() 77 | => Evaluate() 78 | .Where(x => x.Item2 == 2) 79 | .Select(x => x.Item1); 80 | 81 | public IEnumerable<(int, int)> Evaluate() 82 | { 83 | yield return (0, 1); 84 | yield return (1, 2); 85 | yield return (2, 3); 86 | yield return (3, 2); 87 | yield return (4, 1); 88 | } 89 | } 90 | 91 | public class CustomEnumerator : IEnumerator, IEnumerable 92 | { 93 | private readonly IEnumerator<(int, int)> _enumerator; 94 | 95 | public CustomEnumerator(IEnumerable<(int, int)> enumerateOver) 96 | => _enumerator = enumerateOver.GetEnumerator(); 97 | 98 | public bool MoveNext() 99 | { 100 | while (_enumerator.MoveNext()) 101 | { 102 | var (key, value) = _enumerator.Current; 103 | 104 | if (value != 2) 105 | { 106 | continue; 107 | } 108 | 109 | Current = key; 110 | return true; 111 | } 112 | 113 | return false; 114 | } 115 | 116 | public void Reset() => _enumerator.Reset(); 117 | 118 | public int Current { get; private set; } 119 | 120 | object IEnumerator.Current => Current; 121 | 122 | public void Dispose() => _enumerator.Dispose(); 123 | 124 | public IEnumerator GetEnumerator() => this; 125 | 126 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); 127 | } 128 | } -------------------------------------------------------------------------------- /src/StringDB/DatabaseExtensions.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | using StringDB.IO; 4 | 5 | using System.Collections.Generic; 6 | using System.Runtime.CompilerServices; 7 | 8 | namespace StringDB 9 | { 10 | /// 11 | /// Handy extensions for a database. 12 | /// 13 | [PublicAPI] 14 | public static class DatabaseExtensions 15 | { 16 | /// 17 | /// Returns every key of the database. 18 | /// 19 | /// The type of key. 20 | /// The type of value. 21 | /// The database to fetch all the keys from. 22 | /// A of keys. 23 | [NotNull] 24 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 25 | public static IEnumerable Keys 26 | ( 27 | [NotNull] this IDatabase db 28 | ) 29 | { 30 | foreach (var entry in db) 31 | { 32 | yield return entry.Key; 33 | } 34 | } 35 | 36 | /// 37 | /// Returns every value of the database. 38 | /// 39 | /// The type of key. 40 | /// The type of value. 41 | /// The database to fetch all the values from. 42 | /// A of values. 43 | [NotNull] 44 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 45 | public static IEnumerable> Values 46 | ( 47 | [NotNull] this IDatabase db 48 | ) 49 | { 50 | foreach (var entry in db) 51 | { 52 | yield return entry.Value; 53 | } 54 | } 55 | 56 | /// 57 | /// Loads every value, and returns the loaded value of the database. 58 | /// 59 | /// The type of key. 60 | /// The type of value. 61 | /// The database to fetch all the values from. 62 | /// A of loaded values. 63 | [NotNull] 64 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 65 | public static IEnumerable ValuesAggressive 66 | ( 67 | [NotNull] this IDatabase db 68 | ) 69 | { 70 | foreach (var entry in db) 71 | { 72 | yield return entry.Value.Load(); 73 | } 74 | } 75 | 76 | /// 77 | /// Enumerates over the database and loads values as soon as it's optimal to do so. 78 | /// 79 | /// The type of key. 80 | /// The type of value. 81 | /// The database to fetch all the values from. 82 | /// The token to use to determine when it is optimal to read values. 83 | /// An of s with the data. 84 | [NotNull] 85 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 86 | public static IEnumerable> EnumerateOptimally 87 | ( 88 | [NotNull] this IDatabase db, 89 | IOptimalToken optimalToken 90 | ) 91 | { 92 | var lazyList = new List>>(); 93 | 94 | foreach (var entry in db) 95 | { 96 | lazyList.Add(entry); 97 | 98 | if (!optimalToken.OptimalReadingTime) 99 | { 100 | continue; 101 | } 102 | 103 | foreach (var lazyEntry in lazyList) 104 | { 105 | yield return new KeyValuePair(lazyEntry.Key, lazyEntry.Value.Load()); 106 | } 107 | 108 | lazyList.Clear(); 109 | } 110 | 111 | if (lazyList.Count == 0) 112 | { 113 | yield break; 114 | } 115 | 116 | // i really hate how this is duplicated, but we can't use local functions 117 | // since those are delegates and that'd be a bit of a performance hit 118 | // so copying and pasting is the ugliest, but fastest solution. 119 | // i'm very open to PRs for this :p 120 | foreach (var lazyEntry in lazyList) 121 | { 122 | yield return new KeyValuePair(lazyEntry.Key, lazyEntry.Value.Load()); 123 | } 124 | } 125 | 126 | /// 127 | /// Enumerates over the database and loads values at a time. 128 | /// 129 | /// The type of key. 130 | /// The type of value. 131 | /// The database to fetch all the values from. 132 | /// The amount of values to load at a time. 133 | /// An of s with the data. 134 | [NotNull] 135 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 136 | public static IEnumerable> EnumerateAggressively 137 | ( 138 | [NotNull] this IDatabase db, 139 | int valueLoadAmount 140 | ) 141 | { 142 | var lazyList = new List>>(valueLoadAmount); 143 | var loadedList = new List>(valueLoadAmount); 144 | 145 | using (var enumerator = db.GetEnumerator()) 146 | { 147 | int result; 148 | 149 | do 150 | { 151 | result = Pool(valueLoadAmount, enumerator, ref lazyList); 152 | 153 | foreach (var item in lazyList) 154 | { 155 | loadedList.Add(new KeyValuePair(item.Key, item.Value.Load())); 156 | } 157 | 158 | foreach (var item in loadedList) 159 | { 160 | yield return item; 161 | } 162 | 163 | loadedList.Clear(); 164 | lazyList.Clear(); 165 | } 166 | while (result == valueLoadAmount); 167 | } 168 | } 169 | 170 | private static int Pool 171 | ( 172 | int amount, 173 | [NotNull] IEnumerator>> enumerator, 174 | [NotNull] ref List>> lazyList 175 | ) 176 | { 177 | var fillAmount = 0; 178 | 179 | for (; fillAmount < amount && enumerator.MoveNext(); fillAmount++) 180 | { 181 | lazyList.Add(enumerator.Current); 182 | } 183 | 184 | return fillAmount; 185 | } 186 | } 187 | } -------------------------------------------------------------------------------- /src/StringDB/Databases/BaseDatabase.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | using System.Collections; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | 7 | namespace StringDB.Databases 8 | { 9 | /// 10 | /// 11 | /// An implementor of IDatabase that requires the inheriting class 12 | /// to only implement two functions. 13 | /// 14 | /// The type of key of the database. 15 | /// The type of value of the database. 16 | [PublicAPI] 17 | public abstract class BaseDatabase : IDatabase 18 | { 19 | private readonly IEqualityComparer _keyComparer; 20 | 21 | protected BaseDatabase() 22 | : this(EqualityComparer.Default) 23 | { 24 | } 25 | 26 | protected BaseDatabase([NotNull] IEqualityComparer keyComparer) 27 | => _keyComparer = keyComparer; 28 | 29 | /// 30 | public abstract void InsertRange(params KeyValuePair[] items); 31 | 32 | /// 33 | /// Enumerates over all the items in the database. 34 | /// 35 | /// An IEnumerable of KeyValuePairs of keys and their lazy-loading values. 36 | [NotNull] 37 | protected abstract IEnumerable>> Evaluate(); 38 | 39 | /// 40 | /// When the key is unable to be found. 41 | public virtual TValue Get(TKey key) 42 | { 43 | var success = TryGet(key, out var value); 44 | 45 | if (success && !Equals(value, null)) 46 | { 47 | return value; 48 | } 49 | 50 | throw new KeyNotFoundException($"Unable to find {key} in the database."); 51 | } 52 | 53 | public virtual bool TryGet(TKey key, out TValue value) 54 | { 55 | foreach (var result in GetAll(key)) 56 | { 57 | value = result.Load(); 58 | return true; 59 | } 60 | 61 | value = default; 62 | return false; 63 | } 64 | 65 | public virtual void Insert(TKey key, TValue value) 66 | => InsertRange(new KeyValuePair(key, value)); 67 | 68 | public virtual IEnumerable> GetAll(TKey key) 69 | => Evaluate() 70 | .Where(item => _keyComparer.Equals(key, item.Key)) 71 | .Select(item => item.Value); 72 | 73 | public virtual IEnumerator>> GetEnumerator() => Evaluate().GetEnumerator(); 74 | 75 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); 76 | 77 | /// 78 | /// Cleans up any resources the database is using. 79 | /// 80 | public abstract void Dispose(); 81 | } 82 | } -------------------------------------------------------------------------------- /src/StringDB/Databases/BufferedDatabase.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | using StringDB.LazyLoaders; 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Runtime.CompilerServices; 8 | 9 | namespace StringDB.Databases 10 | { 11 | /// 12 | /// Buffers writes to a database, 13 | /// coagulating multiple inserts until the buffer is full. 14 | /// 15 | [PublicAPI] 16 | public sealed class BufferedDatabase 17 | : BaseDatabase, IDatabaseLayer 18 | { 19 | public const int MinimumBufferSize = 16; 20 | 21 | /// 22 | /// Creates a new 23 | /// with the specified buffer. 24 | /// 25 | /// The database to buffer. 26 | /// The size of the buffer. 27 | /// If the underlying database should be disposed on dispose. 28 | public BufferedDatabase 29 | ( 30 | [NotNull] IDatabase database, 31 | int bufferSize = 0x1000, 32 | bool disposeDatabase = true 33 | ) 34 | { 35 | if (bufferSize < MinimumBufferSize) 36 | { 37 | throw new ArgumentException(nameof(bufferSize), $"A buffer smaller than {MinimumBufferSize} is not allowed."); 38 | } 39 | 40 | InnerDatabase = database; 41 | _disposeDatabase = disposeDatabase; 42 | _buffer = new KeyValuePair[bufferSize]; 43 | _bufferPos = 0; 44 | } 45 | 46 | [NotNull] private readonly KeyValuePair[] _buffer; 47 | private readonly bool _disposeDatabase; 48 | private int _bufferPos = 0; 49 | 50 | public IDatabase InnerDatabase { get; } 51 | 52 | /// 53 | /// Flushes the internal buffer. 54 | /// 55 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 56 | public void Flush() => WriteBuffer(); 57 | 58 | /// 59 | /// Fills the internal buffer. 60 | /// 61 | /// 62 | /// As more of the items in the array are used, 63 | /// True if the buffer is filled, false if it is not. 64 | private bool FillBuffer([NotNull] KeyValuePair[] fillAmt, ref int used) 65 | { 66 | var needFill = fillAmt.Length - used; 67 | 68 | if (needFill == 0) 69 | { 70 | return false; 71 | } 72 | 73 | // the amount we can fill, 74 | // it's either the amount of space remaning in the buffer, 75 | // or the array length 76 | var amountCanFill = Math.Min(_buffer.Length - _bufferPos, needFill); 77 | 78 | // if we can't fill anything, say that the buffer's full 79 | if (amountCanFill <= 0) 80 | { 81 | return true; 82 | } 83 | 84 | // an overflow will occur 85 | var willFill = _bufferPos + needFill >= _buffer.Length; 86 | 87 | // copy from the src to the buffer 88 | Array.Copy(fillAmt, used, _buffer, _bufferPos, amountCanFill); 89 | 90 | // increment respective variables 91 | used += amountCanFill; 92 | _bufferPos += amountCanFill; 93 | 94 | return willFill; 95 | } 96 | 97 | private bool FillBufferSingle(KeyValuePair entry) 98 | { 99 | if (_bufferPos == _buffer.Length) 100 | { 101 | // no room to insert anything 102 | return true; 103 | } 104 | 105 | _buffer[_bufferPos++] = entry; 106 | 107 | return false; 108 | } 109 | 110 | /// 111 | /// Writes the entire buffer to the db 112 | /// 113 | private void WriteBuffer() 114 | { 115 | // if our buffer is full 116 | if (_bufferPos == _buffer.Length) 117 | { 118 | // write the entire buffer 119 | InnerDatabase.InsertRange(_buffer); 120 | } 121 | else if (_bufferPos == 0) 122 | { 123 | return; 124 | } 125 | else 126 | { 127 | // otherwise, make a temporary array to copy the buffer to 128 | var array = new KeyValuePair[_bufferPos]; 129 | 130 | Array.Copy(_buffer, array, _bufferPos); 131 | 132 | InnerDatabase.InsertRange(array); 133 | } 134 | 135 | // since we've written the buffer, clean it out 136 | // this will remove all references to used objects to let GC do it's thing 137 | for (int i = 0; i < _buffer.Length; i++) 138 | { 139 | _buffer[i] = default; 140 | } 141 | 142 | _bufferPos = 0; 143 | } 144 | 145 | /// 146 | public override void Dispose() 147 | { 148 | WriteBuffer(); 149 | 150 | if (_disposeDatabase) 151 | { 152 | InnerDatabase.Dispose(); 153 | } 154 | } 155 | 156 | /// 157 | public override void Insert(TKey key, TValue value) 158 | { 159 | var pair = new KeyValuePair(key, value); 160 | 161 | // this will be true if it fails to fill 162 | if (FillBufferSingle(pair)) 163 | { 164 | // that means we need to flush the buffer, 165 | WriteBuffer(); 166 | 167 | // and re-add it to the buffer 168 | FillBufferSingle(pair); 169 | } 170 | } 171 | 172 | /// 173 | public override void InsertRange(params KeyValuePair[] items) 174 | { 175 | int used = 0; 176 | 177 | while (FillBuffer(items, ref used)) 178 | { 179 | WriteBuffer(); 180 | } 181 | } 182 | 183 | /// 184 | protected override IEnumerable>> Evaluate() 185 | { 186 | // return the original database 187 | foreach (var kvp in InnerDatabase) 188 | { 189 | yield return kvp; 190 | } 191 | 192 | // and virtually append the buffer 193 | for (var i = 0; i < _bufferPos; i++) 194 | { 195 | var kvp = _buffer[i]; 196 | yield return new ValueLoader(kvp.Value) 197 | .ToKeyValuePair(kvp.Key); 198 | } 199 | } 200 | } 201 | } -------------------------------------------------------------------------------- /src/StringDB/Databases/CacheDatabase.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | using StringDB.LazyLoaders; 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | 9 | namespace StringDB.Databases 10 | { 11 | /// 12 | /// 13 | /// Non-thread-safely caches results and loaded values from a database. 14 | /// This could be used in scenarios where you're repeatedly iterating 15 | /// over an IODatabase. 16 | /// 17 | /// The type of key. 18 | /// The type of value. 19 | [PublicAPI] 20 | public sealed class CacheDatabase 21 | : BaseDatabase, IDatabaseLayer 22 | { 23 | [NotNull] private readonly List>> _cache; 24 | private readonly bool _disposeDatabase; 25 | 26 | /// 27 | [NotNull] public IDatabase InnerDatabase { get; } 28 | 29 | /// 30 | /// Create a new . 31 | /// 32 | /// The database to cache. 33 | public CacheDatabase 34 | ( 35 | [NotNull] IDatabase database, 36 | bool disposeDatabase = true 37 | ) 38 | : this(database, EqualityComparer.Default, disposeDatabase) 39 | { 40 | } 41 | 42 | /// 43 | /// Create a new . 44 | /// 45 | /// The database to cache. 46 | /// The equality comparer for the key. 47 | public CacheDatabase 48 | ( 49 | [NotNull] IDatabase database, 50 | [NotNull] IEqualityComparer comparer, 51 | bool disposeDatabase = true 52 | ) 53 | : base(comparer) 54 | { 55 | _cache = new List>>(); 56 | InnerDatabase = database; 57 | _disposeDatabase = disposeDatabase; 58 | } 59 | 60 | /// 61 | public override void InsertRange(params KeyValuePair[] items) 62 | 63 | // we can't add it to our cache otherwise it might change the order of things 64 | => InnerDatabase.InsertRange(items); 65 | 66 | /// 67 | protected override IEnumerable>> Evaluate() 68 | { 69 | // first enumerate to however much we have in memory 70 | for (var i = 0; i < _cache.Count; i++) 71 | { 72 | var current = _cache[i]; 73 | yield return current.Value.ToKeyValuePair(current.Key); 74 | } 75 | 76 | // start reading and adding more as we go along 77 | foreach (var item in InnerDatabase.Skip(_cache.Count)) 78 | { 79 | var currentCache = new KeyValuePair> 80 | ( 81 | item.Key, 82 | new CachedLoader(item.Value) 83 | ); 84 | 85 | _cache.Add(currentCache); 86 | 87 | var current = currentCache; 88 | 89 | yield return current.Value.ToKeyValuePair(current.Key); 90 | } 91 | } 92 | 93 | /// 94 | public override void Dispose() 95 | { 96 | foreach (var item in _cache) 97 | { 98 | if (item.Key is IDisposable disposable) 99 | { 100 | disposable.Dispose(); 101 | } 102 | 103 | item.Value.Dispose(); 104 | } 105 | 106 | _cache.Clear(); 107 | 108 | if (_disposeDatabase) 109 | { 110 | InnerDatabase.Dispose(); 111 | } 112 | } 113 | } 114 | } -------------------------------------------------------------------------------- /src/StringDB/Databases/IODatabase.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | using StringDB.IO; 4 | using StringDB.LazyLoaders; 5 | 6 | using System.Collections.Generic; 7 | 8 | namespace StringDB.Databases 9 | { 10 | /// 11 | /// 12 | /// A database for IO-based operations, using an IDatabaseIODevice. 13 | /// 14 | [PublicAPI] 15 | public sealed class IODatabase : BaseDatabase 16 | { 17 | /// 18 | /// The DatabaseIODevice in use. 19 | /// 20 | public IDatabaseIODevice DatabaseIODevice { get; } 21 | 22 | /// 23 | /// Create an IODatabase with the IDatabaseIODevice specified. 24 | /// 25 | /// The DatabaseIODevice to use. 26 | public IODatabase([NotNull] IDatabaseIODevice dbIODevice) 27 | : this(dbIODevice, EqualityComparer.Default) 28 | { 29 | } 30 | 31 | /// 32 | /// Create an IODatabase with the IDatabaseIODevice specified. 33 | /// 34 | /// The DatabaseIODevice to use under the hood. 35 | /// The equality comparer to use for the key. 36 | public IODatabase([NotNull] IDatabaseIODevice dbIODevice, [NotNull] IEqualityComparer comparer) 37 | : base(keyComparer: comparer) 38 | => DatabaseIODevice = dbIODevice; 39 | 40 | /// 41 | public override void InsertRange(params KeyValuePair[] items) => DatabaseIODevice.Insert(items); 42 | 43 | /// 44 | protected override IEnumerable>> Evaluate() 45 | { 46 | DatabaseIODevice.Reset(); 47 | 48 | DatabaseItem dbItem; 49 | 50 | while (!(dbItem = DatabaseIODevice.ReadNext()).EndOfItems) 51 | { 52 | yield return new IOLoader(DatabaseIODevice, dbItem.DataPosition) 53 | .ToKeyValuePair(dbItem.Key); 54 | } 55 | } 56 | 57 | /// 58 | public override void Dispose() => DatabaseIODevice.Dispose(); 59 | } 60 | } -------------------------------------------------------------------------------- /src/StringDB/Databases/MemoryDatabase.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | using StringDB.LazyLoaders; 4 | 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | 8 | namespace StringDB.Databases 9 | { 10 | /// 11 | /// 12 | /// A database entire in memory. 13 | /// 14 | /// The type of key of the database. 15 | /// The type of value of the database. 16 | [PublicAPI] 17 | public sealed class MemoryDatabase : BaseDatabase 18 | { 19 | private readonly List> _data; 20 | 21 | /// 22 | /// Create a new . 23 | /// 24 | /// The data to pre-fill it with. 25 | public MemoryDatabase([CanBeNull] List> data = null) 26 | : this(data, EqualityComparer.Default) 27 | { 28 | } 29 | 30 | /// 31 | /// Create a new . 32 | /// 33 | /// The data to pre-fill it with. 34 | /// The equality comparer to use. 35 | public MemoryDatabase([CanBeNull] List> data, [NotNull] IEqualityComparer comparer) 36 | : base(comparer) 37 | => _data = data ?? new List>(); 38 | 39 | /// 40 | public override void InsertRange(params KeyValuePair[] items) 41 | => _data.AddRange(items); 42 | 43 | /// 44 | protected override IEnumerable>> Evaluate() 45 | => _data 46 | .Select 47 | ( 48 | item => new ValueLoader(item.Value) 49 | .ToKeyValuePair(item.Key) 50 | ); 51 | 52 | /// 53 | public override void Dispose() => _data.Clear(); 54 | } 55 | } -------------------------------------------------------------------------------- /src/StringDB/Databases/ReadOnlyDatabase.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | using System; 4 | using System.Collections; 5 | using System.Collections.Generic; 6 | 7 | namespace StringDB.Databases 8 | { 9 | /// 10 | /// A database which can only be read from. 11 | /// 12 | [PublicAPI] 13 | public sealed class ReadOnlyDatabase 14 | : IDatabase, IDatabaseLayer 15 | { 16 | private readonly bool _disposeDatabase; 17 | 18 | /// 19 | /// Creates a new . 20 | /// 21 | /// The database to make read only. 22 | /// If the underlying database should be disposed on dispose. 23 | public ReadOnlyDatabase([NotNull] IDatabase database, bool disposeDatabase = true) 24 | { 25 | _disposeDatabase = disposeDatabase; 26 | InnerDatabase = database; 27 | } 28 | 29 | /// 30 | [NotNull] public IDatabase InnerDatabase { get; } 31 | 32 | /// 33 | public void Dispose() 34 | { 35 | if (_disposeDatabase) 36 | { 37 | InnerDatabase.Dispose(); 38 | } 39 | } 40 | 41 | private const string Error = "Writing is not supported."; 42 | 43 | /// 44 | public TValue Get([NotNull] TKey key) => InnerDatabase.Get(key); 45 | 46 | /// 47 | public bool TryGet([NotNull] TKey key, [CanBeNull] out TValue value) => InnerDatabase.TryGet(key, out value); 48 | 49 | /// 50 | public IEnumerable> GetAll([NotNull] TKey key) => InnerDatabase.GetAll(key); 51 | 52 | /// 53 | public IEnumerator>> GetEnumerator() => InnerDatabase.GetEnumerator(); 54 | 55 | /// 56 | public void Insert([NotNull] TKey key, [NotNull] TValue value) => throw new NotSupportedException(Error); 57 | 58 | /// 59 | public void InsertRange([NotNull] params KeyValuePair[] items) => throw new NotSupportedException(Error); 60 | 61 | /// 62 | IEnumerator IEnumerable.GetEnumerator() => InnerDatabase.GetEnumerator(); 63 | } 64 | } -------------------------------------------------------------------------------- /src/StringDB/Databases/ThreadLockDatabase.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | using StringDB.LazyLoaders; 4 | 5 | using System.Collections; 6 | using System.Collections.Generic; 7 | 8 | namespace StringDB.Databases 9 | { 10 | /// 11 | /// 12 | /// Intelligently uses locks to force and ensure that 13 | /// two threads never access the database at once. 14 | /// 15 | /// The type of key. 16 | /// The type of value. 17 | [PublicAPI] 18 | public sealed class ThreadLockDatabase 19 | : BaseDatabase, IDatabaseLayer 20 | { 21 | private sealed class ThinDatabaseIEnumeratorWrapper : IEnumerable>>, IEnumerator>> 22 | { 23 | private readonly object _lock; 24 | private readonly IEnumerator>> _enumerator; 25 | private KeyValuePair> _current; 26 | 27 | public ThinDatabaseIEnumeratorWrapper(object @lock, IDatabase database) 28 | { 29 | _lock = @lock; 30 | 31 | lock (_lock) 32 | { 33 | _enumerator = database.GetEnumerator(); 34 | } 35 | } 36 | 37 | public bool MoveNext() 38 | { 39 | lock (_lock) 40 | { 41 | var result = _enumerator.MoveNext(); 42 | 43 | if (!result) return false; 44 | 45 | var current = _enumerator.Current; 46 | 47 | _current = new ThreadLockLoader(current.Value, _lock) 48 | .ToKeyValuePair(current.Key); 49 | 50 | return true; 51 | } 52 | } 53 | 54 | public void Reset() 55 | { 56 | lock (_lock) 57 | { 58 | _enumerator.Reset(); 59 | } 60 | } 61 | 62 | public KeyValuePair> Current => _current; 63 | 64 | object IEnumerator.Current => Current; 65 | 66 | public void Dispose() 67 | { 68 | lock (_lock) 69 | { 70 | _enumerator.Dispose(); 71 | } 72 | } 73 | 74 | public IEnumerator>> GetEnumerator() => this; 75 | 76 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); 77 | } 78 | 79 | [NotNull] private readonly object _lock = new object(); 80 | private readonly bool _disposeDatabase; 81 | 82 | /// 83 | [NotNull] public IDatabase InnerDatabase { get; } 84 | 85 | /// 86 | /// Creates a new around a database. 87 | /// 88 | /// The database to intelligently lock on. 89 | public ThreadLockDatabase([NotNull] IDatabase database, bool disposeDatabase = true) 90 | : this(database, EqualityComparer.Default, disposeDatabase) 91 | { 92 | } 93 | 94 | /// 95 | /// Creates a new around a database. 96 | /// 97 | /// The database to intelligently lock on. 98 | /// The equality comparer to use for keys. 99 | public ThreadLockDatabase 100 | ( 101 | [NotNull] IDatabase database, 102 | [NotNull] IEqualityComparer comparer, 103 | bool disposeDatabase = true 104 | ) 105 | : base(comparer) 106 | { 107 | InnerDatabase = database; 108 | _disposeDatabase = disposeDatabase; 109 | } 110 | 111 | /// 112 | public override void InsertRange(params KeyValuePair[] items) 113 | { 114 | lock (_lock) 115 | { 116 | InnerDatabase.InsertRange(items); 117 | } 118 | } 119 | 120 | /// 121 | protected override IEnumerable>> Evaluate() 122 | => new ThinDatabaseIEnumeratorWrapper(_lock, InnerDatabase); 123 | 124 | /// 125 | public override void Dispose() 126 | { 127 | if (_disposeDatabase) 128 | { 129 | lock (_lock) 130 | { 131 | InnerDatabase.Dispose(); 132 | } 133 | } 134 | } 135 | } 136 | } -------------------------------------------------------------------------------- /src/StringDB/Databases/TransformDatabase.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | using StringDB.LazyLoaders; 4 | 5 | using System.Collections.Generic; 6 | 7 | namespace StringDB.Databases 8 | { 9 | /// 10 | /// 11 | /// A database that uses s to transform 12 | /// keys and values to/from the underlying database. 13 | /// 14 | /// The key type before transformation. 15 | /// The value type before transformation. 16 | /// The key type after transformation. 17 | /// The value type after transformation 18 | [PublicAPI] 19 | public sealed class TransformDatabase 20 | : BaseDatabase, IDatabaseLayer 21 | { 22 | private readonly ITransformer _keyTransformer; 23 | private readonly ITransformer _valueTransformer; 24 | private readonly bool _disposeDatabase; 25 | 26 | /// 27 | public IDatabase InnerDatabase { get; } 28 | 29 | /// 30 | /// Create a new transform database. 31 | /// 32 | /// The underlying database to convert. 33 | /// The transformer for the key. 34 | /// The transformer for the value. 35 | public TransformDatabase 36 | ( 37 | [NotNull] IDatabase db, 38 | [NotNull] ITransformer keyTransformer, 39 | [NotNull] ITransformer valueTransformer, 40 | bool disposeDatabase = true 41 | ) 42 | : this(db, keyTransformer, valueTransformer, EqualityComparer.Default, disposeDatabase) 43 | { 44 | } 45 | 46 | /// 47 | /// Create a new transform database. 48 | /// 49 | /// The underlying database to convert. 50 | /// The transformer for the key. 51 | /// The transformer for the value. 52 | /// The equality comparer to use for keys. 53 | public TransformDatabase 54 | ( 55 | [NotNull] IDatabase db, 56 | [NotNull] ITransformer keyTransformer, 57 | [NotNull] ITransformer valueTransformer, 58 | [NotNull] IEqualityComparer comparer, 59 | bool disposeDatabase = true 60 | ) 61 | : base(comparer) 62 | { 63 | InnerDatabase = db; 64 | _keyTransformer = keyTransformer; 65 | _valueTransformer = valueTransformer; 66 | _disposeDatabase = disposeDatabase; 67 | } 68 | 69 | /// 70 | public override void InsertRange(params KeyValuePair[] items) 71 | { 72 | var pre = new KeyValuePair[items.Length]; 73 | 74 | for (var i = 0; i < items.Length; i++) 75 | { 76 | var current = items[i]; 77 | 78 | pre[i] = new KeyValuePair 79 | ( 80 | key: _keyTransformer.TransformPost(current.Key), 81 | value: _valueTransformer.TransformPost(current.Value) 82 | ); 83 | } 84 | 85 | InnerDatabase.InsertRange(pre); 86 | } 87 | 88 | /// 89 | protected override IEnumerable>> Evaluate() 90 | { 91 | foreach (var element in InnerDatabase) 92 | { 93 | yield return new TransformLazyLoader(element.Value, _valueTransformer) 94 | .ToKeyValuePair(_keyTransformer.TransformPre(element.Key)); 95 | } 96 | } 97 | 98 | /// 99 | public override void Dispose() 100 | { 101 | if (_disposeDatabase) 102 | { 103 | InnerDatabase.Dispose(); 104 | } 105 | } 106 | } 107 | } -------------------------------------------------------------------------------- /src/StringDB/Databases/WriteOnlyDatabase.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | using System; 4 | using System.Collections; 5 | using System.Collections.Generic; 6 | 7 | namespace StringDB.Databases 8 | { 9 | /// 10 | /// A database which can only be read from. 11 | /// 12 | [PublicAPI] 13 | public sealed class WriteOnlyDatabase 14 | : IDatabase, IDatabaseLayer 15 | { 16 | private readonly bool _disposeDatabase; 17 | 18 | /// 19 | /// Creates a new . 20 | /// 21 | /// The database to make write only. 22 | /// If the underlying database should be disposed on dispose. 23 | public WriteOnlyDatabase([NotNull] IDatabase database, bool disposeDatabase = true) 24 | { 25 | _disposeDatabase = disposeDatabase; 26 | InnerDatabase = database; 27 | } 28 | 29 | /// 30 | [NotNull] public IDatabase InnerDatabase { get; } 31 | 32 | /// 33 | public void Dispose() 34 | { 35 | if (_disposeDatabase) 36 | { 37 | InnerDatabase.Dispose(); 38 | } 39 | } 40 | 41 | private const string Error = "Reading is not supported."; 42 | 43 | /// 44 | public TValue Get([NotNull] TKey key) => throw new NotSupportedException(Error); 45 | 46 | /// 47 | public bool TryGet([NotNull] TKey key, [CanBeNull] out TValue value) => throw new NotSupportedException(Error); 48 | 49 | /// 50 | public IEnumerable> GetAll([NotNull] TKey key) => throw new NotSupportedException(Error); 51 | 52 | /// 53 | public IEnumerator>> GetEnumerator() => throw new NotSupportedException(Error); 54 | 55 | /// 56 | public void Insert([NotNull] TKey key, [NotNull] TValue value) => InnerDatabase.Insert(key, value); 57 | 58 | /// 59 | public void InsertRange([NotNull] params KeyValuePair[] items) => InnerDatabase.InsertRange(items); 60 | 61 | /// 62 | IEnumerator IEnumerable.GetEnumerator() => throw new NotSupportedException(Error); 63 | } 64 | } -------------------------------------------------------------------------------- /src/StringDB/Fluency/BufferedDatabaseExtensions.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | using StringDB.Databases; 4 | 5 | using System.Runtime.CompilerServices; 6 | 7 | namespace StringDB.Fluency 8 | { 9 | /// 10 | /// Fluent extensions for a . 11 | /// 12 | [PublicAPI] 13 | public static class BufferedDatabaseExtensions 14 | { 15 | /// 16 | /// Apply a buffer to a database to ease the cost of multiple single inserts. 17 | /// 18 | /// The type of key. 19 | /// The type of value. 20 | /// The database to buffer. 21 | /// The size of the buffer. 22 | /// If the underlying database should be disposed on dispose. 23 | /// A buffered database. 24 | [NotNull] 25 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 26 | public static IDatabase WithBuffer 27 | ( 28 | [NotNull] this IDatabase database, 29 | int bufferSize = 0x1000, 30 | bool disposeDatabase = true 31 | ) 32 | => new BufferedDatabase(database, bufferSize, disposeDatabase); 33 | } 34 | } -------------------------------------------------------------------------------- /src/StringDB/Fluency/CacheDatabaseExtensions.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | using StringDB.Databases; 4 | 5 | using System.Runtime.CompilerServices; 6 | 7 | namespace StringDB.Fluency 8 | { 9 | /// 10 | /// Fluent extensions for a . 11 | /// 12 | [PublicAPI] 13 | public static class CacheDatabaseExtensions 14 | { 15 | /// 16 | /// Apply a non-thread-safe cache to a database. 17 | /// 18 | /// The type of key. 19 | /// The type of value. 20 | /// The database to cache results from. 21 | /// If the underlying database should be disposed on dispose. 22 | /// A database with caching. 23 | [NotNull] 24 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 25 | public static IDatabase WithCache 26 | ( 27 | [NotNull] this IDatabase database, 28 | bool disposeDatabase = true 29 | ) 30 | => new CacheDatabase(database, disposeDatabase); 31 | } 32 | } -------------------------------------------------------------------------------- /src/StringDB/Fluency/DatabaseBuilder.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | namespace StringDB.Fluency 4 | { 5 | /// 6 | /// A class that allows the extensive usage of extensions to create databases. 7 | /// 8 | [PublicAPI] 9 | public struct DatabaseBuilder 10 | { 11 | public static DatabaseBuilder Instance { get; } 12 | } 13 | } -------------------------------------------------------------------------------- /src/StringDB/Fluency/DatabaseIODeviceBuilder.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | namespace StringDB.Fluency 4 | { 5 | /// 6 | /// A class that allows the extensive usage of extensions to create DatabaseIODevices. 7 | /// 8 | [PublicAPI] 9 | public struct DatabaseIODeviceBuilder 10 | { 11 | public static DatabaseIODeviceBuilder Instance { get; } 12 | } 13 | } -------------------------------------------------------------------------------- /src/StringDB/Fluency/DatabaseIODeviceBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | using StringDB.IO; 4 | using StringDB.IO.Compatibility; 5 | 6 | using System; 7 | using System.IO; 8 | using System.Runtime.CompilerServices; 9 | 10 | namespace StringDB.Fluency 11 | { 12 | /// 13 | /// Fluent extensions for a 14 | /// 15 | [PublicAPI] 16 | public static class DatabaseIODeviceBuilderExtensions 17 | { 18 | /// 19 | /// Use a . 20 | /// 21 | /// The builder. 22 | /// The stream to use. 23 | /// If the stream should be left open after disposing of the . 24 | /// A . 25 | [NotNull] 26 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 27 | public static IDatabaseIODevice UseStoneVault 28 | ( 29 | [CanBeNull] this DatabaseIODeviceBuilder builder, 30 | [NotNull] Stream stream, 31 | bool leaveStreamOpen = false 32 | ) 33 | => new StoneVaultIODevice(stream, leaveStreamOpen); 34 | 35 | /// 36 | /// Use StringDB from a file. 37 | /// 38 | /// The builder. 39 | /// The version of StringDB to use. 40 | /// The file to open. 41 | /// An . 42 | [NotNull] 43 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 44 | public static IDatabaseIODevice UseStringDB 45 | ( 46 | [CanBeNull] this DatabaseIODeviceBuilder builder, 47 | StringDBVersion version, 48 | [NotNull] string file 49 | ) 50 | => builder.UseStringDB(version, file, NoByteBuffer.Read); 51 | 52 | /// 53 | /// Use StringDB from a file. 54 | /// 55 | /// The builder. 56 | /// The version of StringDB to use. 57 | /// The file to open. 58 | /// The kind of byte[] reading technique to use. 59 | /// An . 60 | [NotNull] 61 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 62 | public static IDatabaseIODevice UseStringDB 63 | ( 64 | [CanBeNull] this DatabaseIODeviceBuilder builder, 65 | StringDBVersion version, 66 | [NotNull] string file, 67 | [NotNull] Func buffer, 68 | bool leaveStreamOpen = false 69 | ) 70 | => builder 71 | .UseStringDB 72 | ( 73 | version, 74 | File.Open 75 | ( 76 | file, 77 | FileMode.OpenOrCreate, 78 | FileAccess.ReadWrite 79 | ), 80 | buffer, 81 | leaveStreamOpen: leaveStreamOpen 82 | ); 83 | 84 | /// 85 | /// Use a StringDB IDatabaseIODevice 86 | /// 87 | /// The builder. 88 | /// The version of StringDB to accomodate. 89 | /// The stream to use. 90 | /// If the stream should be left open after disposing of the . 91 | /// An . 92 | [NotNull] 93 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 94 | public static IDatabaseIODevice UseStringDB 95 | ( 96 | [CanBeNull] this DatabaseIODeviceBuilder builder, 97 | StringDBVersion version, 98 | [NotNull] Stream stream, 99 | bool leaveStreamOpen = false 100 | ) 101 | => builder.UseStringDB(version, stream, NoByteBuffer.Read, leaveStreamOpen); 102 | 103 | /// 104 | /// Use a StringDB IDatabaseIODevice 105 | /// 106 | /// The builder. 107 | /// The version of StringDB to accomodate. 108 | /// The stream to use. 109 | /// The kind of byte[] reading technique to use. 110 | /// If the stream should be left open after disposing of the . 111 | /// An . 112 | [NotNull] 113 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 114 | public static IDatabaseIODevice UseStringDB 115 | ( 116 | [CanBeNull] this DatabaseIODeviceBuilder builder, 117 | StringDBVersion version, 118 | [NotNull] Stream stream, 119 | [NotNull] Func buffer, 120 | bool leaveStreamOpen = false 121 | ) 122 | => new DatabaseIODevice 123 | ( 124 | version.UseVersion(stream, buffer, leaveStreamOpen) 125 | ); 126 | 127 | [NotNull] 128 | private static ILowlevelDatabaseIODevice UseVersion 129 | ( 130 | this StringDBVersion version, 131 | [NotNull] Stream stream, 132 | Func buffer, 133 | bool leaveStreamOpen = false 134 | ) 135 | { 136 | switch (version) 137 | { 138 | case StringDBVersion.v5_0_0: return new StringDB5_0_0LowlevelDatabaseIODevice(stream, buffer, leaveStreamOpen); 139 | case StringDBVersion.v10_0_0: return new StringDB10_0_0LowlevelDatabaseIODevice(stream, buffer, leaveStreamOpen); 140 | default: throw new NotSupportedException($"Didn't expect a {version}"); 141 | } 142 | } 143 | } 144 | } -------------------------------------------------------------------------------- /src/StringDB/Fluency/IODatabaseExtensions.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | using StringDB.Databases; 4 | using StringDB.IO; 5 | 6 | using System; 7 | using System.IO; 8 | using System.Runtime.CompilerServices; 9 | 10 | namespace StringDB.Fluency 11 | { 12 | /// 13 | /// Fluent extensions for an 14 | /// 15 | [PublicAPI] 16 | public static class IODatabaseExtensions 17 | { 18 | /// 19 | /// Create a new IODatabase with the specified . 20 | /// 21 | /// The builder. 22 | /// The to pass to the IODatabase. 23 | /// An IODatabase. 24 | [NotNull] 25 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 26 | public static IDatabase UseIODatabase 27 | ( 28 | [CanBeNull] this DatabaseBuilder builder, 29 | [NotNull] IDatabaseIODevice databaseIODevice 30 | ) 31 | => builder.UseIODatabase(databaseIODevice, out _); 32 | 33 | /// 34 | /// Create a new IODatabase with the specified , 35 | /// and has an out parameter to specify the optimal token source. 36 | /// 37 | /// The builder. 38 | /// The to pass to the IODatabase. 39 | /// The to use for optimal enumeration. 40 | /// An IODatabase. 41 | [NotNull] 42 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 43 | public static IDatabase UseIODatabase 44 | ( 45 | [CanBeNull] this DatabaseBuilder builder, 46 | [NotNull] IDatabaseIODevice databaseIODevice, 47 | [NotNull] out IOptimalTokenSource optimalTokenSource 48 | ) 49 | { 50 | var iodb = new IODatabase(databaseIODevice); 51 | optimalTokenSource = iodb.DatabaseIODevice.OptimalTokenSource; 52 | return iodb; 53 | } 54 | 55 | /// 56 | /// Create a new IODatabase and allows for fluent usage to create an IDatabaseIODevice. 57 | /// 58 | /// The builder. 59 | /// A delegate that allows for fluent building of an IDatabaseIODevice. 60 | /// An IODatabase. 61 | [NotNull] 62 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 63 | public static IDatabase UseIODatabase 64 | ( 65 | [CanBeNull] this DatabaseBuilder builder, 66 | [NotNull] Func databaseIODevice 67 | ) 68 | => builder.UseIODatabase(databaseIODevice, out _); 69 | 70 | /// 71 | /// Create a new IODatabase and allows for fluent usage to create an IDatabaseIODevice. 72 | /// 73 | /// The builder. 74 | /// A delegate that allows for fluent building of an IDatabaseIODevice. 75 | /// The to use for optimal enumeration. 76 | /// An IODatabase. 77 | [NotNull] 78 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 79 | public static IDatabase UseIODatabase 80 | ( 81 | [CanBeNull] this DatabaseBuilder builder, 82 | [NotNull] Func databaseIODevice, 83 | [NotNull] out IOptimalTokenSource optimalTokenSource 84 | ) 85 | => builder.UseIODatabase(databaseIODevice(new DatabaseIODeviceBuilder()), out optimalTokenSource); 86 | 87 | /// 88 | /// Creates a new IODatabase using StringDB. 89 | /// 90 | /// The builder. 91 | /// The version of StringDB to use. 92 | /// The file to read from. 93 | /// An . 94 | [NotNull] 95 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 96 | public static IDatabase UseIODatabase 97 | ( 98 | [CanBeNull] this DatabaseBuilder builder, 99 | StringDBVersion version, 100 | [NotNull] string file 101 | ) 102 | => builder.UseIODatabase(version, file, out _); 103 | 104 | /// 105 | /// Creates a new IODatabase using StringDB. 106 | /// 107 | /// The builder. 108 | /// The version of StringDB to use. 109 | /// The file to read from. 110 | /// An . 111 | [NotNull] 112 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 113 | public static IDatabase UseIODatabase 114 | ( 115 | [CanBeNull] this DatabaseBuilder builder, 116 | StringDBVersion version, 117 | [NotNull] string file, 118 | [NotNull] out IOptimalTokenSource optimalTokenSource 119 | ) 120 | => builder.UseIODatabase(databaseIODeviceBuilder => databaseIODeviceBuilder.UseStringDB(version, file), out optimalTokenSource); 121 | 122 | public static IDatabase UseIODatabase 123 | ( 124 | this DatabaseBuilder builder, 125 | IODatabaseOptions options, 126 | [NotNull] out IOptimalTokenSource optimalTokenSource 127 | ) 128 | => builder.UseIODatabase(ioDeviceBuilder => 129 | { 130 | var buffer = 131 | options.UseByteBuffer 132 | ? (Func)(new ByteBuffer().Read) 133 | : NoByteBuffer.Read; 134 | 135 | IDatabaseIODevice device = default; 136 | 137 | if (options.FileName == default) 138 | { 139 | device = ioDeviceBuilder.UseStringDB 140 | ( 141 | version: options.Version, 142 | stream: options.Stream, 143 | buffer: buffer, 144 | leaveStreamOpen: options.LeaveStreamOpen 145 | ); 146 | } 147 | else 148 | { 149 | device = ioDeviceBuilder.UseStringDB 150 | ( 151 | version: options.Version, 152 | file: options.FileName, 153 | buffer: buffer, 154 | leaveStreamOpen: options.LeaveStreamOpen 155 | ); 156 | } 157 | 158 | return device; 159 | }, out optimalTokenSource); 160 | } 161 | } -------------------------------------------------------------------------------- /src/StringDB/Fluency/IODatabaseOptions.cs: -------------------------------------------------------------------------------- 1 | using StringDB.IO; 2 | 3 | using System.IO; 4 | 5 | namespace StringDB.Fluency 6 | { 7 | public struct IODatabaseOptions 8 | { 9 | public IODatabaseOptions New() 10 | { 11 | return new IODatabaseOptions 12 | { 13 | Version = StringDBVersion.Latest 14 | }; 15 | } 16 | 17 | public StringDBVersion Version; 18 | 19 | /// 20 | /// Used if is null. 21 | /// 22 | public Stream Stream; 23 | 24 | /// 25 | /// is ignored if this is set to a non-null 26 | /// value. 27 | /// 28 | public string FileName; 29 | 30 | /// 31 | /// Setting this to true makes the reader internally use an 32 | /// instance of to prevent re-allocation of 33 | /// byte arrays when reading the index. This has a slight side effect 34 | /// of if the consumer stores the byte array read from the index for 35 | /// future use, it may be overwritten with the index of a different 36 | /// element with the same length. However, this can easily be ignored 37 | /// if applying a database transformation that consumes the byte array 38 | /// and doesn't store the result, eg. using StringDB.Ceras to transform 39 | /// keys and values to classes. Try to set this to true in scenarios 40 | /// where applicable. 41 | /// 42 | public bool UseByteBuffer; 43 | 44 | public bool LeaveStreamOpen; 45 | } 46 | } -------------------------------------------------------------------------------- /src/StringDB/Fluency/MemoryDatabaseExtensions.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | using StringDB.Databases; 4 | 5 | using System.Collections.Generic; 6 | using System.Runtime.CompilerServices; 7 | 8 | namespace StringDB.Fluency 9 | { 10 | /// 11 | /// Fluent extensions for a 12 | /// 13 | [PublicAPI] 14 | public static class MemoryDatabaseExtensions 15 | { 16 | /// 17 | /// Creates a blank 18 | /// 19 | /// The type of key to use. 20 | /// The type of value to use. 21 | /// The builder. 22 | /// A 23 | [NotNull] 24 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 25 | public static IDatabase UseMemoryDatabase 26 | ( 27 | [CanBeNull] this DatabaseBuilder builder 28 | ) 29 | => builder.UseMemoryDatabase(null); 30 | 31 | /// 32 | /// Creates a with the specified data. 33 | /// 34 | /// The type of key to use. 35 | /// The type of value to use. 36 | /// The builder. 37 | /// The data to prefill it with. 38 | /// A 39 | [NotNull] 40 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 41 | public static IDatabase UseMemoryDatabase 42 | ( 43 | [CanBeNull] this DatabaseBuilder builder, 44 | [NotNull] List> data 45 | ) 46 | => new MemoryDatabase(data); 47 | } 48 | } -------------------------------------------------------------------------------- /src/StringDB/Fluency/ReadOnlyDatabaseExtensions.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | using StringDB.Databases; 4 | 5 | using System.Runtime.CompilerServices; 6 | 7 | namespace StringDB.Fluency 8 | { 9 | /// 10 | /// Fluent extensions for a . 11 | /// 12 | [PublicAPI] 13 | public static class ReadOnlyDatabaseExtensions 14 | { 15 | /// 16 | /// Makes the database only able to be read. 17 | /// 18 | /// The type of key. 19 | /// The type of value. 20 | /// The database to make read only. 21 | /// If the underlying database should be disposed on dispose. 22 | /// A read only database 23 | [NotNull] 24 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 25 | public static IDatabase AsReadOnly 26 | ( 27 | [NotNull] this IDatabase database, 28 | bool disposeDatabase = true 29 | ) 30 | => new ReadOnlyDatabase(database, disposeDatabase); 31 | } 32 | } -------------------------------------------------------------------------------- /src/StringDB/Fluency/ThreadLockDatabaseExtensions.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | using StringDB.Databases; 4 | 5 | using System.Runtime.CompilerServices; 6 | 7 | namespace StringDB.Fluency 8 | { 9 | /// 10 | /// Fluent extensions for a . 11 | /// 12 | [PublicAPI] 13 | public static class ThreadLockDatabaseExtensions 14 | { 15 | /// 16 | /// Apply a lock to a database. 17 | /// 18 | /// The type of key. 19 | /// The type of value. 20 | /// The database to put a lock on. 21 | /// If the underlying database should be disposed on dispose. 22 | /// A . 23 | [NotNull] 24 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 25 | public static IDatabase WithThreadLock 26 | ( 27 | [NotNull] this IDatabase database, 28 | bool disposeDatabase = true 29 | ) 30 | => new ThreadLockDatabase(database, disposeDatabase); 31 | } 32 | } -------------------------------------------------------------------------------- /src/StringDB/Fluency/TransformDatabaseExtensions.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | using StringDB.Databases; 4 | using StringDB.Transformers; 5 | 6 | using System.Runtime.CompilerServices; 7 | 8 | namespace StringDB.Fluency 9 | { 10 | /// 11 | /// Fluent extensions for a . 12 | /// 13 | [PublicAPI] 14 | public static class TransformDatabaseExtensions 15 | { 16 | /// 17 | /// Applies a transformation to a database. 18 | /// 19 | /// The database's key type. 20 | /// The database's value type. 21 | /// The new type of key for the database. 22 | /// The new type of value for the database. 23 | /// The database to transform. 24 | /// A transformer for the keys. 25 | /// A transformer for the values. 26 | /// If the underlying database should be disposed on dispose. 27 | /// A . 28 | [NotNull] 29 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 30 | public static IDatabase WithTransform 31 | ( 32 | [NotNull] this IDatabase database, 33 | [NotNull] ITransformer keyTransformer, 34 | [NotNull] ITransformer valueTransformer, 35 | bool disposeDatabase = true 36 | ) 37 | => new TransformDatabase 38 | ( 39 | database, 40 | keyTransformer, 41 | valueTransformer, 42 | disposeDatabase 43 | ); 44 | 45 | /// 46 | /// Apply a key-only transformation to a database. 47 | /// 48 | /// The database's key type. 49 | /// The new type of key for the database. 50 | /// The database's value type. 51 | /// The database to transform. 52 | /// A transformer for the keys. 53 | /// If the underlying database should be disposed on dispose. 54 | /// A . 55 | [NotNull] 56 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 57 | public static IDatabase WithKeyTransform 58 | ( 59 | [NotNull] this IDatabase database, 60 | [NotNull] ITransformer keyTransformer, 61 | bool disposeDatabase = true 62 | ) 63 | => database.WithTransform 64 | ( 65 | keyTransformer, 66 | NoneTransformer.Default, 67 | disposeDatabase 68 | ); 69 | 70 | /// 71 | /// Applies a value-only transformation to a database. 72 | /// 73 | /// The database's key type. 74 | /// The database's value type. 75 | /// The new type of value for the database. 76 | /// The database to transform. 77 | /// A transformer for the values. 78 | /// If the underlying database should be disposed on dispose. 79 | /// A . 80 | [NotNull] 81 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 82 | public static IDatabase WithValueTransform 83 | ( 84 | [NotNull] this IDatabase database, 85 | [NotNull] ITransformer valueTransformer, 86 | bool disposeDatabase = true 87 | ) 88 | => database.WithTransform 89 | ( 90 | NoneTransformer.Default, 91 | valueTransformer, 92 | disposeDatabase 93 | ); 94 | } 95 | } -------------------------------------------------------------------------------- /src/StringDB/Fluency/WriteOnlyDatabaseExtensions.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | using StringDB.Databases; 4 | 5 | using System.Runtime.CompilerServices; 6 | 7 | namespace StringDB.Fluency 8 | { 9 | /// 10 | /// Fluent extensions for a . 11 | /// 12 | [PublicAPI] 13 | public static class WriteOnlyDatabaseExtensions 14 | { 15 | /// 16 | /// Makes the database only able to be read. 17 | /// 18 | /// The type of key. 19 | /// The type of value. 20 | /// The database to make read only. 21 | /// If the underlying database should be disposed on dispose. 22 | /// A read only database 23 | [NotNull] 24 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 25 | public static IDatabase AsWriteOnly 26 | ( 27 | [NotNull] this IDatabase database, 28 | bool disposeDatabase = true 29 | ) 30 | => new WriteOnlyDatabase(database, disposeDatabase); 31 | } 32 | } -------------------------------------------------------------------------------- /src/StringDB/GlobalSuppressions.cs: -------------------------------------------------------------------------------- 1 | // This file is used by Code Analysis to maintain SuppressMessage 2 | // attributes that are applied to this project. 3 | // Project-level suppressions either have no target or are given 4 | // a specific target and scoped to a namespace, type, member, etc. 5 | 6 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Redundancy", "RCS1175:Unused this parameter.", Justification = "Extension Method", Scope = "member", Target = "~M:StringDB.Fluency.DatabaseIODeviceBuilderExtensions.UseStoneVault(StringDB.Fluency.DatabaseIODeviceBuilder,System.IO.Stream,System.Boolean)~StringDB.IO.IDatabaseIODevice")] 7 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Redundancy", "RCS1175:Unused this parameter.", Justification = "Extension Method", Scope = "member", Target = "~M:StringDB.Fluency.DatabaseIODeviceBuilderExtensions.UseStringDB(StringDB.Fluency.DatabaseIODeviceBuilder,StringDB.IO.StringDBVersions,System.IO.Stream,System.Boolean)~StringDB.IO.IDatabaseIODevice")] 8 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Redundancy", "RCS1175:Unused this parameter.", Justification = "Extension Method", Scope = "member", Target = "~M:StringDB.Fluency.IODatabaseExtensions.UseIODatabase(StringDB.Fluency.DatabaseBuilder,StringDB.IO.IDatabaseIODevice)~StringDB.IDatabase{System.Byte[],System.Byte[]}")] 9 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Redundancy", "RCS1175:Unused this parameter.", Justification = "Extension Method", Scope = "member", Target = "~M:StringDB.Fluency.IODatabaseExtensions.UseIODatabase(StringDB.Fluency.DatabaseBuilder,System.Func{StringDB.Fluency.DatabaseIODeviceBuilder,StringDB.IO.IDatabaseIODevice})~StringDB.IDatabase{System.Byte[],System.Byte[]}")] 10 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Redundancy", "RCS1175:Unused this parameter.", Justification = "Extension Method", Scope = "member", Target = "~M:StringDB.Fluency.MemoryDatabaseExtensions.UseMemoryDatabase``2(StringDB.Fluency.DatabaseBuilder,System.Collections.Generic.List{System.Collections.Generic.KeyValuePair{``0,``1}})~StringDB.IDatabase{``0,``1}")] 11 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0032:Use auto property", Justification = "It's a private state data. Public state data should use auto properties.", Scope = "member", Target = "~F:StringDB.Databases.ThreadLockDatabase`2.ThinDatabaseIEnumeratorWrapper._current")] 12 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Readability", "RCS1123:Add parentheses according to operator precedence.", Justification = "Autogenerated code", Scope = "member", Target = "~M:StringDB.IO.DatabaseItem.GetHashCode~System.Int32")] 13 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Redundancy", "RCS1212:Remove redundant assignment.", Justification = "Autogenerated code", Scope = "member", Target = "~M:StringDB.IO.DatabaseItem.GetHashCode~System.Int32")] 14 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Readability", "RCS1234:Duplicate enum value.", Justification = "The latest value will use the highest one", Scope = "type", Target = "~T:StringDB.IO.StringDBVersions")] -------------------------------------------------------------------------------- /src/StringDB/IDatabase.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | 6 | namespace StringDB 7 | { 8 | /// 9 | /// A database. 10 | /// 11 | /// The type of key of the database. 12 | /// The type of value of the database. 13 | [PublicAPI] 14 | public interface IDatabase : IEnumerable>>, IDisposable 15 | { 16 | /// 17 | /// Gets the first value with the specified key. 18 | /// If the value is unable to be found, an exception is thrown. 19 | /// 20 | /// The key to find the first value with. 21 | /// The first value associated the key. 22 | [NotNull] TValue Get([NotNull] TKey key); 23 | 24 | /// 25 | /// Tries to get a value of the specified key. 26 | /// 27 | /// The key to use. 28 | /// The value. 29 | /// true if a value was found, false if it was not. 30 | bool TryGet([NotNull] TKey key, [CanBeNull] out TValue value); 31 | 32 | /// 33 | /// Gets every lazy loading value based on a key 34 | /// 35 | /// The key to use 36 | /// Every value associated with a key 37 | [NotNull] IEnumerable> GetAll([NotNull] TKey key); 38 | 39 | /// 40 | /// Inserts a single element into the database. 41 | /// 42 | /// The key. 43 | /// The value. 44 | void Insert([NotNull] TKey key, [NotNull] TValue value); 45 | 46 | /// 47 | /// Inserts a range of items into the database. 48 | /// 49 | /// The items to insert. 50 | void InsertRange([NotNull] params KeyValuePair[] items); 51 | } 52 | } -------------------------------------------------------------------------------- /src/StringDB/IDatabaseLayer.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | namespace StringDB 4 | { 5 | /// 6 | /// Represents an object that contains a database inside of it. 7 | /// 8 | /// The type of key of the database. 9 | /// The type of value of the database. 10 | [PublicAPI] 11 | public interface IDatabaseLayer 12 | { 13 | /// 14 | /// The underlying database that's in use. 15 | /// 16 | [NotNull] 17 | IDatabase InnerDatabase { get; } 18 | } 19 | } -------------------------------------------------------------------------------- /src/StringDB/ILazyLoader.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | namespace StringDB 4 | { 5 | /// 6 | /// A promise to return a value upon loading. 7 | /// 8 | /// The type of value to return. 9 | [PublicAPI] 10 | public interface ILazyLoader 11 | { 12 | /// 13 | /// Loads the value that was promised. 14 | /// 15 | /// The value it loads. 16 | [NotNull] T Load(); 17 | } 18 | } -------------------------------------------------------------------------------- /src/StringDB/IO/ByteBuffer.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace StringDB.IO 4 | { 5 | public sealed class NoByteBuffer 6 | { 7 | public static byte[] Read(BinaryReader reader, int count) 8 | => reader.ReadBytes(count); 9 | } 10 | 11 | /// 12 | /// Offers buffering read results to prevent newing up byte arrays constantly 13 | /// for sizes less than 256 14 | /// 15 | public sealed class ByteBuffer 16 | { 17 | public ByteBuffer() 18 | { 19 | _buffers = new byte[byte.MaxValue + 1][]; 20 | 21 | for (var i = 0; i <= byte.MaxValue; i++) 22 | { 23 | _buffers[i] = new byte[i]; 24 | } 25 | } 26 | 27 | private readonly byte[][] _buffers; 28 | 29 | public byte[] Read(BinaryReader reader, int count) 30 | { 31 | var array = _buffers[count]; 32 | 33 | reader.Read(array, 0, count); 34 | 35 | return array; 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /src/StringDB/IO/Compatibility/StringDB5_0_0LowlevelDatabaseIODevice.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | using System; 4 | using System.IO; 5 | using System.Text; 6 | 7 | namespace StringDB.IO.Compatibility 8 | { 9 | [PublicAPI] 10 | public sealed class StringDB5_0_0LowlevelDatabaseIODevice : ILowlevelDatabaseIODevice 11 | { 12 | // https://github.com/SirJosh3917/StringDB/blob/fa703ed893b473829b6140f9d7033575d3291846/StringDB/Consts.cs 13 | private static class Consts 14 | { 15 | public const int MaxLength = 0xFE; 16 | public const byte DeletedValue = 0xFE; 17 | public const byte IndexSeparator = 0xFF; 18 | public const byte IsByteValue = 0x01; 19 | public const byte IsUShortValue = 0x02; 20 | public const byte IsUIntValue = 0x03; 21 | public const byte IsLongValue = 0x04; 22 | public const byte NoIndex = 0x00; 23 | public const int NOSPECIFYLEN = -1; 24 | 25 | // https://github.com/SirJosh3917/StringDB/blob/fa703ed893b473829b6140f9d7033575d3291846/StringDB/DBTypes/Predefined/ByteArrayWriterType.cs#L6 26 | public const byte ByteArrayTypeHandler = 0x01; 27 | } 28 | 29 | private readonly StreamCacheMonitor _stream; 30 | private readonly BinaryReader _br; 31 | private readonly BinaryWriter _bw; 32 | private object _disposeLock = new object(); 33 | 34 | public Stream InnerStream => _stream; 35 | 36 | private Func _buffer; 37 | 38 | public StringDB5_0_0LowlevelDatabaseIODevice 39 | ( 40 | [NotNull] Stream stream, 41 | Func buffer, 42 | bool leaveStreamOpen = false 43 | ) 44 | { 45 | _buffer = buffer; 46 | _stream = new StreamCacheMonitor(stream); 47 | _br = new BinaryReader(_stream, Encoding.UTF8, leaveStreamOpen); 48 | _bw = new BinaryWriter(_stream, Encoding.UTF8, leaveStreamOpen); 49 | 50 | if (_stream.Length < 8) 51 | { 52 | _bw.Write(0L); 53 | Seek(0); 54 | } 55 | 56 | JumpPos = _br.ReadInt64(); 57 | 58 | // we have to inc/dec jumppos since we write the index 59 | if (JumpPos > 0) JumpPos--; 60 | } 61 | 62 | private bool EOF => GetPosition() >= _stream.Length; 63 | 64 | public long JumpPos { get; set; } 65 | 66 | public long GetPosition() => _stream.Position; 67 | 68 | public void Reset() 69 | { 70 | _stream.UpdateCache(); 71 | Seek(8); 72 | } 73 | 74 | public void SeekEnd() => _stream.Seek(0, SeekOrigin.End); 75 | 76 | public void Seek(long position) => _stream.Seek(position, SeekOrigin.Begin); 77 | 78 | public void Flush() 79 | { 80 | _bw.Flush(); 81 | _stream.Flush(); 82 | } 83 | 84 | private byte PeekByte() 85 | { 86 | if (EOF) 87 | { 88 | return 0x00; 89 | } 90 | 91 | var peek = _br.ReadByte(); 92 | return peek; 93 | } 94 | 95 | public NextItemPeek Peek(out byte peekResult) 96 | { 97 | var result = PeekByte(); 98 | peekResult = result; 99 | 100 | switch (result) 101 | { 102 | case Consts.NoIndex: 103 | return NextItemPeek.EOF; 104 | 105 | case Consts.IndexSeparator: 106 | return NextItemPeek.Jump; 107 | 108 | default: 109 | return NextItemPeek.Index; 110 | } 111 | } 112 | 113 | public LowLevelDatabaseItem ReadIndex(byte peekByte) 114 | { 115 | var indexLength = peekByte; 116 | var dataPosition = _br.ReadInt64(); 117 | 118 | _br.ReadByte(); // backwards compatibility - not used 119 | // inputType is for TypeManager stuff in StringDB, we can throw it out of the window 120 | 121 | var index = _buffer(_br, indexLength); 122 | 123 | return new LowLevelDatabaseItem 124 | { 125 | Index = index, 126 | DataPosition = dataPosition 127 | }; 128 | } 129 | 130 | public byte[] ReadValue(long dataPosition) 131 | { 132 | Seek(dataPosition); 133 | 134 | _br.ReadByte(); // backwards compatibility - not used 135 | // inputType is for TypeManager stuff in StringDB, we can throw it out of the window 136 | 137 | var length = ReadLength(); 138 | 139 | var value = _br.ReadBytes(length); 140 | 141 | return value; 142 | } 143 | 144 | public long ReadJump() 145 | { 146 | return _br.ReadInt64(); 147 | } 148 | 149 | public void WriteJump(long jumpTo) 150 | { 151 | _bw.Write(Consts.IndexSeparator); 152 | _bw.Write(jumpTo); 153 | } 154 | 155 | public void WriteIndex(byte[] key, long dataPosition) 156 | { 157 | if (key.Length > Consts.MaxLength) 158 | { 159 | throw new IndexOutOfRangeException($"Index longer than {Consts.MaxLength}."); 160 | } 161 | 162 | _bw.Write((byte)key.Length); 163 | _bw.Write(dataPosition); 164 | _bw.Write(Consts.ByteArrayTypeHandler); 165 | _bw.Write(key); 166 | } 167 | 168 | public void WriteValue(byte[] value) 169 | { 170 | _bw.Write(Consts.ByteArrayTypeHandler); 171 | WriteLength(value.Length); 172 | _bw.Write(value); 173 | } 174 | 175 | public int CalculateIndexOffset(byte[] key) 176 | => sizeof(byte) 177 | + sizeof(long) 178 | + sizeof(byte) 179 | + key.Length; 180 | 181 | public int CalculateValueOffset(byte[] value) 182 | => sizeof(byte) 183 | + CalculateWriteLengthOffset(value.Length) 184 | + value.Length; 185 | 186 | public int JumpOffsetSize { get; } = sizeof(byte) + sizeof(long); 187 | 188 | ~StringDB5_0_0LowlevelDatabaseIODevice() 189 | { 190 | Dispose(); 191 | } 192 | 193 | private bool _disposed; 194 | 195 | public void Dispose() 196 | { 197 | // see the StringDB10_0_0LowlevelDatabaseIODevice for why we lock here 198 | lock (_disposeLock) 199 | { 200 | if (_disposed) 201 | { 202 | return; 203 | } 204 | 205 | _disposed = true; 206 | } 207 | 208 | Seek(0); 209 | 210 | // inc/dec jumppos since we account for the 0xFF in our storage of it 211 | _bw.Write(JumpPos + 1); 212 | 213 | Flush(); 214 | 215 | _br.Dispose(); 216 | _bw.Dispose(); 217 | } 218 | 219 | private int ReadLength() 220 | { 221 | var lengthIdentifier = _br.ReadByte(); 222 | 223 | switch (lengthIdentifier) 224 | { 225 | case Consts.IsByteValue: 226 | return _br.ReadByte(); 227 | 228 | case Consts.IsUShortValue: 229 | return _br.ReadUInt16(); 230 | 231 | case Consts.IsUIntValue: 232 | { 233 | var length = _br.ReadUInt32(); 234 | 235 | if (length > int.MaxValue) 236 | { 237 | throw new IndexOutOfRangeException($"{length} is bigger than the integer max"); 238 | } 239 | 240 | return (int)length; 241 | } 242 | 243 | default: throw new IOException($"Didn't expect to read some length with byte identifier {lengthIdentifier}"); 244 | } 245 | } 246 | 247 | private void WriteLength(int length) 248 | { 249 | if (length < byte.MaxValue) 250 | { 251 | _bw.Write(Consts.IsByteValue); 252 | _bw.Write((byte)length); 253 | } 254 | else if (length < ushort.MaxValue) 255 | { 256 | _bw.Write(Consts.IsUShortValue); 257 | _bw.Write((ushort)length); 258 | } 259 | else 260 | { 261 | _bw.Write(Consts.IsUIntValue); 262 | _bw.Write(length); 263 | } 264 | } 265 | 266 | private int CalculateWriteLengthOffset(int length) 267 | { 268 | if (length < byte.MaxValue) 269 | { 270 | return sizeof(byte) + sizeof(byte); 271 | } 272 | else if (length < ushort.MaxValue) 273 | { 274 | return sizeof(byte) + sizeof(ushort); 275 | } 276 | else 277 | { 278 | return sizeof(byte) + sizeof(uint); 279 | } 280 | } 281 | } 282 | } -------------------------------------------------------------------------------- /src/StringDB/IO/DatabaseIODevice.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | using System.Collections.Generic; 4 | 5 | namespace StringDB.IO 6 | { 7 | /// 8 | /// 9 | /// An that interfaces with 10 | /// 11 | [PublicAPI] 12 | public sealed class DatabaseIODevice : IDatabaseIODevice 13 | { 14 | private bool _disposed = false; 15 | 16 | public ILowlevelDatabaseIODevice LowLevelDatabaseIODevice { get; } 17 | 18 | /// 19 | public IOptimalTokenSource OptimalTokenSource { get; } 20 | 21 | public DatabaseIODevice 22 | ( 23 | [NotNull] ILowlevelDatabaseIODevice lowlevelDBIOD 24 | ) 25 | : this(lowlevelDBIOD, new OptimalTokenSource()) 26 | { 27 | } 28 | 29 | public DatabaseIODevice 30 | ( 31 | [NotNull] ILowlevelDatabaseIODevice lowlevelDBIOD, 32 | [NotNull] IOptimalTokenSource optimalTokenSource 33 | ) 34 | { 35 | LowLevelDatabaseIODevice = lowlevelDBIOD; 36 | OptimalTokenSource = optimalTokenSource; 37 | } 38 | 39 | public void Reset() => LowLevelDatabaseIODevice.Reset(); 40 | 41 | /// 42 | public byte[] ReadValue(long position) 43 | { 44 | // temporarily go to the position to read the value, 45 | // then seek back to the cursor position for reading 46 | var curPos = LowLevelDatabaseIODevice.GetPosition(); 47 | 48 | var value = LowLevelDatabaseIODevice.ReadValue(position); 49 | 50 | LowLevelDatabaseIODevice.Seek(curPos); 51 | 52 | return value; 53 | } 54 | 55 | /// 56 | public DatabaseItem ReadNext() 57 | { 58 | if (OptimalTokenSource.OptimalToken.OptimalReadingTime) 59 | { 60 | OptimalTokenSource.SetOptimalReadingTime(false); 61 | } 62 | 63 | // handle EOFs/Jumps 64 | var peek = LowLevelDatabaseIODevice.Peek(out var peekResult); 65 | 66 | ExecuteJumps(ref peek, out var jmpPeekResult); 67 | 68 | if (jmpPeekResult != 0x00) 69 | { 70 | peekResult = jmpPeekResult; 71 | } 72 | 73 | if (peek == NextItemPeek.EOF) 74 | { 75 | return new DatabaseItem 76 | { 77 | EndOfItems = true 78 | }; 79 | } 80 | 81 | // peek HAS to be an Index at this point 82 | 83 | var item = LowLevelDatabaseIODevice.ReadIndex(peekResult); 84 | 85 | return new DatabaseItem 86 | { 87 | Key = item.Index, 88 | DataPosition = item.DataPosition, 89 | EndOfItems = false 90 | }; 91 | } 92 | 93 | private void ExecuteJumps(ref NextItemPeek peek, out byte peekResult) 94 | { 95 | peekResult = 0x00; 96 | 97 | if (peek != NextItemPeek.Jump) 98 | { 99 | return; 100 | } 101 | 102 | do 103 | { 104 | var jump = LowLevelDatabaseIODevice.ReadJump(); 105 | LowLevelDatabaseIODevice.Seek(jump); 106 | peek = LowLevelDatabaseIODevice.Peek(out peekResult); 107 | } 108 | while (peek == NextItemPeek.Jump); 109 | 110 | OptimalTokenSource.SetOptimalReadingTime(true); 111 | } 112 | 113 | /// 114 | public void Insert(KeyValuePair[] items) 115 | { 116 | LowLevelDatabaseIODevice.SeekEnd(); 117 | 118 | var offset = LowLevelDatabaseIODevice.GetPosition(); 119 | 120 | UpdatePreviousJump(offset); 121 | 122 | // we need to calculate the total offset of all the indexes 123 | // then we write every index & increment the offset by the offset of each value 124 | // and then we write the values 125 | 126 | // phase 1: calculating total offset 127 | 128 | foreach (var kvp in items) 129 | { 130 | offset += LowLevelDatabaseIODevice.CalculateIndexOffset(kvp.Key); 131 | } 132 | 133 | // the jump offset is important, we will be jumping after 134 | offset += LowLevelDatabaseIODevice.JumpOffsetSize; 135 | 136 | // phase 2: writing each key 137 | // and incrementing the offset by the value 138 | 139 | foreach (var kvp in items) 140 | { 141 | LowLevelDatabaseIODevice.WriteIndex(kvp.Key, offset); 142 | 143 | offset += LowLevelDatabaseIODevice.CalculateValueOffset(kvp.Value); 144 | } 145 | 146 | WriteJump(); 147 | 148 | // phase 3: writing each value sequentially 149 | 150 | foreach (var kvp in items) 151 | { 152 | LowLevelDatabaseIODevice.WriteValue(kvp.Value); 153 | } 154 | } 155 | 156 | private void UpdatePreviousJump(long jumpTo) 157 | { 158 | var currentPosition = LowLevelDatabaseIODevice.GetPosition(); 159 | 160 | if (LowLevelDatabaseIODevice.JumpPos != 0) 161 | { 162 | // goto old jump pos and overwrite it with the current jump pos 163 | LowLevelDatabaseIODevice.Seek(LowLevelDatabaseIODevice.JumpPos); 164 | LowLevelDatabaseIODevice.WriteJump(jumpTo); 165 | } 166 | 167 | LowLevelDatabaseIODevice.Seek(currentPosition); 168 | } 169 | 170 | private void WriteJump() 171 | { 172 | var position = LowLevelDatabaseIODevice.GetPosition(); 173 | 174 | LowLevelDatabaseIODevice.JumpPos = position; 175 | LowLevelDatabaseIODevice.WriteJump(0); 176 | } 177 | 178 | public void Dispose() 179 | { 180 | // we call "flush" when we shouldn't flush something disposed 181 | // so we gotta make sure it's not dead 182 | if (_disposed) 183 | { 184 | return; 185 | } 186 | 187 | _disposed = true; 188 | LowLevelDatabaseIODevice.Flush(); 189 | LowLevelDatabaseIODevice.Dispose(); 190 | } 191 | } 192 | } -------------------------------------------------------------------------------- /src/StringDB/IO/DatabaseItem.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | 7 | namespace StringDB.IO 8 | { 9 | [PublicAPI] 10 | public struct DatabaseItem : IEquatable 11 | { 12 | [NotNull] public byte[] Key; 13 | 14 | public long DataPosition; 15 | 16 | /// 17 | /// If this is true, there are no more values left to read and this item itself isn't a readable value. 18 | /// 19 | public bool EndOfItems; 20 | 21 | /// 22 | public override bool Equals(object obj) 23 | { 24 | if (!(obj is DatabaseItem other)) 25 | { 26 | return false; 27 | } 28 | 29 | return Equals(other); 30 | } 31 | 32 | public static bool operator ==(DatabaseItem left, DatabaseItem right) => left.Equals(right); 33 | 34 | public static bool operator !=(DatabaseItem left, DatabaseItem right) => !(left == right); 35 | 36 | /// 37 | public bool Equals(DatabaseItem other) 38 | { 39 | return DataPosition == other.DataPosition 40 | && EndOfItems == other.EndOfItems 41 | && Key.SequenceEqual(other.Key); 42 | } 43 | 44 | // autogenerated 45 | /// 46 | public override int GetHashCode() 47 | { 48 | var hashCode = -551433181; 49 | hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Key); 50 | hashCode = hashCode * -1521134295 + DataPosition.GetHashCode(); 51 | hashCode = hashCode * -1521134295 + EndOfItems.GetHashCode(); 52 | return hashCode; 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /src/StringDB/IO/IDatabaseIODevice.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | 6 | namespace StringDB.IO 7 | { 8 | /// 9 | /// An IO Device for a database. 10 | /// 11 | [PublicAPI] 12 | public interface IDatabaseIODevice : IDisposable 13 | { 14 | /// 15 | /// Resets the device back to the start of reading. 16 | /// 17 | void Reset(); 18 | 19 | /// 20 | /// Reads the next item in the database. 21 | /// 22 | /// A database item. 23 | DatabaseItem ReadNext(); 24 | 25 | /// 26 | /// Reads the value specified at a position; 27 | /// typically gotten from the result of a returned by . 28 | /// 29 | /// The position to begin reading the value from. 30 | /// A byte[] with the data. 31 | [NotNull] 32 | byte[] ReadValue(long position); 33 | 34 | /// 35 | /// Inserts data into the database. 36 | /// 37 | /// The items to insert. 38 | void Insert([NotNull] KeyValuePair[] items); 39 | 40 | /// 41 | /// A token source that dictates the optimal time to start reading values. 42 | /// 43 | [NotNull] 44 | IOptimalTokenSource OptimalTokenSource { get; } 45 | } 46 | } -------------------------------------------------------------------------------- /src/StringDB/IO/ILowlevelDatabaseIODevice.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | using System; 4 | 5 | namespace StringDB.IO 6 | { 7 | /// 8 | /// 9 | /// Used for StringDB based databases. 10 | /// 11 | [PublicAPI] 12 | public interface ILowlevelDatabaseIODevice : IDisposable 13 | { 14 | long JumpPos { get; set; } 15 | 16 | long GetPosition(); 17 | 18 | void Reset(); 19 | 20 | void SeekEnd(); 21 | 22 | void Seek(long position); 23 | 24 | void Flush(); 25 | 26 | // peekResult is tightly coupled with ReadIndex 27 | 28 | NextItemPeek Peek(out byte peekResult); 29 | 30 | LowLevelDatabaseItem ReadIndex(byte peekResult); 31 | 32 | [NotNull] 33 | byte[] ReadValue(long dataPosition); 34 | 35 | long ReadJump(); 36 | 37 | void WriteJump(long jumpTo); 38 | 39 | void WriteIndex([NotNull] byte[] key, long dataPosition); 40 | 41 | void WriteValue([NotNull] byte[] value); 42 | 43 | int CalculateIndexOffset([NotNull] byte[] key); 44 | 45 | int CalculateValueOffset([NotNull] byte[] value); 46 | 47 | int JumpOffsetSize { get; } 48 | } 49 | } -------------------------------------------------------------------------------- /src/StringDB/IO/IOptimalToken.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | namespace StringDB.IO 4 | { 5 | /// 6 | /// Represents the optimal time to read values from a file. 7 | /// 8 | [PublicAPI] 9 | public interface IOptimalToken 10 | { 11 | /// 12 | /// If it is the optimal reading time. 13 | /// 14 | bool OptimalReadingTime { get; } 15 | } 16 | } -------------------------------------------------------------------------------- /src/StringDB/IO/IOptimalTokenSource.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | namespace StringDB.IO 4 | { 5 | /// 6 | /// Controls the value for an 7 | /// 8 | [PublicAPI] 9 | public interface IOptimalTokenSource 10 | { 11 | /// 12 | /// The optimal token this source produces. 13 | /// 14 | IOptimalToken OptimalToken { get; } 15 | 16 | /// 17 | /// Sets the value for the optimal reading time. 18 | /// 19 | void SetOptimalReadingTime(bool value); 20 | } 21 | } -------------------------------------------------------------------------------- /src/StringDB/IO/LowLevelDatabaseItem.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | namespace StringDB.IO 4 | { 5 | public struct LowLevelDatabaseItem 6 | { 7 | [NotNull] 8 | public byte[] Index; 9 | 10 | public long DataPosition; 11 | } 12 | } -------------------------------------------------------------------------------- /src/StringDB/IO/NextItemPeek.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | namespace StringDB.IO 4 | { 5 | /// 6 | /// What the next item could be, after peeking. 7 | /// 8 | [PublicAPI] 9 | public enum NextItemPeek 10 | { 11 | Index, 12 | Jump, 13 | EOF 14 | } 15 | } -------------------------------------------------------------------------------- /src/StringDB/IO/OptimalTokenSource.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | namespace StringDB.IO 4 | { 5 | /// 6 | /// The default optimal reading token implementation. 7 | /// 8 | [PublicAPI] 9 | public sealed class OptimalTokenSource : IOptimalTokenSource, IOptimalToken 10 | { 11 | /// 12 | public bool OptimalReadingTime { get; set; } 13 | 14 | /// 15 | public IOptimalToken OptimalToken => this; 16 | 17 | /// 18 | public void SetOptimalReadingTime(bool value) => OptimalReadingTime = value; 19 | } 20 | } -------------------------------------------------------------------------------- /src/StringDB/IO/StoneVaultIODevice.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Runtime.CompilerServices; 6 | using System.Text; 7 | 8 | namespace StringDB.IO 9 | { 10 | /// 11 | /// 12 | /// An IDatabaseIODevice that implements the StoneVault protocol. 13 | /// 14 | [PublicAPI] 15 | public sealed class StoneVaultIODevice : IDatabaseIODevice 16 | { 17 | private static class Consts 18 | { 19 | public const byte DATA_GOOD = 0x00; 20 | public const byte DATA_END = 0xFF; 21 | } 22 | 23 | private readonly Stream _stream; 24 | private readonly BinaryReader _br; 25 | private readonly BinaryWriter _bw; 26 | 27 | public IOptimalTokenSource OptimalTokenSource { get; } 28 | 29 | public StoneVaultIODevice 30 | ( 31 | Stream stream, 32 | bool leaveStreamOpen = false 33 | ) 34 | : this(stream, new OptimalTokenSource(), leaveStreamOpen) 35 | { 36 | } 37 | 38 | public StoneVaultIODevice 39 | ( 40 | Stream stream, 41 | IOptimalTokenSource optimalTokenSource, 42 | bool leaveStreamOpen = false 43 | ) 44 | { 45 | OptimalTokenSource = optimalTokenSource; 46 | _stream = stream; 47 | _br = new BinaryReader(stream, Encoding.UTF8, leaveStreamOpen); 48 | _bw = new BinaryWriter(stream, Encoding.UTF8, leaveStreamOpen); 49 | } 50 | 51 | public DatabaseItem ReadNext() 52 | { 53 | if (ShouldEndRead()) 54 | { 55 | return End(); 56 | } 57 | 58 | var key = ReadByteArray(); 59 | 60 | // advance past the data but keep record of where it is 61 | var dataPosition = _stream.Position; 62 | 63 | if (ShouldEndRead()) 64 | { 65 | return End(); 66 | } 67 | 68 | var valueLength = _br.ReadInt64(); 69 | _stream.Position += valueLength; 70 | 71 | // the values are right after the index 72 | // it is always a good time to read the value 73 | OptimalTokenSource.SetOptimalReadingTime(true); 74 | 75 | return new DatabaseItem 76 | { 77 | Key = key, 78 | DataPosition = dataPosition 79 | }; 80 | } 81 | 82 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 83 | private static DatabaseItem End() 84 | => new DatabaseItem { EndOfItems = true }; 85 | 86 | public void Insert(KeyValuePair[] items) 87 | { 88 | // we will overwrite the DATA_END with a DATA_GOOD 89 | SeekOneBeforeEnd(); 90 | 91 | foreach (var item in items) 92 | { 93 | WriteByteArray(item.Key); 94 | WriteByteArray(item.Value); 95 | } 96 | 97 | _bw.Write(Consts.DATA_END); 98 | } 99 | 100 | private void SeekOneBeforeEnd() 101 | { 102 | if (_stream.Length < 1) 103 | { 104 | _stream.Seek(0, SeekOrigin.Begin); 105 | } 106 | else 107 | { 108 | _stream.Seek(-1, SeekOrigin.End); 109 | } 110 | } 111 | 112 | private bool ShouldEndRead() 113 | => _stream.Position >= _stream.Length - 1 114 | || _br.ReadByte() == Consts.DATA_END; 115 | 116 | private void WriteByteArray(byte[] data) 117 | { 118 | _bw.Write(Consts.DATA_GOOD); 119 | _bw.Write((long)data.Length); 120 | _bw.Write(data); 121 | } 122 | 123 | private byte[] ReadByteArray() 124 | { 125 | // assume the DATA_GOOD/DATA_END has already been read 126 | var length = _br.ReadInt64(); 127 | return _br.ReadBytes((int)length); 128 | } 129 | 130 | public byte[] ReadValue(long position) 131 | { 132 | var currentPos = _stream.Position; 133 | 134 | _stream.Seek(position, SeekOrigin.Begin); 135 | 136 | if (ShouldEndRead()) return new byte[0]; 137 | 138 | var dataLength = _br.ReadInt64(); 139 | 140 | var value = _br.ReadBytes((int)dataLength); 141 | 142 | _stream.Seek(currentPos, SeekOrigin.Begin); 143 | 144 | return value; 145 | } 146 | 147 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 148 | public void Reset() => _stream.Seek(0, SeekOrigin.Begin); 149 | 150 | public void Dispose() 151 | { 152 | _bw.Flush(); 153 | _stream.Flush(); 154 | 155 | _br.Dispose(); 156 | _bw.Dispose(); 157 | } 158 | } 159 | } -------------------------------------------------------------------------------- /src/StringDB/IO/StreamCacheMonitor.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | using System; 4 | using System.IO; 5 | 6 | namespace StringDB.IO 7 | { 8 | /// 9 | /// Caches a position and length of the underlying stream, 10 | /// to make position and length lookups quick and snappy. 11 | /// 12 | [PublicAPI] 13 | public sealed class StreamCacheMonitor : Stream 14 | { 15 | [NotNull] public Stream InnerStream { get; } 16 | 17 | public StreamCacheMonitor([NotNull] Stream stream) 18 | { 19 | InnerStream = stream; 20 | _pos = InnerStream.Position; 21 | _len = InnerStream.Length; 22 | } 23 | 24 | private long _pos; 25 | private long _userPos; 26 | private long _len; 27 | 28 | public override void Flush() 29 | { 30 | if (InnerStream is FileStream fileStream) 31 | { 32 | fileStream.Flush(true); 33 | } 34 | else 35 | { 36 | InnerStream.Flush(); 37 | } 38 | } 39 | 40 | [Obsolete("Please use the Position property in combination with the Length property for any kind of seeking.", true)] 41 | public override long Seek(long offset, SeekOrigin origin) 42 | { 43 | _pos = InnerStream.Seek(offset, origin); 44 | _userPos = _pos; 45 | 46 | return _pos; 47 | } 48 | 49 | public override void SetLength(long value) 50 | { 51 | _len = value; 52 | InnerStream.SetLength(value); 53 | } 54 | 55 | public override int Read(byte[] buffer, int offset, int count) 56 | { 57 | ForceSeek(); 58 | 59 | var result = InnerStream.Read(buffer, offset, count); 60 | _userPos = _pos += result; 61 | 62 | return result; 63 | } 64 | 65 | public override void Write(byte[] buffer, int offset, int count) 66 | { 67 | ForceSeek(); 68 | 69 | _userPos = _pos += count; 70 | 71 | if (_pos > _len) 72 | { 73 | _len = _pos; 74 | } 75 | 76 | InnerStream.Write(buffer, offset, count); 77 | } 78 | 79 | public override bool CanRead => InnerStream.CanRead; 80 | public override bool CanSeek => InnerStream.CanSeek; 81 | public override bool CanWrite => InnerStream.CanWrite; 82 | 83 | public override long Length => _len; 84 | 85 | public override long Position 86 | { 87 | get => _userPos; 88 | set 89 | { 90 | _userPos = value; 91 | InnerStream.Position = value; 92 | } 93 | } 94 | 95 | public override void Close() => InnerStream.Close(); 96 | 97 | public void UpdateCache() 98 | { 99 | _userPos = _pos = InnerStream.Position; 100 | _len = InnerStream.Length; 101 | } 102 | 103 | public void ForceSeek() 104 | { 105 | if (_userPos != _pos) 106 | { 107 | _userPos = _pos = InnerStream.Seek(_userPos, SeekOrigin.Begin); 108 | } 109 | } 110 | } 111 | } -------------------------------------------------------------------------------- /src/StringDB/IO/StringDBVersion.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | namespace StringDB.IO 4 | { 5 | /// 6 | /// The versions of StringDB that can be used. 7 | /// 8 | [PublicAPI] 9 | public enum StringDBVersion 10 | { 11 | /// 12 | /// StringDB 5.0.0 13 | /// 14 | v5_0_0, 15 | 16 | /// 17 | /// StringDB 10.0.0 18 | /// 19 | v10_0_0, 20 | 21 | /// 22 | /// The latest file format. 23 | /// 24 | Latest = v10_0_0 25 | } 26 | } -------------------------------------------------------------------------------- /src/StringDB/ITransformer.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | namespace StringDB 4 | { 5 | /// 6 | /// Transforms an item. 7 | /// 8 | /// The type of item before the transform. 9 | /// The type of item after the transform. 10 | [PublicAPI] 11 | public interface ITransformer 12 | { 13 | /// 14 | /// Transforms a into a . 15 | /// 16 | /// The to transform. 17 | /// A . 18 | [NotNull] TPost TransformPre([NotNull] TPre pre); 19 | 20 | /// 21 | /// Transforms a into a 22 | /// 23 | /// The to transform. 24 | /// A . 25 | [NotNull] TPre TransformPost([NotNull] TPost post); 26 | } 27 | } -------------------------------------------------------------------------------- /src/StringDB/LazyLoaderAwaiter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.CompilerServices; 3 | using System.Threading.Tasks; 4 | 5 | namespace StringDB 6 | { 7 | // note: no [PublicAPI] 8 | // note: can't be a struct otherwise the values don't update 9 | /// 10 | /// Used to allow an to be awaited. 11 | /// 12 | /// The type of the value. 13 | public class LazyLoaderAwaiter : INotifyCompletion 14 | { 15 | public ILazyLoader LazyLoader; 16 | private T _result; 17 | public bool IsCompleted { get; private set; } 18 | 19 | public T GetResult() 20 | { 21 | if (!IsCompleted) 22 | { 23 | _result = LazyLoader.Load(); 24 | IsCompleted = true; 25 | } 26 | 27 | return _result; 28 | } 29 | 30 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 31 | public void OnCompleted(Action continuation) 32 | => new Task(continuation).Start(); 33 | } 34 | } -------------------------------------------------------------------------------- /src/StringDB/LazyLoaderExtensions.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | using System.Collections.Generic; 4 | using System.Runtime.CompilerServices; 5 | 6 | namespace StringDB 7 | { 8 | /// 9 | /// Extension methods on an . 10 | /// 11 | [PublicAPI] 12 | public static class LazyLoaderExtensions 13 | { 14 | /// 15 | /// Allows the usage of the await keyword for the . 16 | /// 17 | /// The type of the value. 18 | /// The lazy loader to get an awaiter for. 19 | [NotNull] 20 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 21 | public static LazyLoaderAwaiter GetAwaiter(this ILazyLoader lazyLoader) 22 | => new LazyLoaderAwaiter 23 | { 24 | LazyLoader = lazyLoader 25 | }; 26 | 27 | /// 28 | /// Turns any ILazyLoader into a KeyValuePair with the key 29 | /// 30 | /// The type of the key. 31 | /// The type of the value. 32 | /// The lazy loader. 33 | /// The key. 34 | [NotNull] 35 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 36 | public static KeyValuePair> ToKeyValuePair 37 | ( 38 | this ILazyLoader lazyLoader, 39 | TKey key 40 | ) 41 | => new KeyValuePair>(key, lazyLoader); 42 | } 43 | } -------------------------------------------------------------------------------- /src/StringDB/LazyLoaders/CachedLoader.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | using System; 4 | 5 | namespace StringDB.LazyLoaders 6 | { 7 | /// 8 | /// Caches the result of an . 9 | /// 10 | [PublicAPI] 11 | public sealed class CachedLoader : ILazyLoader, IDisposable 12 | { 13 | private readonly ILazyLoader _inner; 14 | 15 | private bool _loaded; 16 | private T _value; 17 | 18 | /// 19 | /// Creates a new . 20 | /// 21 | /// The inner lazy loader to cache the result of. 22 | public CachedLoader([NotNull] ILazyLoader inner) 23 | { 24 | _inner = inner; 25 | _loaded = false; 26 | _value = default; 27 | } 28 | 29 | /// 30 | public T Load() 31 | { 32 | if (!_loaded) 33 | { 34 | _value = _inner.Load(); 35 | _loaded = true; 36 | } 37 | 38 | return _value; 39 | } 40 | 41 | /// 42 | public void Dispose() 43 | { 44 | if (_value is IDisposable disposable) 45 | { 46 | disposable.Dispose(); 47 | } 48 | 49 | _value = default; 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /src/StringDB/LazyLoaders/IOLoader.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | using StringDB.Databases; 4 | using StringDB.IO; 5 | 6 | namespace StringDB.LazyLoaders 7 | { 8 | /// 9 | /// The used for s. 10 | /// 11 | [PublicAPI] 12 | public sealed class IOLoader : ILazyLoader 13 | { 14 | private readonly IDatabaseIODevice _dbIODevice; 15 | private readonly long _position; 16 | 17 | /// 18 | /// Creates a new . 19 | /// 20 | /// The database IO device to read the value from. 21 | /// The position the value is stored at. 22 | public IOLoader 23 | ( 24 | [NotNull] IDatabaseIODevice dbIODevice, 25 | long position 26 | ) 27 | { 28 | _dbIODevice = dbIODevice; 29 | _position = position; 30 | } 31 | 32 | /// 33 | public byte[] Load() => _dbIODevice.ReadValue(_position); 34 | } 35 | } -------------------------------------------------------------------------------- /src/StringDB/LazyLoaders/ThreadLockLoader.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | using StringDB.Databases; 4 | 5 | namespace StringDB.LazyLoaders 6 | { 7 | /// 8 | /// The used in a 9 | /// 10 | /// 11 | [PublicAPI] 12 | public sealed class ThreadLockLoader : ILazyLoader 13 | { 14 | private readonly object _lock; 15 | private readonly ILazyLoader _inner; 16 | 17 | /// 18 | /// Create a new . 19 | /// 20 | /// The lazy loader to wrap in a lock when calling. 21 | /// The object to lock on. 22 | public ThreadLockLoader 23 | ( 24 | [NotNull] ILazyLoader inner, 25 | [NotNull] object @lock 26 | ) 27 | { 28 | _lock = @lock; 29 | _inner = inner; 30 | } 31 | 32 | /// 33 | public T Load() 34 | { 35 | lock (_lock) 36 | { 37 | return _inner.Load(); 38 | } 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /src/StringDB/LazyLoaders/TransformLoader.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | using StringDB.Databases; 4 | 5 | namespace StringDB.LazyLoaders 6 | { 7 | /// 8 | /// The used in a 9 | /// 10 | /// The type before transformation. 11 | /// The type after transformation. 12 | [PublicAPI] 13 | public sealed class TransformLazyLoader : ILazyLoader 14 | { 15 | private readonly ITransformer _transformer; 16 | private readonly ILazyLoader _pre; 17 | 18 | /// 19 | /// Creates a new . 20 | /// 21 | /// The lazy loader with the value. 22 | /// The transformer to transform the value. 23 | public TransformLazyLoader 24 | ( 25 | [NotNull] ILazyLoader pre, 26 | [NotNull] ITransformer transformer 27 | ) 28 | { 29 | _pre = pre; 30 | _transformer = transformer; 31 | } 32 | 33 | /// 34 | public TPost Load() 35 | { 36 | var loaded = _pre.Load(); 37 | 38 | return _transformer.TransformPre(loaded); 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /src/StringDB/LazyLoaders/ValueLoader.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | namespace StringDB.LazyLoaders 4 | { 5 | /// 6 | /// A lazy loader that only returns the value given to it. 7 | /// 8 | [PublicAPI] 9 | public sealed class ValueLoader : ILazyLoader 10 | { 11 | private readonly T _value; 12 | 13 | /// 14 | /// Create a new . 15 | /// 16 | /// The value to immedietly return upon calling . 17 | public ValueLoader(T value) => _value = value; 18 | 19 | /// 20 | public T Load() => _value; 21 | } 22 | } -------------------------------------------------------------------------------- /src/StringDB/StringDB.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net45;netstandard2.0 5 | latest 6 | 10.1.0 7 | true 8 | SirJosh3917 9 | SirJosh3917 10 | StringDB is a modular, key/value pair DB designed to consume *tiny* amounts of ram & produce *tiny* databases. 11 | 12 | 13 | https://github.com/SirJosh3917/StringDB 14 | https://raw.githubusercontent.com/SirJosh3917/StringDB/master/icons/banner_ad.png 15 | https://github.com/SirJosh3917/StringDB 16 | Database, KeyValue, Archival, Storage, Modular, Fluent 17 | 10.1.0, featuring Querying! Now you can perform asynchronous operations on a StringDB, and use it in something like web api! 18 | MIT 19 | true 20 | true 21 | snupkg 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | true 31 | 32 | DEBUG;TRACE 33 | 34 | 35 | 36 | true 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/StringDB/StringDatabase.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | using StringDB.Fluency; 4 | using StringDB.IO; 5 | using StringDB.Transformers; 6 | 7 | using System.IO; 8 | using System.Runtime.CompilerServices; 9 | 10 | namespace StringDB 11 | { 12 | /// 13 | /// A simple utility class to allow for very easy creation of string databases. 14 | /// 15 | [PublicAPI] 16 | public static class StringDatabase 17 | { 18 | /// 19 | /// Creates a string database entirely in memory. 20 | /// 21 | /// A string database, located in memory. 22 | [NotNull] 23 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 24 | public static IDatabase Create() 25 | => new DatabaseBuilder() 26 | .UseMemoryDatabase(); 27 | 28 | /// 29 | /// Creates a string database that saves to a stream (file). 30 | /// 31 | /// The stream to write/read data to/from. 32 | /// If the stream should be left open when the database is getting disposed. 33 | /// An IODatabase with a transform, using the latest version of StringDB. 34 | [NotNull] 35 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 36 | public static IDatabase Create 37 | ( 38 | [NotNull] Stream stream, 39 | bool leaveStreamOpen = false 40 | ) 41 | => Create(stream, StringDBVersion.Latest, leaveStreamOpen); 42 | 43 | /// 44 | /// Creates a string database that saves to a stream, and specify the version. 45 | /// 46 | /// The stream. 47 | /// The version of StringDB. 48 | /// If the stream should be left open when the database is getting disposed. 49 | /// An IODatabase with a transform, using the specified version of StringDB. 50 | [NotNull] 51 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 52 | public static IDatabase Create 53 | ( 54 | [NotNull] Stream stream, 55 | StringDBVersion version, 56 | bool leaveStreamOpen 57 | ) 58 | => new DatabaseBuilder() 59 | .UseIODatabase((builder) => builder.UseStringDB(version, stream, leaveStreamOpen)) 60 | .WithTransform(StringTransformer.Default, StringTransformer.Default); 61 | } 62 | } -------------------------------------------------------------------------------- /src/StringDB/Transformers/NoneTransformer.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | using System.Runtime.CompilerServices; 4 | 5 | namespace StringDB.Transformers 6 | { 7 | /// 8 | /// 9 | /// Applies absolutely no transformation to the objects given to it. 10 | /// 11 | /// The type of object to do nothing to. 12 | [PublicAPI] 13 | public sealed class NoneTransformer : ITransformer 14 | { 15 | /// 16 | /// A default, global instance of this . 17 | /// 18 | [NotNull] 19 | public static NoneTransformer Default { get; } = new NoneTransformer(); 20 | 21 | /// 22 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 23 | public T TransformPost(T post) => post; 24 | 25 | /// 26 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 27 | public T TransformPre(T pre) => pre; 28 | } 29 | } -------------------------------------------------------------------------------- /src/StringDB/Transformers/ReverseTransformer.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | using System.Runtime.CompilerServices; 4 | 5 | namespace StringDB.Transformers 6 | { 7 | /// 8 | /// 9 | /// Reverses the post/pre positions of a transformer. 10 | /// 11 | /// Previously the TPre. 12 | /// Previously the TPost. 13 | [PublicAPI] 14 | public sealed class ReverseTransformer : ITransformer 15 | { 16 | /// 17 | /// The transformer being used under the hood. 18 | /// 19 | [NotNull] 20 | public ITransformer Transformer { get; } 21 | 22 | /// 23 | /// Create a reverse transformer. 24 | /// 25 | /// The transformer to reverse. 26 | public ReverseTransformer(ITransformer transformer) => Transformer = transformer; 27 | 28 | /// 29 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 30 | public TPre TransformPre(TPost pre) => Transformer.TransformPost(pre); 31 | 32 | /// 33 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 34 | public TPost TransformPost(TPre post) => Transformer.TransformPre(post); 35 | } 36 | } -------------------------------------------------------------------------------- /src/StringDB/Transformers/ReverseTransformerExtensions.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | using System.Runtime.CompilerServices; 4 | 5 | namespace StringDB.Transformers 6 | { 7 | /// 8 | /// Some handy extensions to make using a reverse transformer easier. 9 | /// 10 | [PublicAPI] 11 | public static class ReverseTransformerExtensions 12 | { 13 | /// 14 | /// Reverses a transformer's and positions. 15 | /// 16 | /// The transformer to reverse. 17 | /// A reversed transformer. 18 | [NotNull] 19 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 20 | public static ITransformer Reverse 21 | ( 22 | [NotNull] this ITransformer transformer 23 | ) 24 | { 25 | // if we do Reverse() twice, we don't want to wrap ourselves in layers of ReverseTransformer. 26 | if (transformer is ReverseTransformer reverseTransformer) 27 | { 28 | return reverseTransformer.Transformer; 29 | } 30 | 31 | return new ReverseTransformer(transformer); 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /src/StringDB/Transformers/StringTransformer.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | using System.Runtime.CompilerServices; 4 | using System.Text; 5 | 6 | namespace StringDB.Transformers 7 | { 8 | /// 9 | /// 10 | /// Transforms a into a . 11 | /// 12 | [PublicAPI] 13 | public sealed class StringTransformer : ITransformer 14 | { 15 | /// 16 | /// A global, default instance of this . 17 | /// 18 | [NotNull] 19 | public static StringTransformer Default { get; } = new StringTransformer(); 20 | 21 | /// 22 | [NotNull] 23 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 24 | public string TransformPre(byte[] pre) => Encoding.UTF8.GetString(pre); 25 | 26 | /// 27 | [NotNull] 28 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 29 | public byte[] TransformPost(string post) => Encoding.UTF8.GetBytes(post); 30 | } 31 | } -------------------------------------------------------------------------------- /tests/StringDB.Tests/DatabaseExtensionTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | 3 | using StringDB.Tests.Mocks; 4 | 5 | using System.Linq; 6 | 7 | using Xunit; 8 | 9 | namespace StringDB.Tests 10 | { 11 | public class DatabaseExtensionTests 12 | { 13 | [Fact] 14 | public void Keys() 15 | { 16 | var mdb = new MockDatabase(); 17 | 18 | mdb 19 | .Keys() 20 | .Should() 21 | .BeEquivalentTo 22 | ( 23 | new[] 24 | { 25 | "a", "b", "c", "d", 26 | "a", "b", "c", "d", 27 | "a", "b" 28 | }, 29 | "Returns all keys correctly" 30 | ); 31 | 32 | mdb.EnsureNoValuesLoaded(); 33 | } 34 | 35 | [Fact] 36 | public void Values() 37 | { 38 | var mdb = new MockDatabase(); 39 | 40 | mdb 41 | .Values() 42 | .Cast>() 43 | .Select(x => x.Value) 44 | .Should() 45 | .BeEquivalentTo 46 | ( 47 | new[] 48 | { 49 | 0, 1, 2, 3, 50 | 4, 5, 6, 7, 51 | 8, 9 52 | }, 53 | "Returns all values correctly" 54 | ); 55 | 56 | mdb.EnsureNoValuesLoaded(); 57 | } 58 | 59 | [Fact] 60 | public void AggresiveValues() 61 | { 62 | var mdb = new MockDatabase(); 63 | 64 | var enumerator = mdb.ValuesAggressive().GetEnumerator(); 65 | 66 | for (var i = 0; i < mdb.Data.Count; i++) 67 | { 68 | enumerator.MoveNext().Should().BeTrue(); 69 | 70 | mdb.EnsureNoValuesLoadedBeyond(i); 71 | } 72 | 73 | mdb.EnsureAllValuesLoaded(); 74 | } 75 | 76 | /// 77 | /// Eggressively was a typo but i'm keeping it 78 | /// 79 | [Fact] 80 | public void EnumerateEggressively() 81 | { 82 | var mdb = new MockDatabase(); 83 | 84 | var enumerator = mdb.EnumerateAggressively(4).GetEnumerator(); 85 | mdb.EnsureNoValuesLoaded(); 86 | 87 | for (var i = 1; i <= 3; i++) 88 | { 89 | var point = (i * 4) - 1; 90 | 91 | enumerator.MoveNext(); 92 | mdb.EnsureNoValuesLoadedBeyond(point); 93 | 94 | for (var j = 0; j < 4 - 1; j++) 95 | { 96 | enumerator.MoveNext(); 97 | } 98 | } 99 | 100 | mdb.EnsureAllValuesLoaded(); 101 | } 102 | } 103 | } -------------------------------------------------------------------------------- /tests/StringDB.Tests/DatabaseTests/BaseDatabaseTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | 3 | using StringDB.Tests.Mocks; 4 | 5 | using System; 6 | using System.Collections; 7 | using System.Collections.Generic; 8 | 9 | using Xunit; 10 | 11 | namespace StringDB.Tests 12 | { 13 | public class BaseDatabaseTests 14 | { 15 | [Fact] 16 | public void Get() 17 | { 18 | var mbdb = new MockBaseDatabase(); 19 | 20 | mbdb.Get("a") 21 | .Should() 22 | .Be(0); 23 | 24 | mbdb.Get("d") 25 | .Should() 26 | .Be(3); 27 | 28 | Action throws = () => mbdb.Get("e"); 29 | 30 | throws 31 | .Should() 32 | .ThrowExactly(); 33 | } 34 | 35 | [Fact] 36 | public void TryGet() 37 | { 38 | var mbdb = new MockBaseDatabase(); 39 | 40 | mbdb.TryGet("a", out var result) 41 | .Should() 42 | .BeTrue(); 43 | 44 | result 45 | .Should() 46 | .Be(0); 47 | 48 | mbdb.TryGet("d", out result) 49 | .Should() 50 | .BeTrue(); 51 | 52 | result 53 | .Should() 54 | .Be(3); 55 | 56 | mbdb.TryGet("e", out result) 57 | .Should() 58 | .BeFalse(); 59 | 60 | result 61 | .Should() 62 | .Be(default); 63 | } 64 | 65 | [Fact] 66 | public void Insert() 67 | { 68 | var mbdb = new MockBaseDatabase(); 69 | 70 | mbdb.Insert("str", 5); 71 | 72 | mbdb.Inserted 73 | .Should() 74 | .HaveCount(1, "Only 1 item should be inserted") 75 | .And 76 | .BeEquivalentTo 77 | ( 78 | new KeyValuePair[] 79 | { 80 | new KeyValuePair("str", 5) 81 | }, 82 | "An array with 1 item should be inserted upon single insert" 83 | ); 84 | } 85 | 86 | [Fact] 87 | public void GetAll() 88 | { 89 | var mbdb = new MockBaseDatabase(); 90 | 91 | mbdb.EnsureNoValuesLoaded(); 92 | 93 | foreach (var item in mbdb.GetAll("a")) 94 | { 95 | mbdb.EnsureNoValuesLoaded(); 96 | } 97 | 98 | mbdb.EnsureNoValuesLoaded(); 99 | } 100 | 101 | [Fact] 102 | public void GetEnumerator() 103 | { 104 | var mbdb = new MockBaseDatabase(); 105 | 106 | mbdb.GetEnumerator() 107 | .Should() 108 | .BeEquivalentTo(mbdb.Enumerator()); 109 | 110 | ((IEnumerable)mbdb).GetEnumerator() 111 | .Should() 112 | .BeEquivalentTo(mbdb.Enumerator()); 113 | } 114 | } 115 | } -------------------------------------------------------------------------------- /tests/StringDB.Tests/DatabaseTests/BufferedDatabaseTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | 3 | using StringDB.Databases; 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | 9 | using Xunit; 10 | 11 | namespace StringDB.Tests 12 | { 13 | public class BufferedDatabaseTests 14 | { 15 | public class MockDatabase : BaseDatabase 16 | { 17 | public bool Disposed { get; set; } 18 | public int Inserts { get; set; } 19 | public int Evaluations { get; set; } 20 | 21 | public List InsertedData { get; set; } = new List(); 22 | 23 | public override void Dispose() => Disposed = true; 24 | 25 | public override void InsertRange(params KeyValuePair[] items) 26 | { 27 | Inserts++; 28 | InsertedData.InsertRange(InsertedData.Count, items.Select(x => x.Key).ToArray()); 29 | } 30 | 31 | protected override IEnumerable>> Evaluate() 32 | { 33 | Evaluations++; 34 | yield break; 35 | } 36 | } 37 | 38 | private readonly MockDatabase _mockDb; 39 | private readonly BufferedDatabase _db; 40 | public const int BufferSize = BufferedDatabase.MinimumBufferSize; 41 | 42 | public BufferedDatabaseTests() 43 | { 44 | _mockDb = new MockDatabase(); 45 | _db = new BufferedDatabase(_mockDb, BufferSize, false); 46 | } 47 | 48 | [Fact] 49 | public void When_DbIsDisposed_BufferedItems_GetWritten() 50 | { 51 | for (var i = 0; i < BufferSize - 1; i++) 52 | { 53 | _db.Insert(i, i); 54 | } 55 | 56 | _db.Dispose(); 57 | 58 | _mockDb.InsertedData.Count().Should().Be(BufferSize - 1); 59 | } 60 | 61 | [Fact] 62 | public void EvaluationIncludesBufferedItems() 63 | { 64 | _db.Insert(0, 1337); 65 | _mockDb.Inserts 66 | .Should().Be(0); 67 | 68 | // assures us that the buffered database evaluation 69 | // includes buffered entries 70 | _db.Count() 71 | .Should().Be(1); 72 | 73 | // the buffered db should also evaluate the inner database 74 | _mockDb.Evaluations 75 | .Should().Be(1); 76 | } 77 | 78 | [Fact] 79 | public void SingleInsertCallsNoInserts() 80 | { 81 | _db.Insert(0, 0); 82 | 83 | _mockDb.Inserts 84 | .Should().Be(0); 85 | } 86 | 87 | [Fact] 88 | public void FillBufferWithInsert() 89 | { 90 | for (var i = 0; i < BufferSize; i++) 91 | { 92 | _db.Insert(i, 0); 93 | } 94 | 95 | _mockDb.Inserts 96 | .Should().Be(0); 97 | 98 | _db.Insert(BufferSize, 0); 99 | 100 | _mockDb.Inserts 101 | .Should().Be(1); 102 | 103 | _mockDb.InsertedData 104 | .Should() 105 | .BeEquivalentTo(Enumerable.Range(0, BufferSize)); 106 | } 107 | 108 | [Fact] 109 | public void SingleInsertRangeCallsNoInserts() 110 | { 111 | _db.InsertRange(new[] { KeyValuePair.Create(0, 0) }); 112 | 113 | _mockDb.Inserts 114 | .Should().Be(0); 115 | } 116 | 117 | [Fact] 118 | public void FillBufferWithInsertRange() 119 | { 120 | for (var i = 0; i < BufferSize; i++) 121 | { 122 | _db.InsertRange(new[] { KeyValuePair.Create(i, 0) }); 123 | } 124 | 125 | _mockDb.Inserts 126 | .Should().Be(1); 127 | 128 | _db.InsertRange(new[] { KeyValuePair.Create(BufferSize, 0) }); 129 | 130 | _mockDb.Inserts 131 | .Should().Be(1); 132 | 133 | _mockDb.InsertedData 134 | .Should() 135 | .BeEquivalentTo(Enumerable.Range(0, BufferSize)); 136 | } 137 | 138 | /* 139 | public void Dispose() 140 | { 141 | _mockDb.Disposed 142 | .Should().BeFalse(); 143 | 144 | _db.Dispose(); 145 | 146 | _mockDb.Disposed 147 | .Should().BeTrue(); 148 | 149 | // be at least one 150 | _mockDb.Inserts 151 | .Should().BeGreaterOrEqualTo(1); 152 | } 153 | */ 154 | 155 | [Fact] 156 | public void LessThanMinimumBuffer_Throws() 157 | { 158 | // this testing class is BAD so i'm just gonna insert one to get the Dispose part over with 159 | _mockDb.Insert(0, 0); 160 | 161 | Throws(() => new BufferedDatabase(_mockDb, BufferedDatabase.MinimumBufferSize - 1)); 162 | } 163 | 164 | private void Throws(Action throws) 165 | => throws.Should().Throw(); 166 | } 167 | } -------------------------------------------------------------------------------- /tests/StringDB.Tests/DatabaseTests/CacheDatabaseTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | 3 | using StringDB.Databases; 4 | using StringDB.Fluency; 5 | 6 | using System.Collections.Generic; 7 | 8 | using Xunit; 9 | 10 | namespace StringDB.Tests 11 | { 12 | public class CacheDatabaseTests 13 | { 14 | public class MockDatabase : BaseDatabase 15 | { 16 | public class LazyLoader : ILazyLoader 17 | { 18 | private readonly ILazyLoader _inner; 19 | 20 | public LazyLoader(ILazyLoader inner) => _inner = inner; 21 | 22 | public int Loads { get; set; } 23 | 24 | public int Load() 25 | { 26 | Loads++; 27 | return _inner.Load(); 28 | } 29 | } 30 | 31 | private readonly IDatabase _db; 32 | private readonly List _lazyLoaderCache = new List(); 33 | 34 | public MockDatabase(IDatabase db) => _db = db; 35 | 36 | public int InsertRanges { get; set; } 37 | 38 | public override void InsertRange(params KeyValuePair[] items) 39 | { 40 | InsertRanges++; 41 | _db.InsertRange(items); 42 | } 43 | 44 | public int Evaluations { get; set; } 45 | 46 | public int Iterations { get; set; } 47 | 48 | protected override IEnumerable>> Evaluate() 49 | { 50 | Evaluations++; 51 | 52 | var c = 0; 53 | foreach (var item in _db) 54 | { 55 | if (_lazyLoaderCache.Count <= c) 56 | { 57 | _lazyLoaderCache.Add(new LazyLoader(item.Value)); 58 | } 59 | 60 | Iterations++; 61 | 62 | yield return new KeyValuePair>(item.Key, _lazyLoaderCache[c]); 63 | c++; 64 | } 65 | } 66 | 67 | public override void Dispose() => _db.Dispose(); 68 | } 69 | 70 | [Fact] 71 | public void CachesProperly() 72 | { 73 | var memdb = new DatabaseBuilder() 74 | .UseMemoryDatabase(); 75 | 76 | var mock = new MockDatabase(memdb); 77 | 78 | var cache = mock.WithCache(); 79 | 80 | memdb.Insert("ichi", 1); 81 | memdb.Insert("ni", 2); 82 | memdb.Insert("san", 3); 83 | 84 | for (var i = 0; i < 3; i++) 85 | { 86 | foreach (var (key, value) in cache) 87 | { 88 | value.Load(); 89 | value.Load(); 90 | value.Load(); 91 | } 92 | } 93 | 94 | mock.Evaluations 95 | .Should().Be(3); 96 | 97 | mock.Iterations 98 | .Should().Be(3 * 3); 99 | 100 | foreach (var (key, value) in mock) 101 | { 102 | ((MockDatabase.LazyLoader)value) 103 | .Loads.Should().Be(1); 104 | } 105 | 106 | cache.Insert("a", 1); 107 | cache.Insert("a", 2); 108 | cache.Insert("a", 3); 109 | 110 | mock.InsertRanges 111 | .Should().Be(3); 112 | 113 | for (var i = 0; i < 3; i++) 114 | { 115 | foreach (var item in cache) 116 | { 117 | item.Value.Load(); 118 | item.Value.Load(); 119 | item.Value.Load(); 120 | } 121 | } 122 | 123 | foreach (var (key, value) in mock) 124 | { 125 | ((MockDatabase.LazyLoader)value) 126 | .Loads.Should().Be(1); 127 | } 128 | } 129 | } 130 | } -------------------------------------------------------------------------------- /tests/StringDB.Tests/DatabaseTests/IODatabaseTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | 3 | using StringDB.Databases; 4 | using StringDB.Tests.Mocks; 5 | 6 | using System.Collections.Generic; 7 | using System.Text; 8 | 9 | using Xunit; 10 | 11 | namespace StringDB.Tests 12 | { 13 | public class IODatabaseTests 14 | { 15 | [Fact] 16 | public void Enumerates() 17 | { 18 | var mdbiod = new MockDatabaseIODevice(); 19 | var iodb = new IODatabase(mdbiod); 20 | 21 | bool first = true; 22 | 23 | int i = 0; 24 | foreach (var item in iodb) 25 | { 26 | if (first) 27 | { 28 | // 1 because it's enumerated by 1 already 29 | 30 | mdbiod.ItemOn 31 | .Should() 32 | .Be(1, "Reset() called by the IODatabase"); 33 | 34 | first = false; 35 | } 36 | 37 | item.Key 38 | .Should() 39 | .BeEquivalentTo(mdbiod.Data[i].Key); 40 | 41 | var lazyItem = mdbiod.Data[i].Value; 42 | 43 | lazyItem.Loaded 44 | .Should() 45 | .BeFalse(); 46 | 47 | item.Value.Load() 48 | .Should() 49 | .BeEquivalentTo(lazyItem.Value); 50 | 51 | lazyItem.Loaded 52 | .Should() 53 | .BeTrue(); 54 | 55 | i++; 56 | } 57 | } 58 | 59 | [Fact] 60 | public void Inserts() 61 | { 62 | var mdbiod = new MockDatabaseIODevice(); 63 | var iodb = new IODatabase(mdbiod); 64 | 65 | var inserting = new KeyValuePair[] 66 | { 67 | new KeyValuePair 68 | ( 69 | key: Encoding.UTF8.GetBytes("test"), 70 | value: Encoding.UTF8.GetBytes("value") 71 | ) 72 | }; 73 | 74 | iodb.InsertRange(inserting); 75 | 76 | mdbiod.Inserted 77 | .Should() 78 | .BeEquivalentTo(inserting); 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /tests/StringDB.Tests/DatabaseTests/MemoryDatabaseTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | 3 | using StringDB.Databases; 4 | 5 | using System.Collections.Generic; 6 | 7 | using Xunit; 8 | 9 | namespace StringDB.Tests 10 | { 11 | public class MemoryDatabaseTests 12 | { 13 | [Fact] 14 | public void Works() 15 | { 16 | var mdb = new MemoryDatabase(); 17 | 18 | mdb.InsertRange(new KeyValuePair[] 19 | { 20 | new KeyValuePair("a", 1), 21 | new KeyValuePair("b", 2), 22 | new KeyValuePair("c", 3), 23 | }); 24 | 25 | mdb.Keys() 26 | .Should() 27 | .BeEquivalentTo 28 | ( 29 | new[] 30 | { 31 | "a", "b", "c" 32 | } 33 | ); 34 | 35 | mdb.ValuesAggressive() 36 | .Should() 37 | .BeEquivalentTo 38 | ( 39 | new[] 40 | { 41 | 1, 2, 3 42 | } 43 | ); 44 | 45 | mdb.InsertRange(new KeyValuePair[] 46 | { 47 | new KeyValuePair("a", 1), 48 | new KeyValuePair("b", 2), 49 | new KeyValuePair("c", 3), 50 | }); 51 | 52 | mdb.Keys() 53 | .Should() 54 | .BeEquivalentTo 55 | ( 56 | new[] 57 | { 58 | "a", "b", "c", "a", "b", "c" 59 | } 60 | ); 61 | 62 | mdb.ValuesAggressive() 63 | .Should() 64 | .BeEquivalentTo 65 | ( 66 | new[] 67 | { 68 | 1, 2, 3, 1, 2, 3 69 | } 70 | ); 71 | } 72 | } 73 | } -------------------------------------------------------------------------------- /tests/StringDB.Tests/DatabaseTests/ReadOnlyDatabaseTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | 3 | using Moq; 4 | 5 | using StringDB.Fluency; 6 | 7 | using System; 8 | using System.Collections; 9 | using System.Collections.Generic; 10 | 11 | using Xunit; 12 | 13 | namespace StringDB.Tests.DatabaseTests 14 | { 15 | public class ReadOnlyDatabaseTests 16 | { 17 | private readonly Mock> _mock; 18 | private readonly IDatabase _rdb; 19 | private readonly IDatabaseLayer _dbLayer; 20 | 21 | public ReadOnlyDatabaseTests() 22 | { 23 | _mock = new Mock>(); 24 | 25 | _rdb = _mock.Object.AsReadOnly(); 26 | _dbLayer = (IDatabaseLayer)_rdb; 27 | } 28 | 29 | [Fact] 30 | public void DisposesInner_IfToldTo() 31 | { 32 | using (var db = _mock.Object.AsReadOnly(true)) 33 | { 34 | } 35 | 36 | _mock.Verify(x => x.Dispose()); 37 | } 38 | 39 | [Fact] 40 | public void DoesntDisposesInner_IfToldNotTo() 41 | { 42 | using (var db = _mock.Object.AsReadOnly(false)) 43 | { 44 | } 45 | 46 | _mock.Verify(x => x.Dispose(), Times.Never()); 47 | } 48 | 49 | [Fact] 50 | public void InnerDatabase_IsDatabase() 51 | { 52 | _dbLayer.InnerDatabase.As() 53 | .Should().Be(_mock.Object); 54 | } 55 | 56 | [Fact] 57 | public void Get_ShouldCall() 58 | { 59 | _rdb.Get(0); 60 | 61 | _mock.Verify(x => x.Get(0)); 62 | } 63 | 64 | [Fact] 65 | public void TryGet_ShouldCall() 66 | { 67 | _rdb.TryGet(0, out _); 68 | 69 | _mock.Verify(x => x.TryGet(0, out It.Ref.IsAny)); 70 | } 71 | 72 | [Fact] 73 | public void GetAll_ShouldCall() 74 | { 75 | _rdb.GetAll(0); 76 | 77 | _mock.Verify(x => x.GetAll(0)); 78 | } 79 | 80 | [Fact] 81 | public void Enumeration_ShouldWork() 82 | { 83 | _rdb.GetEnumerator(); 84 | 85 | _mock.Verify(x => x.GetEnumerator()); 86 | 87 | // can't test this without making a class for it lmao 88 | ((IEnumerable)_rdb).GetEnumerator(); 89 | } 90 | 91 | [Fact] 92 | public void Insert_ShouldThrow() 93 | { 94 | Action throws = () => _rdb.Insert(0, 0); 95 | 96 | throws.Should().Throw(); 97 | } 98 | 99 | [Fact] 100 | public void InsertRange_ShouldThrow() 101 | { 102 | Action throws = () => _rdb.InsertRange(new KeyValuePair(0, 0)); 103 | 104 | throws.Should().Throw(); 105 | } 106 | } 107 | } -------------------------------------------------------------------------------- /tests/StringDB.Tests/DatabaseTests/StringDatabaseTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | 3 | using StringDB.Databases; 4 | using StringDB.IO; 5 | 6 | using System.IO; 7 | 8 | using Xunit; 9 | 10 | namespace StringDB.Tests 11 | { 12 | /// 13 | /// Tests for the helper class. 14 | /// 15 | public class StringDatabaseTests 16 | { 17 | private void Insert(IDatabase db) 18 | { 19 | db.Insert("init", "Hello, World!"); 20 | db.Get("init").Should().Be("Hello, World!"); 21 | } 22 | 23 | /// 24 | /// Upon using the Create method, a MemoryDatabase should be constructed. 25 | /// 26 | [Fact] 27 | public void Create() 28 | { 29 | using (var db = StringDatabase.Create()) 30 | { 31 | Insert(db); 32 | 33 | db.Should() 34 | .BeOfType>(); 35 | } 36 | } 37 | 38 | /// 39 | /// Assures that upon creating a database with a backing MemoryStream, 40 | /// it uses a TransformDatabase. 41 | /// 42 | [Fact] 43 | public void CreateWithStream() 44 | { 45 | using (var db = StringDatabase.Create(new MemoryStream())) 46 | { 47 | Insert(db); 48 | 49 | db.Should() 50 | .BeOfType>(); 51 | } 52 | } 53 | 54 | /// 55 | /// Assures that upon creating a database with a backing MemoryStream, 56 | /// it uses the proper StringDB version and a memory stream to back it. 57 | /// 58 | /// The version to use. 59 | [Theory] 60 | [InlineData(StringDBVersion.v5_0_0)] 61 | [InlineData(StringDBVersion.v10_0_0)] 62 | public void CreateWithStreamAndVersion(StringDBVersion version) 63 | { 64 | using (var db = StringDatabase.Create(new MemoryStream(), version, false)) 65 | { 66 | Insert(db); 67 | 68 | db.Should() 69 | .BeOfType>(); 70 | } 71 | } 72 | } 73 | } -------------------------------------------------------------------------------- /tests/StringDB.Tests/DatabaseTests/ThreadLockTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | 3 | using StringDB.Databases; 4 | using StringDB.Fluency; 5 | 6 | using System.Collections.Generic; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | 10 | using Xunit; 11 | 12 | namespace StringDB.Tests 13 | { 14 | /// 15 | /// ensures that multiple threads can write to a thread unsafe database 16 | /// 17 | public class ThreadLockTests 18 | { 19 | private readonly MockDatabase _mockdb; 20 | private readonly IDatabase _db; 21 | private readonly List _tasks; 22 | private readonly ManualResetEventSlim _lock; 23 | 24 | public const int IncrementPerLoop = 20; 25 | 26 | public class MockDatabase : BaseDatabase 27 | { 28 | public class LazyLoader : ILazyLoader 29 | { 30 | private readonly MockDatabase _db; 31 | private readonly int _load; 32 | 33 | public LazyLoader(MockDatabase db, int load) 34 | { 35 | _db = db; 36 | _load = load; 37 | } 38 | 39 | public int Load() 40 | { 41 | _db.Counter++; 42 | return _load; 43 | } 44 | } 45 | 46 | public int Counter; 47 | 48 | public override void InsertRange(params KeyValuePair[] items) => Counter++; 49 | 50 | protected override IEnumerable>> Evaluate() 51 | { 52 | for (var i = 0; i < IncrementPerLoop / 2; i++) 53 | { 54 | Counter++; 55 | yield return new KeyValuePair>(i, new LazyLoader(this, i)); 56 | } 57 | } 58 | 59 | public override void Dispose() 60 | { 61 | } 62 | } 63 | 64 | public ThreadLockTests() 65 | { 66 | _mockdb = new MockDatabase(); 67 | _db = _mockdb.WithThreadLock(); 68 | _tasks = new List(); 69 | _lock = new ManualResetEventSlim(); 70 | } 71 | 72 | /// 73 | /// Tests multiple threads inserting into the database. 74 | /// 75 | [Fact] 76 | public async Task MultipleThreadsInsertRange() 77 | { 78 | const int threads = 10_000; 79 | const int insertRanges = 1_000; 80 | 81 | for (var i = 0; i < threads; i++) 82 | { 83 | _tasks.Add(Task.Run(() => 84 | { 85 | _lock.Wait(); 86 | for (var j = 0; j < insertRanges; j++) 87 | { 88 | _db.InsertRange(new KeyValuePair[0]); 89 | } 90 | })); 91 | } 92 | 93 | _lock.Set(); 94 | await Task.WhenAll(_tasks).ConfigureAwait(false); 95 | 96 | _mockdb.Counter 97 | .Should() 98 | .Be(threads * insertRanges); 99 | } 100 | 101 | /// 102 | /// Tests if multiple threads looping over it causes it to fail 103 | /// 104 | [Fact] 105 | public async Task MultipleThreadsForeachOver() 106 | { 107 | const int threads = 10_000; 108 | 109 | for (var i = 0; i < threads; i++) 110 | { 111 | _tasks.Add(Task.Run(action: () => 112 | { 113 | _lock.Wait(); 114 | Parallel.ForEach(_db, j => j.Value.Load()); 115 | })); 116 | } 117 | 118 | _lock.Set(); 119 | await Task.WhenAll(_tasks).ConfigureAwait(false); 120 | 121 | _mockdb.Counter 122 | .Should() 123 | .Be(threads * IncrementPerLoop); 124 | } 125 | } 126 | } -------------------------------------------------------------------------------- /tests/StringDB.Tests/DatabaseTests/TransformerDatabaseTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | 3 | using StringDB.Databases; 4 | using StringDB.Tests.Mocks; 5 | using StringDB.Transformers; 6 | 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | 10 | using Xunit; 11 | 12 | namespace StringDB.Tests 13 | { 14 | /// 15 | /// Tests for a 16 | /// 17 | public class TransformerDatabaseTests 18 | { 19 | private readonly MemoryDatabase _memoryDatabase; 20 | private readonly MockTransformer _keyTransformer; 21 | private readonly MockTransformer _valueTransformer; 22 | private readonly TransformDatabase _transformDatabase; 23 | private readonly KeyValuePair[] _values; 24 | private readonly KeyValuePair[] _expectValues; 25 | 26 | public TransformerDatabaseTests() 27 | { 28 | _memoryDatabase = new MemoryDatabase(); 29 | _keyTransformer = new MockTransformer(); 30 | _valueTransformer = new MockTransformer(); 31 | 32 | _transformDatabase = new TransformDatabase 33 | ( 34 | db: _memoryDatabase, 35 | keyTransformer: _keyTransformer.Reverse(), 36 | valueTransformer: _valueTransformer 37 | ); 38 | 39 | _values = new[] 40 | { 41 | KeyValuePair.Create(1, "a"), 42 | KeyValuePair.Create(2, "b"), 43 | KeyValuePair.Create(3, "c"), 44 | }; 45 | 46 | _expectValues = _values 47 | .Select 48 | ( 49 | x => KeyValuePair.Create 50 | ( 51 | _keyTransformer.TransformPre(x.Key), 52 | _valueTransformer.TransformPost(x.Value) 53 | ) 54 | ) 55 | .ToArray(); 56 | } 57 | 58 | /// 59 | /// Ensures that when we insert a range of entries, it transforms the entries back. 60 | /// 61 | [Fact] 62 | public void InsertRange() 63 | { 64 | _transformDatabase.InsertRange(_values); 65 | 66 | // now the memory db should have the transformed values in them 67 | _memoryDatabase.EnumerateAggressively(3) 68 | .Should() 69 | .BeEquivalentTo(_expectValues, "Inserting values into a transform database should insert the transformed values into the underlying database."); 70 | } 71 | 72 | /// 73 | /// Enumerates over the transform database - tests integration between the memdb & tdb 74 | /// 75 | [Fact] 76 | public void Enumerate() 77 | { 78 | _transformDatabase.InsertRange(_values); 79 | 80 | // make sure enumeration is fine 81 | _transformDatabase.EnumerateAggressively(3) 82 | .Should() 83 | .BeEquivalentTo(_values); 84 | } 85 | } 86 | } -------------------------------------------------------------------------------- /tests/StringDB.Tests/DatabaseTests/WriteOnlyDatabaseTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | 3 | using Moq; 4 | 5 | using StringDB.Fluency; 6 | 7 | using System; 8 | using System.Collections; 9 | using System.Collections.Generic; 10 | 11 | using Xunit; 12 | 13 | namespace StringDB.Tests.DatabaseTests 14 | { 15 | public class WriteOnlyDatabaseTests 16 | { 17 | private readonly Mock> _mock; 18 | private readonly IDatabase _wdb; 19 | private readonly IDatabaseLayer _dbLayer; 20 | 21 | public WriteOnlyDatabaseTests() 22 | { 23 | _mock = new Mock>(); 24 | 25 | _wdb = _mock.Object.AsWriteOnly(); 26 | _dbLayer = (IDatabaseLayer)_wdb; 27 | } 28 | 29 | [Fact] 30 | public void DisposesInner_IfToldTo() 31 | { 32 | using (var db = _mock.Object.AsWriteOnly(true)) 33 | { 34 | } 35 | 36 | _mock.Verify(x => x.Dispose()); 37 | } 38 | 39 | [Fact] 40 | public void DoesntDisposesInner_IfToldNotTo() 41 | { 42 | using (var db = _mock.Object.AsWriteOnly(false)) 43 | { 44 | } 45 | 46 | _mock.Verify(x => x.Dispose(), Times.Never()); 47 | } 48 | 49 | [Fact] 50 | public void InnerDatabase_IsDatabase() 51 | { 52 | _dbLayer.InnerDatabase.As() 53 | .Should().Be(_mock.Object); 54 | } 55 | 56 | [Fact] 57 | public void Get_ShouldThrow() 58 | => Throws(() => _wdb.Get(0)); 59 | 60 | [Fact] 61 | public void TryGet_ShouldThrow() 62 | => Throws(() => _wdb.TryGet(0, out _)); 63 | 64 | [Fact] 65 | public void GetAll_ShouldThrow() 66 | => Throws(() => _wdb.GetAll(0)); 67 | 68 | [Fact] 69 | public void Enumerating_ShouldThrow() 70 | { 71 | Throws(() => _wdb.GetEnumerator()); 72 | Throws(() => ((IEnumerable)_wdb).GetEnumerator()); 73 | } 74 | 75 | [Fact] 76 | public void Insert_ShouldCall() 77 | { 78 | _wdb.Insert(0, 0); 79 | 80 | _mock.Verify(x => x.Insert(0, 0)); 81 | } 82 | 83 | [Fact] 84 | public void InsertRange_ShouldCall() 85 | { 86 | _wdb.InsertRange(new KeyValuePair(0, 0)); 87 | 88 | _mock.Verify(x => x.InsertRange(It.IsAny[]>())); 89 | } 90 | 91 | private void Throws(Action action) 92 | => action.Should().Throw(); 93 | } 94 | } -------------------------------------------------------------------------------- /tests/StringDB.Tests/FluencyTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | 3 | using StringDB.Databases; 4 | using StringDB.Fluency; 5 | using StringDB.IO; 6 | using StringDB.IO.Compatibility; 7 | using StringDB.Tests.Mocks; 8 | using StringDB.Transformers; 9 | 10 | using System; 11 | using System.IO; 12 | 13 | using Xunit; 14 | 15 | namespace StringDB.Tests 16 | { 17 | /* 18 | * 19 | * These aren't meant to say "ah ha yes i can expect .Method() 20 | * to give me a MethodDatabase every time!" they're just here 21 | * to hit that sweet :100: unit testing percent 22 | * 23 | */ 24 | 25 | public class FluencyTests 26 | { 27 | [Fact] 28 | public void ReturnsMemoryDatabase() 29 | { 30 | var db = new DatabaseBuilder() 31 | .UseMemoryDatabase(); 32 | 33 | db 34 | .Should() 35 | .BeOfType>(); 36 | } 37 | 38 | [Fact] 39 | public void ReturnsIODatabase() 40 | { 41 | var db = new DatabaseBuilder() 42 | .UseIODatabase(new StoneVaultIODevice(new MemoryStream())); 43 | 44 | db 45 | .Should() 46 | .BeOfType(); 47 | } 48 | 49 | [Fact] 50 | public void ReturnsIODatabase_WhenUsingBuilderPattern() 51 | { 52 | var db = new DatabaseBuilder() 53 | .UseIODatabase(builder => builder.UseStoneVault(new MemoryStream())); 54 | 55 | db 56 | .Should() 57 | .BeOfType(); 58 | } 59 | 60 | [Fact] 61 | public void ReturnsTransformDatabase() 62 | { 63 | var db = new DatabaseBuilder() 64 | .UseMemoryDatabase() 65 | .WithTransform(new MockTransformer().Reverse(), new MockTransformer()); 66 | 67 | db.Should() 68 | .BeOfType>(); 69 | } 70 | 71 | [Fact] 72 | public void ReturnsThreadLockDatabase() 73 | { 74 | var db = new DatabaseBuilder() 75 | .UseMemoryDatabase() 76 | .WithThreadLock(); 77 | 78 | db.Should() 79 | .BeOfType>(); 80 | } 81 | 82 | [Fact] 83 | public void ReturnsCacheDatabase() 84 | { 85 | var db = new DatabaseBuilder() 86 | .UseMemoryDatabase() 87 | .WithCache(); 88 | 89 | db.Should() 90 | .BeOfType>(); 91 | } 92 | 93 | [Fact] 94 | public void CreatesStringDB5_0_0LowlevelDatabaseIODevice() 95 | { 96 | var dbiod = new DatabaseIODeviceBuilder() 97 | .UseStringDB(StringDBVersion.v5_0_0, new MemoryStream()); 98 | 99 | dbiod 100 | .Should() 101 | .BeOfType(); 102 | } 103 | 104 | [Fact] 105 | public void CreatesStringDB10_0_0LowlevelDatabaseIODevice() 106 | { 107 | var dbiod = new DatabaseIODeviceBuilder() 108 | .UseStringDB(StringDBVersion.v10_0_0, new MemoryStream()); 109 | 110 | dbiod 111 | .Should() 112 | .BeOfType(); 113 | 114 | ((DatabaseIODevice)dbiod) 115 | .LowLevelDatabaseIODevice 116 | .Should() 117 | .BeOfType(); 118 | } 119 | 120 | [Fact] 121 | public void LatestIsJust10() 122 | { 123 | var dbiod = new DatabaseIODeviceBuilder() 124 | .UseStringDB(StringDBVersion.Latest, new MemoryStream()); 125 | 126 | dbiod 127 | .Should() 128 | .BeOfType(); 129 | 130 | ((DatabaseIODevice)dbiod) 131 | .LowLevelDatabaseIODevice 132 | .Should() 133 | .BeOfType(); 134 | } 135 | 136 | [Fact] 137 | public void ThrowsOnInvalidStringDBCreation() 138 | { 139 | Action throws = () => new DatabaseIODeviceBuilder() 140 | .UseStringDB((StringDBVersion)1337, new MemoryStream()); 141 | 142 | throws.Should() 143 | .ThrowExactly(); 144 | } 145 | 146 | [Fact] 147 | public void CreatesStoneVaultIODevice() 148 | { 149 | var dbiod = new DatabaseIODeviceBuilder() 150 | .UseStoneVault(new MemoryStream()); 151 | 152 | dbiod 153 | .Should() 154 | .BeOfType(); 155 | } 156 | 157 | [Fact] 158 | public void UseIODatabaseWithVersionAndFile() 159 | { 160 | // verify that the DBIODevice was created correctly 161 | 162 | var db = new DatabaseBuilder() 163 | .UseIODatabase(StringDBVersion.v10_0_0, "test1.db"); 164 | 165 | db 166 | .Should() 167 | .BeOfType(); 168 | 169 | var iodb = db as IODatabase; 170 | 171 | iodb.DatabaseIODevice 172 | .Should() 173 | .BeOfType(); 174 | 175 | var dbiod = iodb.DatabaseIODevice as DatabaseIODevice; 176 | 177 | dbiod.LowLevelDatabaseIODevice 178 | .Should() 179 | .BeOfType(); 180 | } 181 | 182 | [Fact] 183 | public void UseStringDB() 184 | { 185 | // just make sure that down the chain we initialized a file stream 186 | using (var idbiod = new DatabaseIODeviceBuilder() 187 | .UseStringDB(StringDBVersion.v10_0_0, "test2.db")) 188 | { 189 | idbiod 190 | .Should() 191 | .BeOfType(); 192 | 193 | var dbiod = idbiod as DatabaseIODevice; 194 | 195 | dbiod.LowLevelDatabaseIODevice 196 | .Should() 197 | .BeOfType(); 198 | 199 | var ldbiod = dbiod.LowLevelDatabaseIODevice as StringDB10_0_0LowlevelDatabaseIODevice; 200 | 201 | ldbiod.InnerStream 202 | .Should() 203 | .BeOfType(); 204 | 205 | var scm = ldbiod.InnerStream as StreamCacheMonitor; 206 | 207 | scm.InnerStream 208 | .Should() 209 | .BeOfType(); 210 | 211 | var fs = scm.InnerStream as FileStream; 212 | 213 | fs.CanRead.Should().BeTrue(); 214 | fs.CanWrite.Should().BeTrue(); 215 | } 216 | } 217 | 218 | [Fact] 219 | public void UseKeyTransform() 220 | { 221 | using var memdb = new DatabaseBuilder() 222 | .UseMemoryDatabase(); 223 | 224 | using var transform = memdb.WithKeyTransform(IntToStringTransformer.Default); 225 | 226 | transform.Should().BeOfType>(); 227 | } 228 | 229 | [Fact] 230 | public void UseValueTransform() 231 | { 232 | using var memdb = new DatabaseBuilder() 233 | .UseMemoryDatabase(); 234 | 235 | using var transform = memdb.WithValueTransform(IntToStringTransformer.Default); 236 | 237 | transform.Should().BeOfType>(); 238 | } 239 | 240 | [Fact] 241 | public void MakeReadOnly() 242 | { 243 | using var memdb = new DatabaseBuilder() 244 | .UseMemoryDatabase(); 245 | 246 | var readOnly = memdb.AsReadOnly(); 247 | 248 | readOnly.Should().BeOfType>(); 249 | } 250 | 251 | [Fact] 252 | public void MakeWriteOnly() 253 | { 254 | using var memdb = new DatabaseBuilder() 255 | .UseMemoryDatabase(); 256 | 257 | var readOnly = memdb.AsWriteOnly(); 258 | 259 | readOnly.Should().BeOfType>(); 260 | } 261 | 262 | [Fact] 263 | public void WithBufferedDatabase() 264 | { 265 | using var memdb = new DatabaseBuilder() 266 | .UseMemoryDatabase(); 267 | 268 | var buffered = memdb.WithBuffer(); 269 | 270 | buffered.Should().BeOfType>(); 271 | } 272 | } 273 | } -------------------------------------------------------------------------------- /tests/StringDB.Tests/GlobalSuppressions.cs: -------------------------------------------------------------------------------- 1 | // This file is used by Code Analysis to maintain SuppressMessage 2 | // attributes that are applied to this project. 3 | // Project-level suppressions either have no target or are given 4 | // a specific target and scoped to a namespace, type, member, etc. 5 | 6 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("General", "RCS1079:Throwing of new NotImplementedException.", Justification = "Mock class", Scope = "member", Target = "~M:StringDB.Tests.MockBaseDatabase.Dispose")] 7 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("General", "RCS1079:Throwing of new NotImplementedException.", Justification = "Mock class", Scope = "member", Target = "~M:StringDB.Tests.MockDatabase.Dispose")] -------------------------------------------------------------------------------- /tests/StringDB.Tests/IMockDatabase.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace StringDB.Tests 4 | { 5 | public interface IMockDatabase 6 | { 7 | List>> Data { get; } 8 | } 9 | } -------------------------------------------------------------------------------- /tests/StringDB.Tests/IntegrationTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | 7 | using Xunit; 8 | 9 | namespace StringDB.Tests 10 | { 11 | /// 12 | /// Tests the integration of all DB components 13 | /// 14 | public class IntegrationTests : IDisposable 15 | { 16 | private readonly MemoryStream _ms; 17 | private readonly Func> _openDb; 18 | private readonly Action> _insert; 19 | 20 | public IntegrationTests() 21 | { 22 | _ms = new MemoryStream(); 23 | 24 | _openDb = () => 25 | { 26 | _ms.Position = 0; 27 | return StringDatabase.Create(_ms, true); 28 | }; 29 | 30 | _insert = db => 31 | { 32 | db.Insert("a", "d"); 33 | db.Insert("b", "e"); 34 | db.Insert("c", "f"); 35 | }; 36 | } 37 | 38 | public void Dispose() => _ms.Dispose(); 39 | 40 | /// 41 | /// Assures that inserting and enumeration work properly. 42 | /// 43 | [Fact] 44 | public void IntegrationTest() 45 | { 46 | using (var db = _openDb()) 47 | { 48 | _insert(db); 49 | } 50 | 51 | using (var db = _openDb()) 52 | { 53 | db.EnumerateAggressively(3) 54 | .Should().BeEquivalentTo(new[] 55 | { 56 | new KeyValuePair("a", "d"), 57 | new KeyValuePair("b", "e"), 58 | new KeyValuePair("c", "f"), 59 | }); 60 | } 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /tests/StringDB.Tests/LazyItem.cs: -------------------------------------------------------------------------------- 1 | namespace StringDB.Tests 2 | { 3 | public class LazyItem : ILazyLoader 4 | { 5 | public LazyItem(T value) => Value = value; 6 | 7 | public T Value { get; } 8 | 9 | public bool Loaded { get; private set; } 10 | 11 | public T Load() 12 | { 13 | Loaded = true; 14 | return Value; 15 | } 16 | 17 | public override string ToString() => $"[{Value}: {Loaded}]"; 18 | } 19 | } -------------------------------------------------------------------------------- /tests/StringDB.Tests/LazyLoaderAwaiterTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | 3 | using System.Threading.Tasks; 4 | 5 | using Xunit; 6 | 7 | namespace StringDB.Tests 8 | { 9 | /// 10 | /// Tests for a . 11 | /// 12 | public class LazyLoaderAwaiterTests 13 | { 14 | public const int Value = 7; 15 | private readonly LazyLoaderMock _mock; 16 | private readonly LazyLoaderAwaiter _awaiter; 17 | 18 | public class LazyLoaderMock : ILazyLoader 19 | { 20 | private readonly int _value; 21 | public int Loads { get; set; } 22 | 23 | public LazyLoaderMock(int value = Value) => _value = value; 24 | 25 | public int Load() 26 | { 27 | Loads++; 28 | return _value; 29 | } 30 | } 31 | 32 | public LazyLoaderAwaiterTests() 33 | { 34 | _mock = new LazyLoaderMock(); 35 | _awaiter = new LazyLoaderAwaiter 36 | { 37 | LazyLoader = _mock 38 | }; 39 | } 40 | 41 | /// 42 | /// Tests that the await keyword has the same functionality as Load 43 | /// 44 | /// 45 | [Fact] 46 | public async Task AwaitLazyLoader() 47 | { 48 | var value = await _mock; 49 | 50 | value.Should().Be(Value); 51 | _mock.Loads.Should().Be(1); 52 | } 53 | 54 | [Fact] 55 | public void LazyLoaderAwaiter_LoadsALazyLoader() 56 | { 57 | _mock.Loads.Should().Be(0); 58 | _awaiter.IsCompleted.Should().BeFalse(); 59 | 60 | _awaiter.GetResult().Should().Be(Value); 61 | 62 | _mock.Loads.Should().Be(1); 63 | _awaiter.IsCompleted.Should().BeTrue(); 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /tests/StringDB.Tests/Mocks/IntToStringTransformer.cs: -------------------------------------------------------------------------------- 1 | namespace StringDB.Tests.Mocks 2 | { 3 | public class IntToStringTransformer : ITransformer 4 | { 5 | public static IntToStringTransformer Default { get; } = new IntToStringTransformer(); 6 | 7 | public int TransformPost(string post) => int.Parse(post); 8 | 9 | public string TransformPre(int pre) => pre.ToString(); 10 | } 11 | } -------------------------------------------------------------------------------- /tests/StringDB.Tests/Mocks/MockBaseDatabase.cs: -------------------------------------------------------------------------------- 1 | using StringDB.Databases; 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | 7 | namespace StringDB.Tests.Mocks 8 | { 9 | public class MockBaseDatabase : BaseDatabase, IMockDatabase 10 | { 11 | public List>> Data { get; } = new MockDatabase().Data; 12 | 13 | public KeyValuePair[] Inserted { get; set; } 14 | 15 | public override void InsertRange(params KeyValuePair[] items) => Inserted = items; 16 | 17 | protected override IEnumerable>> Evaluate() 18 | => Data.Select(x => new KeyValuePair>(x.Key, x.Value)); 19 | 20 | public IEnumerable>> Enumerator() => Evaluate(); 21 | 22 | public override void Dispose() => throw new NotImplementedException(); 23 | } 24 | } -------------------------------------------------------------------------------- /tests/StringDB.Tests/Mocks/MockDatabase.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace StringDB.Tests.Mocks 6 | { 7 | public class MockDatabase : IDatabase, IMockDatabase 8 | { 9 | public List>> Data { get; } = 10 | new List>> 11 | ( 12 | collection: new KeyValuePair[] 13 | { 14 | // starts at 0 :^) 15 | // TAKE NOTE PROGRAMMERS! 16 | new KeyValuePair("a", 0 ), 17 | new KeyValuePair("b", 1 ), 18 | new KeyValuePair("c", 2 ), 19 | new KeyValuePair("d", 3 ), 20 | new KeyValuePair("a", 4 ), 21 | new KeyValuePair("b", 5 ), 22 | new KeyValuePair("c", 6 ), 23 | new KeyValuePair("d", 7 ), 24 | new KeyValuePair("a", 8 ), 25 | new KeyValuePair("b", 9 ) 26 | } 27 | .Select(x => new KeyValuePair> 28 | ( 29 | key: x.Key, 30 | value: new LazyItem(x.Value) 31 | ) 32 | ) 33 | ); 34 | 35 | public IEnumerable>> Evaluate() 36 | { 37 | foreach (var item in Data) 38 | { 39 | yield return new KeyValuePair>(item.Key, item.Value); 40 | } 41 | } 42 | 43 | public int Get(string key) => Data.First(x => x.Key == key).Value.Load(); 44 | 45 | public IEnumerable> GetAll(string key) => 46 | Data.Where(x => x.Key == key).Select(x => x.Value); 47 | 48 | public IEnumerator>> GetEnumerator() => Evaluate().GetEnumerator(); 49 | 50 | public void Insert(string key, int value) => Data.Add(new KeyValuePair>(key, new LazyItem(value))); 51 | 52 | public void InsertRange(params KeyValuePair[] items) 53 | { 54 | foreach (var item in items) 55 | { 56 | Insert(item.Key, item.Value); 57 | } 58 | } 59 | 60 | public bool TryGet(string key, out int value) 61 | { 62 | foreach (var item in Data) 63 | { 64 | if (item.Key == key) 65 | { 66 | value = item.Value.Load(); 67 | return true; 68 | } 69 | } 70 | 71 | value = default; 72 | return false; 73 | } 74 | 75 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); 76 | 77 | public void Dispose() => throw new System.NotImplementedException(); 78 | } 79 | } -------------------------------------------------------------------------------- /tests/StringDB.Tests/Mocks/MockDatabaseExtensions.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | 3 | using System.Linq; 4 | 5 | namespace StringDB.Tests.Mocks 6 | { 7 | public static class MockDatabaseExtensions 8 | { 9 | public static void EnsureNoValuesLoaded(this IMockDatabase mdb) 10 | => mdb.Data.Where(x => x.Value.Loaded) 11 | .Should() 12 | .BeEmpty("No lazy values should be loaded with this operation"); 13 | 14 | public static void EnsureAllValuesLoaded(this IMockDatabase mdb) 15 | => mdb.Data.Where(kvp => !kvp.Value.Loaded) 16 | .Should() 17 | .BeEmpty("All values should be loaded"); 18 | 19 | public static void EnsureNoValuesLoadedBeyond(this IMockDatabase mdb, int position) 20 | => mdb.Data.Where((kvp, index) => index <= position ? !kvp.Value.Loaded : kvp.Value.Loaded) 21 | .Should() 22 | .BeEmpty("No values beyond the current enumeration point should be loaded"); 23 | } 24 | } -------------------------------------------------------------------------------- /tests/StringDB.Tests/Mocks/MockDatabaseIODevice.cs: -------------------------------------------------------------------------------- 1 | using StringDB.IO; 2 | 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | 7 | namespace StringDB.Tests.Mocks 8 | { 9 | public class MockDatabaseIODevice : IDatabaseIODevice, IMockDatabase 10 | { 11 | public List>> Data { get; } = new List>() 12 | { 13 | { new KeyValuePair("a", "b") }, 14 | { new KeyValuePair("c", "d") }, 15 | { new KeyValuePair("e", "e") }, 16 | { new KeyValuePair("e", "g") }, 17 | { new KeyValuePair("g", "g") }, 18 | } 19 | .Select(x => 20 | { 21 | return new KeyValuePair> 22 | ( 23 | key: Encoding.UTF8.GetBytes(x.Key), 24 | value: new LazyItem(Encoding.UTF8.GetBytes(x.Value)) 25 | ); 26 | }) 27 | .ToList(); 28 | 29 | public IOptimalTokenSource OptimalTokenSource => throw new System.NotImplementedException(); 30 | 31 | public int ItemOn = -1; 32 | 33 | public void Reset() => ItemOn = 0; 34 | 35 | public KeyValuePair[] Inserted; 36 | 37 | public void Insert(KeyValuePair[] items) => Inserted = items; 38 | 39 | public DatabaseItem ReadNext() 40 | { 41 | var current = ItemOn++; 42 | 43 | return new DatabaseItem 44 | { 45 | EndOfItems = ItemOn == Data.Count, 46 | Key = Data[current].Key, 47 | DataPosition = current 48 | }; 49 | } 50 | 51 | public long RequestedRead = 0; 52 | 53 | public byte[] ReadValue(long position) 54 | { 55 | RequestedRead = position; 56 | 57 | return Data[(int)position].Value.Load(); 58 | } 59 | 60 | public bool Disposed; 61 | 62 | public void Dispose() => Disposed = true; 63 | } 64 | } -------------------------------------------------------------------------------- /tests/StringDB.Tests/Mocks/MockTransformer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace StringDB.Tests.Mocks 4 | { 5 | public class MockTransformer : ITransformer 6 | { 7 | public int PreTransform { get; private set; } 8 | 9 | public string TransformPre(int pre) 10 | { 11 | PreTransform = pre; 12 | return Convert.ToChar(pre).ToString(); 13 | } 14 | 15 | public string PostTransform { get; private set; } 16 | 17 | public int TransformPost(string post) 18 | { 19 | PostTransform = post; 20 | return Convert.ToInt32(Convert.ToChar(post)); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /tests/StringDB.Tests/NotThoroughTests/5_0_0.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | 3 | using StringDB.Fluency; 4 | using StringDB.IO; 5 | using StringDB.Transformers; 6 | 7 | using System.Collections.Generic; 8 | using System.IO; 9 | 10 | using Xunit; 11 | 12 | namespace StringDB.Tests.NotThoroughTests 13 | { 14 | public class _5_0_0 15 | { 16 | // TODO: remove the manual testing labor component of this 17 | // currently you should load a project w/ stringdb 5.0.0 and check if it works 18 | // just make sure it reads it fine and inserting it is fine 19 | 20 | [Fact] 21 | public void WorksIGuess() 22 | { 23 | // using (var fs = File.Open("copy.db", FileMode.OpenOrCreate)) 24 | using (var ms = new MemoryStream()) 25 | { 26 | using (var db = new DatabaseBuilder() 27 | .UseIODatabase(builder => builder.UseStringDB(StringDBVersion.v5_0_0, ms, true)) 28 | .WithTransform(StringTransformer.Default, StringTransformer.Default)) 29 | { 30 | db.Insert("test", "value"); 31 | db.InsertRange(new KeyValuePair[] 32 | { 33 | new KeyValuePair("a,", "c,"), 34 | new KeyValuePair("b,", "d,"), 35 | }); 36 | 37 | db.EnumerateAggressively(2) 38 | .Should() 39 | .BeEquivalentTo 40 | ( 41 | new KeyValuePair[] 42 | { 43 | new KeyValuePair("test", "value"), 44 | new KeyValuePair("a,", "c,"), 45 | new KeyValuePair("b,", "d,"), 46 | } 47 | ); 48 | } 49 | 50 | ms.Seek(0, SeekOrigin.Begin); 51 | // ms.CopyTo(fs); 52 | } 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /tests/StringDB.Tests/OptimalTokenTests/DatabaseIODeviceOptimalTokenTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | 3 | using StringDB.Fluency; 4 | using StringDB.IO; 5 | using StringDB.IO.Compatibility; 6 | using StringDB.Transformers; 7 | 8 | using System.Collections.Generic; 9 | using System.IO; 10 | 11 | using Xunit; 12 | 13 | namespace StringDB.Tests 14 | { 15 | /// 16 | /// Tests that ensure the optimal token gets called when it needs to be called. 17 | /// 18 | public class DatabaseIODeviceOptimalTokenTests 19 | { 20 | private readonly OptimalTokenSource _token; 21 | private readonly MemoryStream _ms; 22 | private readonly StringDB10_0_0LowlevelDatabaseIODevice _lldbiod; 23 | private readonly DatabaseIODevice _dbiod; 24 | 25 | public DatabaseIODeviceOptimalTokenTests() 26 | { 27 | _token = new OptimalTokenSource(); 28 | _ms = new MemoryStream(); 29 | 30 | // setup a db 31 | using (var _db = new DatabaseBuilder() 32 | .UseIODatabase(builder => builder.UseStringDB(StringDBVersion.v10_0_0, _ms, true)) 33 | .WithTransform(StringTransformer.Default, StringTransformer.Default)) 34 | { 35 | _db.InsertRange(KeyValuePair.Create("one key", "one value"), KeyValuePair.Create("two key", "two value")); 36 | _db.Insert("key", "value"); 37 | _db.Insert("another key", "another value"); 38 | } 39 | 40 | _ms.Position = 0; 41 | 42 | _lldbiod = new StringDB10_0_0LowlevelDatabaseIODevice(_ms, NoByteBuffer.Read, false); 43 | _dbiod = new DatabaseIODevice(_lldbiod, _token); 44 | } 45 | 46 | [Fact] 47 | public void TriggersOptimalTokenOnJump() 48 | { 49 | FalseToken(); 50 | FalseToken(); 51 | TrueToken(); 52 | TrueToken(); 53 | 54 | void FalseToken() 55 | { 56 | _dbiod.ReadNext(); 57 | _token.OptimalReadingTime.Should().BeFalse(); 58 | } 59 | 60 | void TrueToken() 61 | { 62 | _dbiod.ReadNext(); 63 | _token.OptimalReadingTime.Should().BeTrue(); 64 | } 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /tests/StringDB.Tests/OptimalTokenTests/OptimalEnumerationTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | 3 | using StringDB.Databases; 4 | using StringDB.IO; 5 | using StringDB.LazyLoaders; 6 | 7 | using System; 8 | using System.Collections.Generic; 9 | 10 | using Xunit; 11 | 12 | namespace StringDB.Tests 13 | { 14 | /// 15 | /// Tests to verify that the optimal enumeration method is working. 16 | /// 17 | public class OptimalEnumerationTests 18 | { 19 | public class MockDb : BaseDatabase 20 | { 21 | private readonly int _value; 22 | 23 | public MockDb(int value) 24 | { 25 | _value = value; 26 | } 27 | 28 | public OptimalTokenSource OptimalToken { get; } = new OptimalTokenSource(); 29 | public bool EvaluateNext { get; set; } = true; 30 | 31 | public override void Dispose() 32 | { 33 | } 34 | 35 | public override void InsertRange(params KeyValuePair[] items) => throw new NotImplementedException(); 36 | 37 | protected override IEnumerable>> Evaluate() 38 | { 39 | for (var i = 1; i <= _value; i++) 40 | { 41 | // we're evaluating 42 | EvaluateNext = true; 43 | OptimalToken.SetOptimalReadingTime(false); 44 | 45 | // we've hit 10, claim this to be the optimal time to stop reading 46 | if (i % 10 == 0) 47 | { 48 | // we set this to false, we shouldn't be evaluating any more 49 | EvaluateNext = false; 50 | OptimalToken.SetOptimalReadingTime(true); 51 | } 52 | 53 | // return the kvp 54 | yield return new ValueLoader(i).ToKeyValuePair(i); 55 | } 56 | } 57 | } 58 | 59 | /// 60 | /// Every 10, it should enumerate optimally 61 | /// 62 | [Fact] 63 | public void OptimallyEnumeratesEveryTen() 64 | { 65 | var db = new MockDb(100); 66 | 67 | foreach (var (key, value) in db.EnumerateOptimally(db.OptimalToken)) 68 | { 69 | db.EvaluateNext.Should().BeFalse(); 70 | } 71 | } 72 | 73 | /// 74 | /// We never hit the optimal condition, so we should enumerate under an evaluated condition. 75 | /// 76 | [Fact] 77 | public void IfNotOptimalEnumeratesAnyway() 78 | { 79 | var db = new MockDb(9); 80 | 81 | foreach (var (key, value) in db.EnumerateOptimally(db.OptimalToken)) 82 | { 83 | db.EvaluateNext.Should().BeTrue(); 84 | } 85 | } 86 | } 87 | } -------------------------------------------------------------------------------- /tests/StringDB.Tests/OptimalTokenTests/OptimalTokenTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | 3 | using StringDB.IO; 4 | 5 | using Xunit; 6 | 7 | namespace StringDB.Tests 8 | { 9 | /// 10 | /// Tests for an 11 | /// 12 | public class OptimalTokenTests 13 | { 14 | /// 15 | /// Test that we can set the token to an on and off state. 16 | /// 17 | [Fact] 18 | public void SettingTokenStateWorks() 19 | { 20 | IOptimalTokenSource optimalToken = new OptimalTokenSource(); 21 | 22 | optimalToken.OptimalToken.OptimalReadingTime.Should().Be(false); 23 | 24 | SetRead(true); 25 | SetRead(false); 26 | 27 | void SetRead(bool value) 28 | { 29 | optimalToken.SetOptimalReadingTime(value); 30 | optimalToken.OptimalToken.OptimalReadingTime.Should().Be(value); 31 | } 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /tests/StringDB.Tests/StoneVaultIODeviceTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | 3 | using StringDB.IO; 4 | 5 | using System.Collections.Generic; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Text; 9 | 10 | using Xunit; 11 | 12 | namespace StringDB.Tests 13 | { 14 | /// 15 | /// Tests that a StoneVaultIODevice writes the correct bytes. 16 | /// 17 | public class StoneVaultIODeviceTests 18 | { 19 | private readonly MemoryStream _ms; 20 | private readonly StoneVaultIODevice _sviod; 21 | 22 | public StoneVaultIODeviceTests() 23 | { 24 | _ms = new MemoryStream(); 25 | _sviod = new StoneVaultIODevice(_ms); 26 | } 27 | 28 | /// 29 | /// Tests that inserts work. 30 | /// 31 | [Fact] 32 | public void InsertWorks() 33 | { 34 | // insert key/value 35 | _sviod.Insert(new KeyValuePair[] 36 | { 37 | new KeyValuePair(Encoding.UTF8.GetBytes("key"), Encoding.UTF8.GetBytes("value")) 38 | }); 39 | 40 | // tests file format 41 | _ms.ToArray() 42 | .Should() 43 | .BeEquivalentTo(TestKeyValue().Concat(DataBad())); 44 | 45 | _sviod.Insert(new KeyValuePair[] 46 | { 47 | new KeyValuePair(Encoding.UTF8.GetBytes("a"), Encoding.UTF8.GetBytes("a")) 48 | }); 49 | 50 | _ms.ToArray() 51 | .Should() 52 | .BeEquivalentTo(TestKeyValue().Concat(TestAA()).Concat(DataBad())); 53 | 54 | static IEnumerable TestKeyValue() 55 | { 56 | return new byte[] 57 | { 58 | 0x00, // DATA_GOOD 59 | 3, 0, 0, 0, 0, 0, 0, 0, // 3 60 | } 61 | .Concat(Encoding.UTF8.GetBytes("key")) 62 | .Concat(new byte[] 63 | { 64 | 0x00, // DATA_GOOD 65 | 5, 0, 0, 0, 0, 0, 0, 0, // 5 66 | }) 67 | .Concat(Encoding.UTF8.GetBytes("value")); 68 | } 69 | 70 | static IEnumerable TestAA() 71 | { 72 | return new byte[] 73 | { 74 | 0x00, // DATA_GOOD 75 | 1, 0, 0, 0, 0, 0, 0, 0, // 1 76 | } 77 | .Concat(Encoding.UTF8.GetBytes("a")) 78 | .Concat(new byte[] 79 | { 80 | 0x00, // DATA_GOOD 81 | 1, 0, 0, 0, 0, 0, 0, 0, // 1 82 | }) 83 | .Concat(Encoding.UTF8.GetBytes("a")); 84 | } 85 | 86 | static IEnumerable DataBad() 87 | { 88 | return new byte[] 89 | { 90 | 0xFF // DATA_BAD 91 | }; 92 | } 93 | } 94 | 95 | /// 96 | /// Tests that reading works. 97 | /// 98 | [Fact] 99 | public void CanRead() 100 | { 101 | // insert two entries 102 | _sviod.Insert(new KeyValuePair[] 103 | { 104 | new KeyValuePair(Bytes("test"), Bytes("value")), 105 | new KeyValuePair(Bytes("test2"), Bytes("value2")), 106 | }); 107 | 108 | // reset the head 109 | _sviod.Reset(); 110 | 111 | // read some keys 112 | var keys = new[] 113 | { 114 | _sviod.ReadNext(), 115 | _sviod.ReadNext(), 116 | _sviod.ReadNext(), 117 | }; 118 | 119 | foreach (var key in keys.Take(2)) 120 | { 121 | key.EndOfItems.Should().BeFalse(); 122 | } 123 | 124 | // TODO: use hat ^ 125 | keys[keys.Length - 1].EndOfItems.Should().BeTrue(); 126 | 127 | // now test reading key and value 128 | keys[0].Key.Should().BeEquivalentTo(Bytes("test")); 129 | keys[1].Key.Should().BeEquivalentTo(Bytes("test2")); 130 | 131 | _sviod.ReadValue(keys[0].DataPosition) 132 | .Should().BeEquivalentTo(Bytes("value")); 133 | 134 | _sviod.ReadValue(keys[1].DataPosition) 135 | .Should().BeEquivalentTo(Bytes("value2")); 136 | 137 | _sviod.Reset(); 138 | 139 | // ensure that the stream seeks back to where we were reading when we read a value 140 | var pos = _sviod.ReadNext().DataPosition; 141 | 142 | // when we read a value, the head should go to the value at 2 now 143 | _sviod.ReadValue(pos); 144 | 145 | _sviod.ReadNext() 146 | .Should().BeEquivalentTo(keys[1]); 147 | 148 | static byte[] Bytes(string str) => Encoding.UTF8.GetBytes(str); 149 | } 150 | } 151 | } -------------------------------------------------------------------------------- /tests/StringDB.Tests/StringDB.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.2 5 | 6 | false 7 | 8 | preview 9 | 10 | 11 | 12 | 13 | all 14 | runtime; build; native; contentfiles; analyzers 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | all 23 | runtime; build; native; contentfiles; analyzers 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /tests/StringDB.Tests/TransformerTests/NoneTransformerTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | 3 | using StringDB.Transformers; 4 | 5 | using Xunit; 6 | 7 | namespace StringDB.Tests 8 | { 9 | /// 10 | /// Tests for a 11 | /// 12 | public class NoneTransformerTests 13 | { 14 | [Fact] 15 | public void DoesLiterallyNothing() 16 | { 17 | var a = new NoneTransformer(); 18 | 19 | a.TransformPre(7) 20 | .Should().Be(7); 21 | 22 | a.TransformPost(7) 23 | .Should().Be(7); 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /tests/StringDB.Tests/TransformerTests/ReverseTransformerTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | 3 | using StringDB.Transformers; 4 | 5 | using System; 6 | 7 | using Xunit; 8 | 9 | namespace StringDB.Tests 10 | { 11 | /// 12 | /// Tests for a . 13 | /// 14 | public class ReverseTransformerTests 15 | { 16 | // class setup 17 | public class A 18 | { 19 | public int Value { get; set; } 20 | } 21 | 22 | public class B 23 | { 24 | public int OtherValue { get; set; } 25 | } 26 | 27 | public class SimpleTransformer : ITransformer 28 | { 29 | public B TransformPre(A pre) => new B { OtherValue = pre.Value }; 30 | 31 | public A TransformPost(B post) => new A { Value = post.OtherValue }; 32 | } 33 | 34 | /// 35 | /// Tests that the reverse transformer performs the exact same transformations 36 | /// as a regular transformer, but reverses the position of stuff. 37 | /// 38 | [Fact] 39 | public void Reverses() 40 | { 41 | const string because = "The transformation should call the appropriate method in the underlying transformer"; 42 | 43 | var reverse = new ReverseTransformer(new SimpleTransformer()); 44 | 45 | // in SimpleTransformer this is TransformPost 46 | reverse.TransformPre(new B { OtherValue = 3 }) 47 | .Value 48 | .Should() 49 | .Be(3, because); 50 | 51 | // in SimpleTransformer this is TransformPre 52 | reverse.TransformPost(new A { Value = 4 }) 53 | .OtherValue 54 | .Should() 55 | .Be(4, because); 56 | } 57 | 58 | /// 59 | /// Test that the Reverse call reverses a transformer. 60 | /// 61 | [Fact] 62 | public void ExtensionWorks() 63 | { 64 | new SimpleTransformer() 65 | .Reverse() 66 | .Should() 67 | .BeOfType>("A reversed SimpleTransformer"); 68 | } 69 | 70 | /// 71 | /// When reversing a transformer twice, the original transformer should be returned. 72 | /// 73 | [Fact] 74 | public void TwoReversesShouldReturnOriginalTransformer() 75 | { 76 | new SimpleTransformer() 77 | .Reverse() 78 | .Reverse() 79 | .Should() 80 | .BeOfType("Reversing a transformer twice should result in the original transformer."); 81 | } 82 | } 83 | } -------------------------------------------------------------------------------- /tests/StringDB.Tests/TransformerTests/StringTransformerTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | 3 | using StringDB.Transformers; 4 | 5 | using System.Text; 6 | 7 | using Xunit; 8 | 9 | namespace StringDB.Tests 10 | { 11 | /// 12 | /// tests. 13 | /// 14 | public class StringTransformerTests 15 | { 16 | private readonly StringTransformer _transformer; 17 | private readonly string _testingString; 18 | private readonly byte[] _testingBytes; 19 | 20 | public StringTransformerTests() 21 | { 22 | _transformer = new StringTransformer(); 23 | 24 | _testingString = "test"; 25 | _testingBytes = Encoding.UTF8.GetBytes(_testingString); 26 | } 27 | 28 | /// 29 | /// The TransformPost method should transform the string into the correct bytes. 30 | /// 31 | [Fact] 32 | public void TransformPostTest() 33 | => _transformer.TransformPost(_testingString) 34 | .Should() 35 | .BeEquivalentTo(_testingBytes); 36 | 37 | /// 38 | /// The TransformPre method should transform the bytes into the correct string. 39 | /// 40 | [Fact] 41 | public void TransformPreTest() 42 | => _transformer.TransformPre(_testingBytes) 43 | .Should() 44 | .BeEquivalentTo(_testingString); 45 | } 46 | } --------------------------------------------------------------------------------