├── .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 |

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