├── .gitignore ├── CODE-OF-CONDUCT.md ├── DataAccessPerformance.sln ├── LICENSE.TXT ├── NuGet.config ├── README.md ├── experiments └── Peregrine │ ├── Peregrine.Tests │ ├── AdoTests.cs │ ├── DB.sql │ ├── PGSessionTests.cs │ └── Peregrine.Tests.csproj │ └── Peregrine │ ├── Ado │ ├── PeregrineCommand.cs │ ├── PeregrineConnection.cs │ ├── PeregrineDataReader.cs │ └── PeregrineFactory.cs │ ├── AwaitableSocket.cs │ ├── Hashing.cs │ ├── PG.cs │ ├── PGSession.cs │ ├── PGSessionPool.cs │ ├── Peregrine.csproj │ ├── ReadBuffer.cs │ └── WriteBuffer.cs └── src └── BenchmarkDb ├── AdoDriver.cs ├── BenchmarkDb.csproj ├── DriverBase.cs ├── FirebirdClientAdoDriver.cs ├── Fortune.cs ├── MySqlAdoDriver.cs ├── NpgsqlAdoDriver.cs ├── PeregrineAdoDriver.cs ├── PeregrineDriver.cs ├── Program.cs ├── Variation.cs ├── aspnet-Benchmarks.db ├── firebird-fortune.sql └── firebird-full.sql /.gitignore: -------------------------------------------------------------------------------- 1 | # Custom 2 | .build/ 3 | .vscode/ 4 | 5 | ## Ignore Visual Studio temporary files, build results, and 6 | ## files generated by popular Visual Studio add-ons. 7 | ## 8 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 9 | 10 | # User-specific files 11 | *.suo 12 | *.user 13 | *.userosscache 14 | *.sln.docstates 15 | 16 | # User-specific files (MonoDevelop/Xamarin Studio) 17 | *.userprefs 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | bld/ 27 | [Bb]in/ 28 | [Oo]bj/ 29 | [Ll]og/ 30 | 31 | # Visual Studio 2015 cache/options directory 32 | .vs/ 33 | # Uncomment if you have tasks that create the project's static files in wwwroot 34 | #wwwroot/ 35 | 36 | # MSTest test Results 37 | [Tt]est[Rr]esult*/ 38 | [Bb]uild[Ll]og.* 39 | 40 | # NUNIT 41 | *.VisualState.xml 42 | TestResult.xml 43 | 44 | # Build Results of an ATL Project 45 | [Dd]ebugPS/ 46 | [Rr]eleasePS/ 47 | dlldata.c 48 | 49 | # .NET Core 50 | global.json 51 | project.lock.json 52 | project.fragment.lock.json 53 | artifacts/ 54 | **/Properties/launchSettings.json 55 | 56 | *_i.c 57 | *_p.c 58 | *_i.h 59 | *.ilk 60 | *.meta 61 | *.obj 62 | *.pch 63 | *.pdb 64 | *.pgc 65 | *.pgd 66 | *.rsp 67 | *.sbr 68 | *.tlb 69 | *.tli 70 | *.tlh 71 | *.tmp 72 | *.tmp_proj 73 | *.log 74 | *.vspscc 75 | *.vssscc 76 | .builds 77 | *.pidb 78 | *.svclog 79 | *.scc 80 | 81 | # Chutzpah Test files 82 | _Chutzpah* 83 | 84 | # Visual C++ cache files 85 | ipch/ 86 | *.aps 87 | *.ncb 88 | *.opendb 89 | *.opensdf 90 | *.sdf 91 | *.cachefile 92 | *.VC.db 93 | *.VC.VC.opendb 94 | 95 | # Visual Studio profiler 96 | *.psess 97 | *.vsp 98 | *.vspx 99 | *.sap 100 | 101 | # TFS 2012 Local Workspace 102 | $tf/ 103 | 104 | # Guidance Automation Toolkit 105 | *.gpState 106 | 107 | # ReSharper is a .NET coding add-in 108 | _ReSharper*/ 109 | *.[Rr]e[Ss]harper 110 | *.DotSettings.user 111 | 112 | # JustCode is a .NET coding add-in 113 | .JustCode 114 | 115 | # TeamCity is a build add-in 116 | _TeamCity* 117 | 118 | # DotCover is a Code Coverage Tool 119 | *.dotCover 120 | 121 | # Visual Studio code coverage results 122 | *.coverage 123 | *.coveragexml 124 | 125 | # NCrunch 126 | _NCrunch_* 127 | .*crunch*.local.xml 128 | nCrunchTemp_* 129 | 130 | # MightyMoose 131 | *.mm.* 132 | AutoTest.Net/ 133 | 134 | # Web workbench (sass) 135 | .sass-cache/ 136 | 137 | # Installshield output folder 138 | [Ee]xpress/ 139 | 140 | # DocProject is a documentation generator add-in 141 | DocProject/buildhelp/ 142 | DocProject/Help/*.HxT 143 | DocProject/Help/*.HxC 144 | DocProject/Help/*.hhc 145 | DocProject/Help/*.hhk 146 | DocProject/Help/*.hhp 147 | DocProject/Help/Html2 148 | DocProject/Help/html 149 | 150 | # Click-Once directory 151 | publish/ 152 | 153 | # Publish Web Output 154 | *.[Pp]ublish.xml 155 | *.azurePubxml 156 | # TODO: Comment the next line if you want to checkin your web deploy settings 157 | # but database connection strings (with potential passwords) will be unencrypted 158 | *.pubxml 159 | *.publishproj 160 | 161 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 162 | # checkin your Azure Web App publish settings, but sensitive information contained 163 | # in these scripts will be unencrypted 164 | PublishScripts/ 165 | 166 | # NuGet Packages 167 | *.nupkg 168 | # The packages folder can be ignored because of Package Restore 169 | **/packages/* 170 | # except build/, which is used as an MSBuild target. 171 | !**/packages/build/ 172 | # Uncomment if necessary however generally it will be regenerated when needed 173 | #!**/packages/repositories.config 174 | # NuGet v3's project.json files produces more ignorable files 175 | *.nuget.props 176 | *.nuget.targets 177 | 178 | # Microsoft Azure Build Output 179 | csx/ 180 | *.build.csdef 181 | 182 | # Microsoft Azure Emulator 183 | ecf/ 184 | rcf/ 185 | 186 | # Windows Store app package directories and files 187 | AppPackages/ 188 | BundleArtifacts/ 189 | Package.StoreAssociation.xml 190 | _pkginfo.txt 191 | 192 | # Visual Studio cache files 193 | # files ending in .cache can be ignored 194 | *.[Cc]ache 195 | # but keep track of directories ending in .cache 196 | !*.[Cc]ache/ 197 | 198 | # Others 199 | ClientBin/ 200 | ~$* 201 | *~ 202 | *.dbmdl 203 | *.dbproj.schemaview 204 | *.jfm 205 | *.pfx 206 | *.publishsettings 207 | orleans.codegen.cs 208 | 209 | # Since there are multiple workflows, uncomment next line to ignore bower_components 210 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 211 | #bower_components/ 212 | 213 | # RIA/Silverlight projects 214 | Generated_Code/ 215 | 216 | # Backup & report files from converting an old project file 217 | # to a newer Visual Studio version. Backup files are not needed, 218 | # because we have git ;-) 219 | _UpgradeReport_Files/ 220 | Backup*/ 221 | UpgradeLog*.XML 222 | UpgradeLog*.htm 223 | 224 | # SQL Server files 225 | *.mdf 226 | *.ldf 227 | 228 | # Business Intelligence projects 229 | *.rdl.data 230 | *.bim.layout 231 | *.bim_*.settings 232 | 233 | # Microsoft Fakes 234 | FakesAssemblies/ 235 | 236 | # GhostDoc plugin setting file 237 | *.GhostDoc.xml 238 | 239 | # Node.js Tools for Visual Studio 240 | .ntvs_analysis.dat 241 | node_modules/ 242 | 243 | # Typescript v1 declaration files 244 | typings/ 245 | 246 | # Visual Studio 6 build log 247 | *.plg 248 | 249 | # Visual Studio 6 workspace options file 250 | *.opt 251 | 252 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 253 | *.vbw 254 | 255 | # Visual Studio LightSwitch build output 256 | **/*.HTMLClient/GeneratedArtifacts 257 | **/*.DesktopClient/GeneratedArtifacts 258 | **/*.DesktopClient/ModelManifest.xml 259 | **/*.Server/GeneratedArtifacts 260 | **/*.Server/ModelManifest.xml 261 | _Pvt_Extensions 262 | 263 | # Paket dependency manager 264 | .paket/paket.exe 265 | paket-files/ 266 | 267 | # FAKE - F# Make 268 | .fake/ 269 | 270 | # JetBrains Rider 271 | .idea/ 272 | *.sln.iml 273 | 274 | # CodeRush 275 | .cr/ 276 | 277 | # Python Tools for Visual Studio (PTVS) 278 | __pycache__/ 279 | *.pyc 280 | 281 | # Cake - Uncomment if you are using it 282 | # tools/** 283 | # !tools/packages.config 284 | korebuild-lock.txt 285 | /src/BenchmarkDb/results.csv 286 | /src/BenchmarkDb/results.md 287 | -------------------------------------------------------------------------------- /CODE-OF-CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | This project has adopted the code of conduct defined by the Contributor Covenant 4 | to clarify expected behavior in our community. 5 | 6 | For more information, see the [.NET Foundation Code of Conduct](https://dotnetfoundation.org/code-of-conduct). 7 | -------------------------------------------------------------------------------- /DataAccessPerformance.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27004.2006 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BenchmarkDb", "src\BenchmarkDb\BenchmarkDb.csproj", "{7FA56803-EFBD-4028-8067-33F9608E7821}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "experiments", "experiments", "{F5A90C0E-9ECD-4338-9749-03B3004C2D02}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Peregrine", "experiments\Peregrine\Peregrine\Peregrine.csproj", "{5D23DC7F-7E20-4E88-86A8-3D822A9BE52C}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Peregrine.Tests", "experiments\Peregrine\Peregrine.Tests\Peregrine.Tests.csproj", "{9BFE1DCB-396C-480A-9827-854E75B41BE0}" 13 | EndProject 14 | Global 15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 16 | Debug|Any CPU = Debug|Any CPU 17 | Release|Any CPU = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 20 | {7FA56803-EFBD-4028-8067-33F9608E7821}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {7FA56803-EFBD-4028-8067-33F9608E7821}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {7FA56803-EFBD-4028-8067-33F9608E7821}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {7FA56803-EFBD-4028-8067-33F9608E7821}.Release|Any CPU.Build.0 = Release|Any CPU 24 | {5D23DC7F-7E20-4E88-86A8-3D822A9BE52C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {5D23DC7F-7E20-4E88-86A8-3D822A9BE52C}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {5D23DC7F-7E20-4E88-86A8-3D822A9BE52C}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {5D23DC7F-7E20-4E88-86A8-3D822A9BE52C}.Release|Any CPU.Build.0 = Release|Any CPU 28 | {9BFE1DCB-396C-480A-9827-854E75B41BE0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {9BFE1DCB-396C-480A-9827-854E75B41BE0}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {9BFE1DCB-396C-480A-9827-854E75B41BE0}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {9BFE1DCB-396C-480A-9827-854E75B41BE0}.Release|Any CPU.Build.0 = Release|Any CPU 32 | EndGlobalSection 33 | GlobalSection(SolutionProperties) = preSolution 34 | HideSolutionNode = FALSE 35 | EndGlobalSection 36 | GlobalSection(NestedProjects) = preSolution 37 | {5D23DC7F-7E20-4E88-86A8-3D822A9BE52C} = {F5A90C0E-9ECD-4338-9749-03B3004C2D02} 38 | {9BFE1DCB-396C-480A-9827-854E75B41BE0} = {F5A90C0E-9ECD-4338-9749-03B3004C2D02} 39 | EndGlobalSection 40 | GlobalSection(ExtensibilityGlobals) = postSolution 41 | SolutionGuid = {29C6A361-AEF7-4B3C-BD61-6225B47CBF91} 42 | EndGlobalSection 43 | EndGlobal 44 | -------------------------------------------------------------------------------- /LICENSE.TXT: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) .NET Foundation and Contributors 4 | 5 | All rights reserved. 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | -------------------------------------------------------------------------------- /NuGet.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # .NET Data Access Performance 2 | The purpose of this repository is to host benchmarks and prototypes, as well as serve as the main forum for an open initiative to greatly improve the performance of storing and retrieving data using popular database systems through common .NET data access APIs. 3 | 4 | ## Background 5 | 6 | While we have achieved great performance gains in other areas, .NET still lags behind other platforms in data access benchmarks. 7 | 8 | As an example, the [TechEmpower Fortunes benchmark](https://www.techempower.com/benchmarks/) shows similar numbers across raw ADO.NET, Dapper and EF Core variations: throughput flatlines or even decreases as concurrent requests increase, which seems to indicate that bottlenecks are preventing efficient hardware utilization. 9 | 10 | The performance of accessing databases using .NET should be much more competitive, and we (several people from Microsoft and the .NET developer community) are going to do something about it. 11 | 12 | ## Goals 13 | 14 | - __Improve our understanding__ of how the performance of data access in .NET is affected by several factors (sensitivity analysis), in order to gain the ability to answer questions like these: 15 | - What prevents .NET from scaling in the TechEmpower Fortunes benchmark as the number of concurrent requests increases? 16 | - Why are EF Core results comparatively worse on a "cloud setup" but almost the same as raw data access on a "physical setup"? 17 | - What can cause the significant differences we observe between different ADO.NET providers (e.g. SqlClient, Npgsql, MySqlConnect)? 18 | - How does our performance on Linux compare to Windows? 19 | - How async compares to sync for different number of concurrent requests? 20 | - How does our scalability improve when adding processors? 21 | 22 | 23 | - __Identify and remove bottlenecks__ so that .NET can be much more competitive on data access performance 24 | 25 | - __Identify additional common scenarios__ that our own benchmarks or TechEmpower could track 26 | 27 | - Come up with __novel ways to increase performance__ as well as learn about __clever things other platforms are doing__ to be more efficient in common scenarios 28 | 29 | ## Notes on methodology 30 | 31 | Here are some general ideas we have discussed: 32 | 33 | - __Everything is on the table right now:__ 34 | 35 | - Although ideally we will find ways to speed up existing APIs (and hence existing application code that consumes those APIs) without changing their contracts, we will not stop there. E.g. if we find any API pattern in ADO.NET which fundamentally prevents writing code that reaches optimal performance, we will try to come up with better patterns. 36 | - We will be taking a deeper look at the networking code in database drivers: Although purely managed code solutions have several advantages, we will try with (and learn from) whatever makes things faster, including native APIs. 37 | - We must assume that we don't know much about what makes a data access APIs really fast (otherwise we wouldn't need to do this work, right?). For this reason we need to keep an open mind when looking at alternative lines of investigation, regardless of whether they have or not been tried before. 38 | 39 | 40 | - __Performance baselines:__ for each type of database we will test, we'll need to identify an API or tool which we can rely on to produce performance results that we can assume equivalent to the theoretical optimal performance. E.g. for PostgreSQL we are using [pgbench](https://www.postgresql.org/docs/devel/static/pgbench.html). We will execute this on any machine/database setup we use, to find a baseline we can compare all other results of the same setup against. 41 | 42 | - __Strive for easily reproducible tests:__ Anyone should be able to clone and run our tests with minimal effort on their own machine setup. We have had good results using Docker for this, as the overhead seems to be usually low and the results still representative. 43 | -------------------------------------------------------------------------------- /experiments/Peregrine/Peregrine.Tests/AdoTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System.Collections.Generic; 5 | using System.Data; 6 | using System.Threading.Tasks; 7 | using Peregrine.Ado; 8 | using Xunit; 9 | 10 | namespace Peregrine.Tests 11 | { 12 | public class AdoTests 13 | { 14 | private const string ConnectionString 15 | = "host=127.0.0.1;database=aspnet5-Benchmarks;username=postgres;password=Password1"; 16 | 17 | [Fact] 18 | public async Task Open_success() 19 | { 20 | using (var connection = new PeregrineConnection { ConnectionString = ConnectionString }) 21 | { 22 | Assert.NotEqual(ConnectionState.Open, connection.State); 23 | 24 | await connection.OpenAsync(); 25 | 26 | Assert.Equal(ConnectionState.Open, connection.State); 27 | } 28 | } 29 | 30 | [Fact] 31 | public async Task Execute_query_no_parameters_success() 32 | { 33 | using (var connection = new PeregrineConnection { ConnectionString = ConnectionString }) 34 | { 35 | await connection.OpenAsync(); 36 | 37 | using (var command = (PeregrineCommand)connection.CreateCommand()) 38 | { 39 | command.CommandText = "select id, message from fortune"; 40 | 41 | await command.PrepareAsync(); 42 | 43 | var fortunes = new List(); 44 | 45 | using (var reader = await command.ExecuteReaderAsync()) 46 | { 47 | while (await reader.ReadAsync()) 48 | { 49 | fortunes.Add( 50 | new Fortune 51 | { 52 | Id = reader.GetInt32(0), 53 | Message = reader.GetString(1) 54 | }); 55 | } 56 | } 57 | 58 | Assert.Equal(12, fortunes.Count); 59 | } 60 | } 61 | } 62 | 63 | public class Fortune 64 | { 65 | public int Id { get; set; } 66 | public string Message { get; set; } 67 | } 68 | 69 | public class World 70 | { 71 | public int Id { get; set; } 72 | public int RandomNumber { get; set; } 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /experiments/Peregrine/Peregrine.Tests/PGSessionTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Diagnostics; 7 | using System.Net.Sockets; 8 | using System.Threading.Tasks; 9 | using Xunit; 10 | 11 | namespace Peregrine.Tests 12 | { 13 | public class PGSessionTests 14 | { 15 | private const string Host = "127.0.0.1"; 16 | private const int Port = 5432; 17 | private const string Database = "aspnet5-Benchmarks"; 18 | private const string User = "postgres"; 19 | private const string Password = "Password1"; 20 | 21 | [Fact] 22 | public async Task Start_success() 23 | { 24 | using (var session = new PGSession(Host, Port, Database, User, Password)) 25 | { 26 | Assert.False(session.IsConnected); 27 | 28 | await session.StartAsync(); 29 | 30 | Assert.True(session.IsConnected); 31 | } 32 | } 33 | 34 | [Fact] 35 | public async Task Start_timeout_on_bad_host() 36 | { 37 | using (var session = new PGSession("1.2.3.4", Port, Database, User, Password)) 38 | { 39 | await Assert.ThrowsAsync(() => session.StartAsync()); 40 | } 41 | } 42 | 43 | [Fact] 44 | public async Task Start_fail_on_bad_port() 45 | { 46 | using (var session = new PGSession(Host, 2345, Database, User, Password)) 47 | { 48 | await Assert.ThrowsAnyAsync(() => session.StartAsync()); 49 | } 50 | } 51 | 52 | [Fact] 53 | public async Task Start_fail_bad_user() 54 | { 55 | using (var session = new PGSession(Host, Port, Database, "Bad!", Password)) 56 | { 57 | Assert.Equal( 58 | "password authentication failed for user \"Bad!\"", 59 | (await Assert.ThrowsAsync( 60 | () => session.StartAsync())).Message); 61 | } 62 | } 63 | 64 | [Fact] 65 | public async Task Start_fail_bad_password() 66 | { 67 | using (var session = new PGSession(Host, Port, Database, User, "wrong")) 68 | { 69 | Assert.Equal( 70 | "password authentication failed for user \"postgres\"", 71 | (await Assert.ThrowsAsync( 72 | () => session.StartAsync())).Message); 73 | } 74 | } 75 | 76 | [Fact] 77 | public async Task Dispose_when_open() 78 | { 79 | using (var session = new PGSession(Host, Port, Database, User, Password)) 80 | { 81 | await session.StartAsync(); 82 | 83 | Assert.True(session.IsConnected); 84 | 85 | session.Dispose(); 86 | 87 | Assert.Throws(() => session.IsConnected); 88 | } 89 | } 90 | 91 | [Fact] 92 | public async Task Terminate_when_open_reopen() 93 | { 94 | using (var session = new PGSession(Host, Port, Database, User, Password)) 95 | { 96 | await session.StartAsync(); 97 | 98 | Assert.True(session.IsConnected); 99 | 100 | session.Terminate(); 101 | 102 | Assert.False(session.IsConnected); 103 | 104 | await session.StartAsync(); 105 | 106 | Assert.True(session.IsConnected); 107 | } 108 | } 109 | 110 | [Fact] 111 | public async Task Prepare_success() 112 | { 113 | using (var session = new PGSession(Host, Port, Database, User, Password)) 114 | { 115 | await session.StartAsync(); 116 | 117 | await session.PrepareAsync("_p0", "select id, message from fortune"); 118 | } 119 | } 120 | 121 | [Fact] 122 | public async Task Prepare_failure_invalid_sql() 123 | { 124 | using (var session = new PGSession(Host, Port, Database, User, Password)) 125 | { 126 | await session.StartAsync(); 127 | 128 | Assert.Equal( 129 | "syntax error at or near \"boom\"", 130 | (await Assert.ThrowsAsync( 131 | () => session.PrepareAsync("_p0", "boom!"))).Message); 132 | } 133 | } 134 | 135 | [Fact] 136 | public async Task Execute_query_no_parameters_success() 137 | { 138 | using (var session = new PGSession(Host, Port, Database, User, Password)) 139 | { 140 | await session.StartAsync(); 141 | await session.PrepareAsync("q", "select id, message from fortune"); 142 | 143 | Fortune CreateFortune(List results) 144 | { 145 | var fortune = new Fortune(); 146 | 147 | results.Add(fortune); 148 | 149 | return fortune; 150 | } 151 | 152 | void BindColumn(Fortune fortune, ReadBuffer readBuffer, int index, int length) 153 | { 154 | switch (index) 155 | { 156 | case 0: 157 | fortune.Id = readBuffer.ReadInt(); 158 | break; 159 | case 1: 160 | fortune.Message = readBuffer.ReadString(length); 161 | break; 162 | } 163 | } 164 | 165 | var fortunes = new List(); 166 | 167 | await session.ExecuteAsync("q", fortunes, CreateFortune, BindColumn); 168 | 169 | Assert.Equal(12, fortunes.Count); 170 | } 171 | } 172 | 173 | [Fact] 174 | public async Task Execute_query_parameter_success() 175 | { 176 | using (var session = new PGSession(Host, Port, Database, User, Password)) 177 | { 178 | await session.StartAsync(); 179 | await session.PrepareAsync("q", "select id, randomnumber from world where id = $1"); 180 | 181 | World world = null; 182 | 183 | World CreateWorld() 184 | { 185 | world = new World(); 186 | 187 | return world; 188 | } 189 | 190 | void BindColumn(World w, ReadBuffer readBuffer, int index, int _) 191 | { 192 | switch (index) 193 | { 194 | case 0: 195 | w.Id = readBuffer.ReadInt(); 196 | break; 197 | case 1: 198 | w.RandomNumber = readBuffer.ReadInt(); 199 | break; 200 | } 201 | } 202 | 203 | await session.ExecuteAsync("q", CreateWorld, BindColumn, 45); 204 | 205 | Assert.NotNull(world); 206 | Assert.Equal(45, world.Id); 207 | Assert.InRange(world.RandomNumber, 1, 10000); 208 | } 209 | } 210 | 211 | public class Fortune 212 | { 213 | public int Id { get; set; } 214 | public string Message { get; set; } 215 | } 216 | 217 | public class World 218 | { 219 | public int Id { get; set; } 220 | public int RandomNumber { get; set; } 221 | } 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /experiments/Peregrine/Peregrine.Tests/Peregrine.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | 6 | 7 | 8 | 7.1 9 | 10 | 11 | 12 | 7.1 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /experiments/Peregrine/Peregrine/Ado/PeregrineCommand.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Data; 6 | using System.Data.Common; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | 10 | namespace Peregrine.Ado 11 | { 12 | public class PeregrineCommand : DbCommand 13 | { 14 | private readonly PGSession _session; 15 | private readonly PeregrineDataReader _reader; 16 | private readonly ReadBuffer _readBuffer; 17 | 18 | public PeregrineCommand(PGSession session) 19 | { 20 | _session = session; 21 | _readBuffer = session.ReadBuffer; 22 | _reader = new PeregrineDataReader(session.ReadBuffer); 23 | } 24 | 25 | public Task PrepareAsync() => _session.PrepareAsync("p", CommandText); 26 | 27 | protected override async Task ExecuteDbDataReaderAsync( 28 | CommandBehavior behavior, CancellationToken cancellationToken) 29 | { 30 | await _session.ExecuteAsync("p"); 31 | await _readBuffer.ReceiveAsync(); 32 | 33 | var message = _readBuffer.ReadMessage(); 34 | 35 | switch (message) 36 | { 37 | case MessageType.BindComplete: 38 | return _reader; 39 | 40 | case MessageType.ErrorResponse: 41 | throw new InvalidOperationException(_readBuffer.ReadErrorMessage()); 42 | 43 | default: 44 | throw new NotImplementedException(message.ToString()); 45 | } 46 | } 47 | 48 | public override int ExecuteNonQuery() 49 | => throw new NotImplementedException(); 50 | 51 | public override void Cancel() 52 | => throw new NotImplementedException(); 53 | 54 | public override object ExecuteScalar() 55 | => throw new NotImplementedException(); 56 | 57 | public override void Prepare() 58 | => throw new NotImplementedException(); 59 | 60 | public override string CommandText { get; set; } 61 | 62 | public override int CommandTimeout 63 | { 64 | get => throw new NotImplementedException(); 65 | set => throw new NotImplementedException(); 66 | } 67 | 68 | public override CommandType CommandType 69 | { 70 | get => throw new NotImplementedException(); 71 | set => throw new NotImplementedException(); 72 | } 73 | 74 | protected override DbConnection DbConnection 75 | { 76 | get => throw new NotImplementedException(); 77 | set => throw new NotImplementedException(); 78 | } 79 | 80 | protected override DbParameterCollection DbParameterCollection 81 | => throw new NotImplementedException(); 82 | 83 | protected override DbTransaction DbTransaction 84 | { 85 | get => throw new NotImplementedException(); 86 | set => throw new NotImplementedException(); 87 | } 88 | 89 | public override bool DesignTimeVisible 90 | { 91 | get => throw new NotImplementedException(); 92 | set => throw new NotImplementedException(); 93 | } 94 | 95 | public override UpdateRowSource UpdatedRowSource 96 | { 97 | get => throw new NotImplementedException(); 98 | set => throw new NotImplementedException(); 99 | } 100 | 101 | protected override DbParameter CreateDbParameter() 102 | => throw new NotImplementedException(); 103 | 104 | protected override DbDataReader ExecuteDbDataReader(CommandBehavior behavior) 105 | => throw new NotImplementedException(); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /experiments/Peregrine/Peregrine/Ado/PeregrineConnection.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Data; 6 | using System.Data.Common; 7 | using System.Linq; 8 | using System.Threading; 9 | using System.Threading.Tasks; 10 | 11 | namespace Peregrine.Ado 12 | { 13 | public class PeregrineConnection : DbConnection 14 | { 15 | private sealed class Pool 16 | { 17 | private readonly PGSession[] _sessions; 18 | 19 | public Pool(int maximumRetained) 20 | { 21 | _sessions = new PGSession[maximumRetained]; 22 | } 23 | 24 | public PGSession Rent() 25 | { 26 | for (var i = 0; i < _sessions.Length; i++) 27 | { 28 | var item = _sessions[i]; 29 | 30 | if (item != null 31 | && Interlocked.CompareExchange(ref _sessions[i], null, item) == item) 32 | { 33 | return item; 34 | } 35 | } 36 | 37 | return null; 38 | } 39 | 40 | public void Return(PGSession session) 41 | { 42 | for (var i = 0; i < _sessions.Length; i++) 43 | { 44 | if (Interlocked.CompareExchange(ref _sessions[i], session, null) == null) 45 | { 46 | return; 47 | } 48 | } 49 | 50 | session.Dispose(); 51 | } 52 | } 53 | 54 | private static readonly Pool _pool = new Pool(256); 55 | 56 | private PGSession _session; 57 | 58 | private bool _disposed; 59 | 60 | public override string ConnectionString { get; set; } 61 | 62 | public override ConnectionState State 63 | => _session?.IsConnected == true 64 | ? ConnectionState.Open 65 | : ConnectionState.Closed; 66 | 67 | public override Task OpenAsync(CancellationToken cancellationToken) 68 | { 69 | ThrowIfDisposed(); 70 | 71 | if (_session != null) 72 | { 73 | return Task.CompletedTask; 74 | } 75 | 76 | _session = _pool.Rent(); 77 | 78 | if (_session == null) 79 | { 80 | var parts 81 | = (from s in ConnectionString.Split(';') 82 | let kv = s.Split('=') 83 | select kv) 84 | .ToDictionary(p => p[0], p => p[1]); 85 | 86 | _session = new PGSession( 87 | parts["host"], 88 | port: 5432, 89 | database: parts["database"], 90 | user: parts["username"], 91 | password: parts["password"]); 92 | } 93 | 94 | return _session.IsConnected ? Task.CompletedTask : _session.StartAsync(); 95 | } 96 | 97 | private void ThrowIfDisposed() 98 | { 99 | if (_disposed) 100 | { 101 | throw new ObjectDisposedException(nameof(PeregrineConnection)); 102 | } 103 | } 104 | 105 | protected override void Dispose(bool disposing) 106 | { 107 | if (_session != null) 108 | { 109 | _pool.Return(_session); 110 | } 111 | 112 | _disposed = true; 113 | } 114 | 115 | protected override DbCommand CreateDbCommand() => new PeregrineCommand(_session); 116 | 117 | public override void Close() => throw new NotImplementedException(); 118 | public override string Database => throw new NotImplementedException(); 119 | public override string DataSource => throw new NotImplementedException(); 120 | public override string ServerVersion => throw new NotImplementedException(); 121 | 122 | protected override DbTransaction BeginDbTransaction(IsolationLevel isolationLevel) 123 | => throw new NotImplementedException(); 124 | 125 | public override void ChangeDatabase(string databaseName) 126 | => throw new NotImplementedException(); 127 | 128 | public override void Open() 129 | => throw new NotImplementedException(); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /experiments/Peregrine/Peregrine/Ado/PeregrineDataReader.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Collections; 6 | using System.Data.Common; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | 10 | namespace Peregrine.Ado 11 | { 12 | public class PeregrineDataReader : DbDataReader 13 | { 14 | private static readonly Task _trueResult = Task.FromResult(true); 15 | private static readonly Task _falseResult = Task.FromResult(false); 16 | 17 | private readonly ReadBuffer _readBuffer; 18 | 19 | public PeregrineDataReader(ReadBuffer readBuffer) 20 | => _readBuffer = readBuffer; 21 | 22 | public override Task ReadAsync(CancellationToken cancellationToken) 23 | { 24 | var message = _readBuffer.ReadMessage(); 25 | 26 | switch (message) 27 | { 28 | case MessageType.DataRow: 29 | { 30 | // Column count 31 | _readBuffer.SkipShort(); 32 | 33 | return _trueResult; 34 | } 35 | 36 | case MessageType.CommandComplete: 37 | return _falseResult; 38 | 39 | case MessageType.ErrorResponse: 40 | throw new InvalidOperationException(_readBuffer.ReadErrorMessage()); 41 | 42 | default: 43 | throw new NotImplementedException(message.ToString()); 44 | } 45 | } 46 | 47 | // TODO: ordinal is ignored 48 | 49 | public override int GetInt32(int ordinal) 50 | { 51 | _readBuffer.ReadInt(); 52 | 53 | return _readBuffer.ReadInt(); 54 | } 55 | 56 | public override string GetString(int ordinal) 57 | { 58 | var length = _readBuffer.ReadInt(); 59 | 60 | return _readBuffer.ReadString(length); 61 | } 62 | 63 | public override bool GetBoolean(int ordinal) 64 | { 65 | throw new NotImplementedException(); 66 | } 67 | 68 | public override byte GetByte(int ordinal) 69 | { 70 | throw new NotImplementedException(); 71 | } 72 | 73 | public override long GetBytes(int ordinal, long dataOffset, byte[] buffer, int bufferOffset, int length) 74 | { 75 | throw new NotImplementedException(); 76 | } 77 | 78 | public override char GetChar(int ordinal) 79 | { 80 | throw new NotImplementedException(); 81 | } 82 | 83 | public override long GetChars(int ordinal, long dataOffset, char[] buffer, int bufferOffset, int length) 84 | { 85 | throw new NotImplementedException(); 86 | } 87 | 88 | public override string GetDataTypeName(int ordinal) 89 | { 90 | throw new NotImplementedException(); 91 | } 92 | 93 | public override DateTime GetDateTime(int ordinal) 94 | { 95 | throw new NotImplementedException(); 96 | } 97 | 98 | public override decimal GetDecimal(int ordinal) 99 | { 100 | throw new NotImplementedException(); 101 | } 102 | 103 | public override double GetDouble(int ordinal) 104 | { 105 | throw new NotImplementedException(); 106 | } 107 | 108 | public override Type GetFieldType(int ordinal) 109 | { 110 | throw new NotImplementedException(); 111 | } 112 | 113 | public override float GetFloat(int ordinal) 114 | { 115 | throw new NotImplementedException(); 116 | } 117 | 118 | public override Guid GetGuid(int ordinal) 119 | { 120 | throw new NotImplementedException(); 121 | } 122 | 123 | public override short GetInt16(int ordinal) 124 | { 125 | throw new NotImplementedException(); 126 | } 127 | 128 | public override long GetInt64(int ordinal) 129 | { 130 | throw new NotImplementedException(); 131 | } 132 | 133 | public override string GetName(int ordinal) 134 | { 135 | throw new NotImplementedException(); 136 | } 137 | 138 | public override int GetOrdinal(string name) 139 | { 140 | throw new NotImplementedException(); 141 | } 142 | 143 | public override object GetValue(int ordinal) 144 | { 145 | throw new NotImplementedException(); 146 | } 147 | 148 | public override int GetValues(object[] values) 149 | { 150 | throw new NotImplementedException(); 151 | } 152 | 153 | public override bool IsDBNull(int ordinal) 154 | { 155 | throw new NotImplementedException(); 156 | } 157 | 158 | public override int FieldCount => throw new NotImplementedException(); 159 | 160 | public override object this[int ordinal] => throw new NotImplementedException(); 161 | 162 | public override object this[string name] => throw new NotImplementedException(); 163 | 164 | public override int RecordsAffected => throw new NotImplementedException(); 165 | 166 | public override bool HasRows => throw new NotImplementedException(); 167 | 168 | public override bool IsClosed => throw new NotImplementedException(); 169 | 170 | public override bool NextResult() 171 | { 172 | throw new NotImplementedException(); 173 | } 174 | 175 | public override bool Read() 176 | { 177 | throw new NotImplementedException(); 178 | } 179 | 180 | public override int Depth => throw new NotImplementedException(); 181 | 182 | public override IEnumerator GetEnumerator() 183 | { 184 | throw new NotImplementedException(); 185 | } 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /experiments/Peregrine/Peregrine/Ado/PeregrineFactory.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System.Data.Common; 5 | 6 | namespace Peregrine.Ado 7 | { 8 | public class PeregrineFactory : DbProviderFactory 9 | { 10 | public static readonly PeregrineFactory Instance = new PeregrineFactory(); 11 | 12 | private PeregrineFactory() 13 | { 14 | } 15 | 16 | public override DbConnection CreateConnection() 17 | { 18 | return new PeregrineConnection(); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /experiments/Peregrine/Peregrine/AwaitableSocket.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Net.Sockets; 6 | using System.Runtime.CompilerServices; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | 10 | namespace Peregrine 11 | { 12 | public sealed class AwaitableSocket : INotifyCompletion, IDisposable 13 | { 14 | private static readonly Action _sentinel = () => { }; 15 | 16 | private readonly SocketAsyncEventArgs _socketAsyncEventArgs; 17 | private readonly Socket _socket; 18 | 19 | private Action _continuation; 20 | 21 | public AwaitableSocket(SocketAsyncEventArgs socketAsyncEventArgs, Socket socket) 22 | { 23 | _socketAsyncEventArgs = socketAsyncEventArgs; 24 | _socket = socket; 25 | 26 | socketAsyncEventArgs.Completed 27 | += (_, __) => 28 | { 29 | var continuation 30 | = _continuation 31 | ?? Interlocked.CompareExchange(ref _continuation, _sentinel, null); 32 | 33 | continuation?.Invoke(); 34 | }; 35 | } 36 | 37 | public bool IsConnected => _socket.Connected; 38 | public int BytesTransferred => _socketAsyncEventArgs.BytesTransferred; 39 | 40 | public void SetBuffer(byte[] buffer, int offset, int count) 41 | { 42 | _socketAsyncEventArgs.SetBuffer(buffer, offset, count); 43 | } 44 | 45 | public void SetBuffer(Memory memory) 46 | => _socketAsyncEventArgs.SetBuffer(memory); 47 | 48 | public AwaitableSocket ConnectAsync(CancellationToken cancellationToken) 49 | { 50 | Reset(); 51 | 52 | if (!_socket.ConnectAsync(_socketAsyncEventArgs)) 53 | { 54 | IsCompleted = true; 55 | } 56 | 57 | cancellationToken.Register(Cancel); 58 | 59 | void Cancel() 60 | { 61 | if (!_socket.Connected) 62 | { 63 | _socket.Dispose(); 64 | } 65 | } 66 | 67 | return this; 68 | } 69 | 70 | public AwaitableSocket ReceiveAsync() 71 | { 72 | Reset(); 73 | 74 | if (!_socket.ReceiveAsync(_socketAsyncEventArgs)) 75 | { 76 | IsCompleted = true; 77 | } 78 | 79 | return this; 80 | } 81 | 82 | public AwaitableSocket SendAsync() 83 | { 84 | Reset(); 85 | 86 | if (!_socket.SendAsync(_socketAsyncEventArgs)) 87 | { 88 | IsCompleted = true; 89 | } 90 | 91 | return this; 92 | } 93 | 94 | private void Reset() 95 | { 96 | IsCompleted = false; 97 | _continuation = null; 98 | } 99 | 100 | public AwaitableSocket GetAwaiter() 101 | { 102 | return this; 103 | } 104 | 105 | public bool IsCompleted { get; private set; } 106 | 107 | public void OnCompleted(Action continuation) 108 | { 109 | if (_continuation == _sentinel 110 | || Interlocked.CompareExchange( 111 | ref _continuation, continuation, null) == _sentinel) 112 | { 113 | Task.Run(continuation); 114 | } 115 | } 116 | 117 | public void GetResult() 118 | { 119 | if (_socketAsyncEventArgs.SocketError != SocketError.Success) 120 | { 121 | throw new SocketException((int)_socketAsyncEventArgs.SocketError); 122 | } 123 | } 124 | 125 | public void Dispose() 126 | { 127 | if (_socket != null) 128 | { 129 | if (_socket.Connected) 130 | { 131 | _socket.Shutdown(SocketShutdown.Both); 132 | _socket.Close(); 133 | } 134 | 135 | _socket.Dispose(); 136 | } 137 | 138 | _socketAsyncEventArgs?.Dispose(); 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /experiments/Peregrine/Peregrine/Hashing.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Security.Cryptography; 6 | using System.Text; 7 | 8 | namespace Peregrine 9 | { 10 | internal static class Hashing 11 | { 12 | public static byte[] CreateMD5(string password, string username, byte[] salt) 13 | { 14 | using (var md5 = MD5.Create()) 15 | { 16 | var passwordBytes = PG.UTF8.GetBytes(password); 17 | var usernameBytes = PG.UTF8.GetBytes(username); 18 | 19 | var buffer = new byte[passwordBytes.Length + usernameBytes.Length]; 20 | 21 | passwordBytes.CopyTo(buffer, 0); 22 | usernameBytes.CopyTo(buffer, passwordBytes.Length); 23 | 24 | var hash = md5.ComputeHash(buffer); 25 | 26 | var stringBuilder = new StringBuilder(); 27 | 28 | for (var i = 0; i < hash.Length; i++) 29 | { 30 | stringBuilder.Append(hash[i].ToString("x2")); 31 | } 32 | 33 | var preHashBytes = PG.UTF8.GetBytes(stringBuilder.ToString()); 34 | 35 | buffer = new byte[preHashBytes.Length + 4]; 36 | 37 | Array.Copy(salt, 0, buffer, preHashBytes.Length, 4); 38 | 39 | preHashBytes.CopyTo(buffer, 0); 40 | 41 | stringBuilder = new StringBuilder("md5"); 42 | 43 | hash = md5.ComputeHash(buffer); 44 | 45 | for (var i = 0; i < hash.Length; i++) 46 | { 47 | stringBuilder.Append(hash[i].ToString("x2")); 48 | } 49 | 50 | var resultString = stringBuilder.ToString(); 51 | var resultBytes = new byte[Encoding.UTF8.GetByteCount(resultString) + 1]; 52 | 53 | Encoding.UTF8.GetBytes(resultString, 0, resultString.Length, resultBytes, 0); 54 | 55 | resultBytes[resultBytes.Length - 1] = 0; 56 | 57 | return resultBytes; 58 | } 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /experiments/Peregrine/Peregrine/PG.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System.Text; 5 | 6 | namespace Peregrine 7 | { 8 | internal static class PG 9 | { 10 | public static readonly UTF8Encoding UTF8 11 | = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true); 12 | } 13 | 14 | internal enum MessageType : byte 15 | { 16 | AuthenticationRequest = (byte)'R', 17 | BackendKeyData = (byte)'K', 18 | BindComplete = (byte)'2', 19 | CloseComplete = (byte)'3', 20 | CommandComplete = (byte)'C', 21 | CopyData = (byte)'d', 22 | CopyDone = (byte)'c', 23 | CopyBothResponse = (byte)'W', 24 | CopyInResponse = (byte)'G', 25 | CopyOutResponse = (byte)'H', 26 | DataRow = (byte)'D', 27 | EmptyQueryResponse = (byte)'I', 28 | ErrorResponse = (byte)'E', 29 | FunctionCall = (byte)'F', 30 | FunctionCallResponse = (byte)'V', 31 | NoData = (byte)'n', 32 | NoticeResponse = (byte)'N', 33 | NotificationResponse = (byte)'A', 34 | ParameterDescription = (byte)'t', 35 | ParameterStatus = (byte)'S', 36 | ParseComplete = (byte)'1', 37 | PasswordPacket = (byte)' ', 38 | PortalSuspended = (byte)'s', 39 | ReadyForQuery = (byte)'Z', 40 | RowDescription = (byte)'T' 41 | } 42 | 43 | internal enum FormatCode : short 44 | { 45 | Text = 0, 46 | Binary = 1 47 | } 48 | 49 | internal enum AuthenticationRequestType 50 | { 51 | AuthenticationOk = 0, 52 | AuthenticationKerberosV4 = 1, 53 | AuthenticationKerberosV5 = 2, 54 | AuthenticationCleartextPassword = 3, 55 | AuthenticationCryptPassword = 4, 56 | AuthenticationMD5Password = 5, 57 | AuthenticationSCMCredential = 6, 58 | AuthenticationGSS = 7, 59 | AuthenticationGSSContinue = 8, 60 | AuthenticationSSPI = 9 61 | } 62 | 63 | internal enum ErrorFieldTypeCode : byte 64 | { 65 | Done = 0, 66 | Severity = (byte)'S', 67 | Code = (byte)'C', 68 | Message = (byte)'M', 69 | Detail = (byte)'D', 70 | Hint = (byte)'H', 71 | Position = (byte)'P', 72 | InternalPosition = (byte)'p', 73 | InternalQuery = (byte)'q', 74 | Where = (byte)'W', 75 | SchemaName = (byte)'s', 76 | TableName = (byte)'t', 77 | ColumnName = (byte)'c', 78 | DataTypeName = (byte)'d', 79 | ConstraintName = (byte)'n', 80 | File = (byte)'F', 81 | Line = (byte)'L', 82 | Routine = (byte)'R' 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /experiments/Peregrine/Peregrine/PGSession.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Net; 7 | using System.Net.Sockets; 8 | using System.Threading; 9 | using System.Threading.Tasks; 10 | 11 | namespace Peregrine 12 | { 13 | public class PGSession : IDisposable 14 | { 15 | private const int DefaultConnectionTimeout = 2000; // ms 16 | 17 | private readonly string _host; 18 | private readonly int _port; 19 | private readonly string _database; 20 | private readonly string _user; 21 | private readonly string _password; 22 | 23 | private WriteBuffer _writeBuffer; 24 | 25 | private AwaitableSocket _awaitableSocket; 26 | 27 | private bool _disposed; 28 | 29 | private readonly HashSet _prepared = new HashSet(); 30 | 31 | public PGSession( 32 | string host, 33 | int port, 34 | string database, 35 | string user, 36 | string password) 37 | { 38 | _host = host; 39 | _port = port; 40 | _database = database; 41 | _user = user; 42 | _password = password; 43 | } 44 | 45 | public bool IsConnected 46 | { 47 | get 48 | { 49 | ThrowIfDisposed(); 50 | 51 | return _awaitableSocket?.IsConnected == true; 52 | } 53 | } 54 | 55 | public Task PrepareAsync(string statementName, string query) 56 | { 57 | ThrowIfDisposed(); 58 | ThrowIfNotConnected(); 59 | 60 | return _prepared.Contains(statementName) 61 | ? Task.CompletedTask 62 | : PrepareSlow(statementName, query); 63 | } 64 | 65 | private async Task PrepareSlow(string statementName, string query) 66 | { 67 | await _writeBuffer 68 | .StartMessage('P') 69 | .WriteString(statementName) 70 | .WriteString(query) 71 | .WriteShort(0) 72 | .EndMessage() 73 | .StartMessage('S') 74 | .EndMessage() 75 | .FlushAsync(); 76 | 77 | await ReadBuffer.ReceiveAsync(); 78 | 79 | var message = ReadBuffer.ReadMessage(); 80 | 81 | switch (message) 82 | { 83 | case MessageType.ParseComplete: 84 | _prepared.Add(statementName); 85 | break; 86 | 87 | case MessageType.ErrorResponse: 88 | throw new InvalidOperationException(ReadBuffer.ReadErrorMessage()); 89 | 90 | default: 91 | throw new NotImplementedException(message.ToString()); 92 | } 93 | } 94 | 95 | public AwaitableSocket ExecuteAsync(string statementName) 96 | { 97 | ThrowIfDisposed(); 98 | ThrowIfNotConnected(); 99 | 100 | WriteExecStart(statementName, parameterCount: 0); 101 | 102 | return WriteExecFinish(); 103 | } 104 | 105 | internal ReadBuffer ReadBuffer { get; private set; } 106 | 107 | public Task ExecuteAsync( 108 | string statementName, 109 | Func resultFactory, 110 | Action columnBinder, 111 | int parameterValue) 112 | { 113 | ThrowIfDisposed(); 114 | ThrowIfNotConnected(); 115 | 116 | WriteExecStart(statementName, parameterCount: 1); 117 | 118 | _writeBuffer 119 | .WriteInt(4) 120 | .WriteInt(parameterValue); 121 | 122 | return WriteExecFinishAndProcess(null, _ => resultFactory(), columnBinder); 123 | } 124 | 125 | public Task ExecuteAsync( 126 | string statementName, 127 | TState initialState, 128 | Func resultFactory, 129 | Action columnBinder) 130 | { 131 | ThrowIfDisposed(); 132 | ThrowIfNotConnected(); 133 | 134 | WriteExecStart(statementName, parameterCount: 0); 135 | 136 | return WriteExecFinishAndProcess(initialState, resultFactory, columnBinder); 137 | } 138 | 139 | private void WriteExecStart(string statementName, short parameterCount) 140 | => _writeBuffer 141 | .StartMessage('B') 142 | .WriteNull() 143 | .WriteString(statementName) 144 | .WriteShort(1) 145 | .WriteShort(1) 146 | .WriteShort(parameterCount); 147 | 148 | private AwaitableSocket WriteExecFinish() 149 | => _writeBuffer 150 | .WriteShort(1) 151 | .WriteShort(1) 152 | .EndMessage() 153 | .StartMessage('E') 154 | .WriteNull() 155 | .WriteInt(0) 156 | .EndMessage() 157 | .StartMessage('S') 158 | .EndMessage() 159 | .FlushAsync(); 160 | 161 | private async Task WriteExecFinishAndProcess( 162 | TState initialState, 163 | Func resultFactory, 164 | Action columnBinder) 165 | { 166 | await WriteExecFinish(); 167 | await ReadBuffer.ReceiveAsync(); 168 | 169 | read: 170 | 171 | var message = ReadBuffer.ReadMessage(); 172 | 173 | switch (message) 174 | { 175 | case MessageType.BindComplete: 176 | goto read; 177 | 178 | case MessageType.DataRow: 179 | { 180 | var result 181 | = resultFactory != null 182 | ? resultFactory(initialState) 183 | : default; 184 | 185 | var columns = ReadBuffer.ReadShort(); 186 | 187 | for (var i = 0; i < columns; i++) 188 | { 189 | var length = ReadBuffer.ReadInt(); 190 | 191 | columnBinder(result, ReadBuffer, i, length); 192 | } 193 | 194 | goto read; 195 | } 196 | 197 | case MessageType.CommandComplete: 198 | return; 199 | 200 | case MessageType.ErrorResponse: 201 | throw new InvalidOperationException(ReadBuffer.ReadErrorMessage()); 202 | 203 | default: 204 | throw new NotImplementedException(message.ToString()); 205 | } 206 | } 207 | 208 | public Task StartAsync(int millisecondsTimeout = DefaultConnectionTimeout) 209 | { 210 | ThrowIfDisposed(); 211 | 212 | return IsConnected 213 | ? Task.CompletedTask 214 | : StartSessionAsync(millisecondsTimeout); 215 | } 216 | 217 | private async Task StartSessionAsync(int millisecondsTimeout) 218 | { 219 | await OpenSocketAsync(millisecondsTimeout); 220 | 221 | _writeBuffer = new WriteBuffer(_awaitableSocket); 222 | ReadBuffer = new ReadBuffer(_awaitableSocket); 223 | 224 | await WriteStartupAsync(); 225 | 226 | await ReadBuffer.ReceiveAsync(); 227 | 228 | read: 229 | 230 | var message = ReadBuffer.ReadMessage(); 231 | 232 | switch (message) 233 | { 234 | case MessageType.AuthenticationRequest: 235 | { 236 | var authenticationRequestType 237 | = (AuthenticationRequestType)ReadBuffer.ReadInt(); 238 | 239 | switch (authenticationRequestType) 240 | { 241 | case AuthenticationRequestType.AuthenticationOk: 242 | { 243 | return; 244 | } 245 | 246 | case AuthenticationRequestType.AuthenticationMD5Password: 247 | { 248 | var salt = ReadBuffer.ReadBytes(4); 249 | var hash = Hashing.CreateMD5(_password, _user, salt); 250 | 251 | await _writeBuffer 252 | .StartMessage('p') 253 | .WriteBytes(hash) 254 | .EndMessage() 255 | .FlushAsync(); 256 | 257 | await ReadBuffer.ReceiveAsync(); 258 | 259 | goto read; 260 | } 261 | 262 | default: 263 | throw new NotImplementedException(authenticationRequestType.ToString()); 264 | } 265 | } 266 | 267 | case MessageType.ErrorResponse: 268 | throw new InvalidOperationException(ReadBuffer.ReadErrorMessage()); 269 | 270 | case MessageType.BackendKeyData: 271 | case MessageType.EmptyQueryResponse: 272 | case MessageType.ParameterStatus: 273 | case MessageType.ReadyForQuery: 274 | throw new NotImplementedException($"Unhandled MessageType '{message}'"); 275 | 276 | default: 277 | throw new InvalidOperationException($"Unexpected MessageType '{message}'"); 278 | } 279 | } 280 | 281 | private async Task OpenSocketAsync(int millisecondsTimeout) 282 | { 283 | var socket 284 | = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp) 285 | { 286 | NoDelay = true 287 | }; 288 | 289 | _awaitableSocket 290 | = new AwaitableSocket( 291 | new SocketAsyncEventArgs 292 | { 293 | RemoteEndPoint = new IPEndPoint(IPAddress.Parse(_host), _port) 294 | }, 295 | socket); 296 | 297 | using (var cts = new CancellationTokenSource()) 298 | { 299 | cts.CancelAfter(millisecondsTimeout); 300 | 301 | await _awaitableSocket.ConnectAsync(cts.Token); 302 | } 303 | } 304 | 305 | private AwaitableSocket WriteStartupAsync() 306 | { 307 | const int protocolVersion3 = 3 << 16; 308 | 309 | var parameters = new(string Name, string Value)[] 310 | { 311 | ("user", _user), 312 | ("client_encoding", "UTF8"), 313 | ("database", _database) 314 | }; 315 | 316 | _writeBuffer 317 | .StartMessage() 318 | .WriteInt(protocolVersion3); 319 | 320 | for (var i = 0; i < parameters.Length; i++) 321 | { 322 | var p = parameters[i]; 323 | 324 | _writeBuffer 325 | .WriteString(p.Name) 326 | .WriteString(p.Value); 327 | } 328 | 329 | return _writeBuffer 330 | .WriteNull() 331 | .EndMessage() 332 | .FlushAsync(); 333 | } 334 | 335 | public void Terminate() 336 | { 337 | ThrowIfDisposed(); 338 | 339 | if (IsConnected) 340 | { 341 | try 342 | { 343 | _writeBuffer 344 | .StartMessage('X') 345 | .EndMessage() 346 | .FlushAsync() 347 | .GetAwaiter() 348 | .GetResult(); 349 | } 350 | catch (SocketException) 351 | { 352 | // Socket may have closed 353 | } 354 | } 355 | 356 | _awaitableSocket?.Dispose(); 357 | } 358 | 359 | public void Dispose() 360 | { 361 | if (!_disposed) 362 | { 363 | Terminate(); 364 | 365 | _disposed = true; 366 | } 367 | } 368 | 369 | private void ThrowIfDisposed() 370 | { 371 | if (_disposed) 372 | { 373 | throw new ObjectDisposedException(nameof(PGSession)); 374 | } 375 | } 376 | 377 | private void ThrowIfNotConnected() 378 | { 379 | if (!IsConnected) 380 | { 381 | throw new InvalidOperationException(); 382 | } 383 | } 384 | } 385 | } 386 | -------------------------------------------------------------------------------- /experiments/Peregrine/Peregrine/PGSessionPool.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | namespace Peregrine 9 | { 10 | public class PGSessionPool : IDisposable 11 | { 12 | private readonly string _host; 13 | private readonly int _port; 14 | private readonly string _database; 15 | private readonly string _user; 16 | private readonly string _password; 17 | 18 | private readonly PGSession[] _sessions; 19 | 20 | public PGSessionPool( 21 | string host, 22 | int port, 23 | string database, 24 | string user, 25 | string password, 26 | int maximumRetained) 27 | { 28 | _host = host; 29 | _port = port; 30 | _database = database; 31 | _user = user; 32 | _password = password; 33 | 34 | _sessions = new PGSession[maximumRetained]; 35 | } 36 | 37 | public Func OnCreate { get; set; } 38 | 39 | public ValueTask Rent() 40 | { 41 | for (var i = 0; i < _sessions.Length; i++) 42 | { 43 | var item = _sessions[i]; 44 | 45 | if (item != null 46 | && Interlocked.CompareExchange(ref _sessions[i], null, item) == item) 47 | { 48 | return new ValueTask(item); 49 | } 50 | } 51 | 52 | return CreateSession(); 53 | } 54 | 55 | private async ValueTask CreateSession() 56 | { 57 | var session = new PGSession(_host, _port, _database, _user, _password); 58 | 59 | await session.StartAsync(); 60 | 61 | if (OnCreate != null) 62 | { 63 | await OnCreate.Invoke(session); 64 | } 65 | 66 | return session; 67 | } 68 | 69 | public void Return(PGSession session) 70 | { 71 | for (var i = 0; i < _sessions.Length; i++) 72 | { 73 | if (Interlocked.CompareExchange(ref _sessions[i], session, null) == null) 74 | { 75 | return; 76 | } 77 | } 78 | 79 | session.Dispose(); 80 | } 81 | 82 | public void Dispose() 83 | { 84 | for (var i = 0; i < _sessions.Length; i++) 85 | { 86 | _sessions[i]?.Dispose(); 87 | } 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /experiments/Peregrine/Peregrine/Peregrine.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | true 6 | 7 | 8 | 9 | latest 10 | 11 | 12 | 13 | 7.1 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /experiments/Peregrine/Peregrine/ReadBuffer.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Buffers.Binary; 6 | 7 | namespace Peregrine 8 | { 9 | public class ReadBuffer 10 | { 11 | private const int DefaultBufferSize = 8192; 12 | 13 | private readonly AwaitableSocket _awaitableSocket; 14 | 15 | private readonly Memory _buffer = new Memory(new byte[DefaultBufferSize]); 16 | 17 | private int _position; 18 | 19 | internal ReadBuffer(AwaitableSocket awaitableSocket) 20 | { 21 | _awaitableSocket = awaitableSocket; 22 | } 23 | 24 | internal MessageType ReadMessage() 25 | { 26 | var messageType = (MessageType)ReadByte(); 27 | 28 | // Skip length 29 | _position += sizeof(int); 30 | 31 | return messageType; 32 | } 33 | 34 | internal (MessageType Type, int Length) ReadMessageWithLength() 35 | { 36 | var messageType = (MessageType)ReadByte(); 37 | var length = ReadInt() - 4; 38 | 39 | return (messageType, length); 40 | } 41 | 42 | internal string ReadErrorMessage() 43 | { 44 | string message = null; 45 | 46 | read: 47 | 48 | var code = (ErrorFieldTypeCode)ReadByte(); 49 | 50 | switch (code) 51 | { 52 | case ErrorFieldTypeCode.Done: 53 | break; 54 | case ErrorFieldTypeCode.Message: 55 | message = ReadNullTerminatedString(); 56 | break; 57 | default: 58 | ReadNullTerminatedString(); 59 | goto read; 60 | } 61 | 62 | return message; 63 | } 64 | 65 | public byte ReadByte() 66 | => _buffer.Span[_position++]; 67 | 68 | public byte[] ReadBytes(int length) 69 | { 70 | var bs = new byte[length]; 71 | var span = _buffer.Span; 72 | 73 | for (var i = 0; i < length; i++) 74 | { 75 | bs[i] = span[_position++]; 76 | } 77 | 78 | return bs; 79 | } 80 | 81 | public void SkipShort() 82 | { 83 | _position += sizeof(short); 84 | } 85 | 86 | public short ReadShort() 87 | { 88 | var result = BinaryPrimitives.ReadInt16BigEndian(_buffer.Span.Slice(_position, 2)); 89 | 90 | _position += sizeof(short); 91 | 92 | return result; 93 | } 94 | 95 | public ushort ReadUShort() 96 | { 97 | var result = BinaryPrimitives.ReadUInt16BigEndian(_buffer.Span.Slice(_position, 2)); 98 | 99 | _position += sizeof(short); 100 | 101 | return result; 102 | } 103 | 104 | public int ReadInt() 105 | { 106 | var result = BinaryPrimitives.ReadInt32BigEndian(_buffer.Span.Slice(_position, 4)); 107 | 108 | _position += sizeof(int); 109 | 110 | return result; 111 | } 112 | 113 | public uint ReadUInt() 114 | { 115 | var result = BinaryPrimitives.ReadUInt32BigEndian(_buffer.Span.Slice(_position, 4)); 116 | 117 | _position += sizeof(int); 118 | 119 | return result; 120 | } 121 | 122 | public string ReadNullTerminatedString() 123 | { 124 | var start = _position; 125 | var span = _buffer.Span; 126 | 127 | while (span[_position++] != 0 128 | && _position < _buffer.Length) 129 | { 130 | } 131 | 132 | var s = PG.UTF8.GetString(span.Slice(start, _position - start - 1)); 133 | 134 | return s; 135 | } 136 | 137 | public string ReadString(int length) 138 | { 139 | var result = PG.UTF8.GetString(_buffer.Span.Slice(_position, length)); 140 | 141 | _position += length; 142 | 143 | return result; 144 | } 145 | 146 | public AwaitableSocket ReceiveAsync() 147 | { 148 | _awaitableSocket.SetBuffer(_buffer); 149 | _position = 0; 150 | 151 | return _awaitableSocket.ReceiveAsync(); 152 | } 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /experiments/Peregrine/Peregrine/WriteBuffer.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | 6 | namespace Peregrine 7 | { 8 | internal class WriteBuffer 9 | { 10 | private const int DefaultBufferSize = 1024; 11 | 12 | private readonly AwaitableSocket _awaitableSocket; 13 | 14 | private readonly byte[] _buffer = new byte[DefaultBufferSize]; 15 | 16 | private int _position; 17 | private int _messageOffset; 18 | 19 | public WriteBuffer(AwaitableSocket awaitableSocket) 20 | { 21 | _awaitableSocket = awaitableSocket; 22 | } 23 | 24 | public WriteBuffer StartMessage(char type) 25 | { 26 | WriteByte((byte)type); 27 | 28 | return StartMessage(); 29 | } 30 | 31 | public WriteBuffer StartMessage() 32 | { 33 | _messageOffset = _position; 34 | _position += sizeof(int); 35 | 36 | return this; 37 | } 38 | 39 | public WriteBuffer EndMessage() 40 | { 41 | WriteInt(_messageOffset, _position - _messageOffset); 42 | 43 | return this; 44 | } 45 | 46 | public WriteBuffer WriteByte(byte b) 47 | { 48 | _buffer[_position++] = b; 49 | 50 | return this; 51 | } 52 | 53 | public WriteBuffer WriteBytes(byte[] bytes) 54 | { 55 | Buffer.BlockCopy(bytes, 0, _buffer, _position, bytes.Length); 56 | 57 | _position += bytes.Length; 58 | 59 | return this; 60 | } 61 | 62 | public WriteBuffer WriteNull() => WriteByte(0); 63 | 64 | public WriteBuffer WriteShort(short s) 65 | { 66 | _buffer[_position] = (byte)(s >> 8); 67 | _buffer[_position + 1] = (byte)s; 68 | 69 | _position += sizeof(short); 70 | 71 | return this; 72 | } 73 | 74 | public WriteBuffer WriteInt(int i) 75 | { 76 | WriteInt(_position, i); 77 | 78 | _position += sizeof(int); 79 | 80 | return this; 81 | } 82 | 83 | private void WriteInt(int position, int i) 84 | { 85 | _buffer[position] = (byte)(i >> 24); 86 | _buffer[position + 1] = (byte)(i >> 16); 87 | _buffer[position + 2] = (byte)(i >> 8); 88 | _buffer[position + 3] = (byte)i; 89 | } 90 | 91 | public WriteBuffer WriteString(string s) 92 | { 93 | _position += PG.UTF8.GetBytes(s, 0, s.Length, _buffer, _position); 94 | 95 | return WriteNull(); 96 | } 97 | 98 | public AwaitableSocket FlushAsync() 99 | { 100 | _awaitableSocket.SetBuffer(_buffer, 0, _position); 101 | _position = 0; 102 | 103 | return _awaitableSocket.SendAsync(); 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/BenchmarkDb/AdoDriver.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System.Collections.Generic; 5 | using System.Data.Common; 6 | using System.Threading.Tasks; 7 | 8 | namespace BenchmarkDb 9 | { 10 | public class AdoDriver : DriverBase 11 | { 12 | protected readonly DbProviderFactory _providerFactory; 13 | protected string _connectionString; 14 | 15 | public AdoDriver(DbProviderFactory providerFactory) 16 | { 17 | _providerFactory = providerFactory; 18 | } 19 | 20 | public override void Initialize(string connectionString, int _) 21 | { 22 | _connectionString = connectionString; 23 | } 24 | 25 | public override Task DoWorkSync() 26 | { 27 | while (Program.IsRunning) 28 | { 29 | var results = new List(); 30 | 31 | using (var connection = _providerFactory.CreateConnection()) 32 | { 33 | connection.ConnectionString = _connectionString; 34 | connection.Open(); 35 | 36 | using (var command = connection.CreateCommand()) 37 | { 38 | command.CommandText = Program.TestQuery; 39 | command.Prepare(); 40 | 41 | using (var reader = command.ExecuteReader()) 42 | { 43 | while (reader.Read()) 44 | { 45 | results.Add( 46 | new Fortune 47 | { 48 | Id = reader.GetInt32(0), 49 | Message = reader.GetString(1) 50 | }); 51 | } 52 | } 53 | } 54 | } 55 | 56 | CheckResults(results); 57 | 58 | Program.IncrementCounter(); 59 | } 60 | 61 | return Task.CompletedTask; 62 | } 63 | 64 | public override Task DoWorkSyncCaching() 65 | { 66 | using (var connection = _providerFactory.CreateConnection()) 67 | { 68 | connection.ConnectionString = _connectionString; 69 | connection.Open(); 70 | 71 | using (var command = connection.CreateCommand()) 72 | { 73 | command.CommandText = Program.TestQuery; 74 | command.Prepare(); 75 | 76 | while (Program.IsRunning) 77 | { 78 | var results = new List(); 79 | 80 | using (var reader = command.ExecuteReader()) 81 | { 82 | while (reader.Read()) 83 | { 84 | results.Add( 85 | new Fortune 86 | { 87 | Id = reader.GetInt32(0), 88 | Message = reader.GetString(1) 89 | }); 90 | } 91 | } 92 | 93 | CheckResults(results); 94 | 95 | Program.IncrementCounter(); 96 | } 97 | } 98 | } 99 | 100 | return Task.CompletedTask; 101 | } 102 | 103 | public override async Task DoWorkAsync() 104 | { 105 | while (Program.IsRunning) 106 | { 107 | var results = new List(); 108 | 109 | using (var connection = _providerFactory.CreateConnection()) 110 | { 111 | connection.ConnectionString = _connectionString; 112 | 113 | await connection.OpenAsync(); 114 | 115 | using (var command = connection.CreateCommand()) 116 | { 117 | command.CommandText = Program.TestQuery; 118 | command.Prepare(); 119 | 120 | using (var reader = await command.ExecuteReaderAsync()) 121 | { 122 | while (await reader.ReadAsync()) 123 | { 124 | results.Add( 125 | new Fortune 126 | { 127 | Id = reader.GetInt32(0), 128 | Message = reader.GetString(1) 129 | }); 130 | } 131 | } 132 | } 133 | } 134 | 135 | CheckResults(results); 136 | 137 | Program.IncrementCounter(); 138 | } 139 | } 140 | 141 | public override async Task DoWorkAsyncCaching() 142 | { 143 | using (var connection = _providerFactory.CreateConnection()) 144 | { 145 | connection.ConnectionString = _connectionString; 146 | 147 | await connection.OpenAsync(); 148 | 149 | using (var command = connection.CreateCommand()) 150 | { 151 | command.CommandText = Program.TestQuery; 152 | command.Prepare(); 153 | 154 | while (Program.IsRunning) 155 | { 156 | var results = new List(); 157 | 158 | using (var reader = await command.ExecuteReaderAsync()) 159 | { 160 | while (await reader.ReadAsync()) 161 | { 162 | results.Add( 163 | new Fortune 164 | { 165 | Id = reader.GetInt32(0), 166 | Message = reader.GetString(1) 167 | }); 168 | } 169 | } 170 | 171 | CheckResults(results); 172 | 173 | Program.IncrementCounter(); 174 | } 175 | } 176 | } 177 | } 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /src/BenchmarkDb/BenchmarkDb.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp2.1 6 | latest 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | None 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/BenchmarkDb/DriverBase.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Threading.Tasks; 7 | 8 | namespace BenchmarkDb 9 | { 10 | public abstract class DriverBase 11 | { 12 | public static Func NotSupportedVariation = () => null; 13 | 14 | protected static void CheckResults(ICollection results) 15 | { 16 | if (results.Count != 12) 17 | { 18 | throw new InvalidOperationException($"Unexpected number of results! Expected 12 got {results.Count}"); 19 | } 20 | } 21 | 22 | public virtual Func TryGetVariation(string variationName) 23 | { 24 | switch (variationName) 25 | { 26 | case Variation.Sync: 27 | return DoWorkSync; 28 | case Variation.SyncCaching: 29 | return DoWorkSyncCaching; 30 | case Variation.Async: 31 | return DoWorkAsync; 32 | case Variation.AsyncCaching: 33 | return DoWorkAsyncCaching; 34 | } 35 | 36 | return default; 37 | } 38 | 39 | public abstract void Initialize(string connectionString, int threadCount); 40 | 41 | public virtual Task DoWorkSync() 42 | { 43 | throw VariationNotSupported(Variation.Sync); 44 | } 45 | 46 | public virtual Task DoWorkSyncCaching() 47 | { 48 | throw VariationNotSupported(Variation.SyncCaching); 49 | } 50 | 51 | public virtual Task DoWorkAsync() 52 | { 53 | throw VariationNotSupported(Variation.Async); 54 | } 55 | 56 | public virtual Task DoWorkAsyncCaching() 57 | { 58 | throw VariationNotSupported(Variation.AsyncCaching); 59 | } 60 | 61 | private static Exception VariationNotSupported(string variationName) 62 | => new NotSupportedException($"Variation {variationName} not supported on driver."); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/BenchmarkDb/FirebirdClientAdoDriver.cs: -------------------------------------------------------------------------------- 1 | // Author: Jiri Cincura (jiri@cincura.net) 2 | 3 | using System; 4 | using System.Data.Common; 5 | using FirebirdSql.Data.FirebirdClient; 6 | 7 | namespace BenchmarkDb 8 | { 9 | public sealed class FirebirdClientAdoDriver : AdoDriver 10 | { 11 | public FirebirdClientAdoDriver() 12 | : base(FirebirdClientFactory.Instance) 13 | { } 14 | 15 | public override void Initialize(string connectionString, int _) 16 | { 17 | var settings = new FbConnectionStringBuilder(connectionString); 18 | 19 | if (settings.Enlist) 20 | throw new ArgumentException($"Enlist=false must be specified for {nameof(FirebirdSql.Data.FirebirdClient)}."); 21 | 22 | base.Initialize(connectionString, _); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/BenchmarkDb/Fortune.cs: -------------------------------------------------------------------------------- 1 | namespace BenchmarkDb 2 | { 3 | public class Fortune 4 | { 5 | public int Id { get; set; } 6 | public string Message { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/BenchmarkDb/MySqlAdoDriver.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Data.Common; 6 | using MySql.Data.MySqlClient; 7 | 8 | namespace BenchmarkDb 9 | { 10 | public sealed class MySqlAdoDriver : AdoDriver 11 | { 12 | public MySqlAdoDriver() : base(MySqlClientFactory.Instance) {} 13 | 14 | public override void Initialize(string connectionString, int threadCount) 15 | { 16 | var settings = new MySqlConnectionStringBuilder(connectionString); 17 | 18 | var message = ""; 19 | if (settings.ConnectionIdlePingTime < 300) 20 | message += "* ConnectionIdlePingTime=300 (or higher)\n"; 21 | if (settings.ConnectionReset) 22 | message += "* ConnectionReset=false\n"; 23 | if (settings.DefaultCommandTimeout != 0) 24 | message += "* DefaultCommandTimeout=0\n"; 25 | if (settings.SslMode != MySqlSslMode.None) 26 | message += "* SslMode=None\n"; 27 | if (message.Length != 0) 28 | throw new ArgumentException("ado-mysql requires the following connection string settings:\n" + message); 29 | 30 | base.Initialize(connectionString, threadCount); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/BenchmarkDb/NpgsqlAdoDriver.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Data.Common; 6 | using Npgsql; 7 | 8 | namespace BenchmarkDb 9 | { 10 | public sealed class NpgsqlAdoDriver : AdoDriver 11 | { 12 | public NpgsqlAdoDriver() : base(NpgsqlFactory.Instance) {} 13 | 14 | public override void Initialize(string connectionString, int _) 15 | { 16 | var settings = new NpgsqlConnectionStringBuilder(connectionString); 17 | 18 | if (!settings.NoResetOnClose) 19 | throw new ArgumentException("No Reset On Close=true must be specified for Npgsql"); 20 | if (settings.Enlist) 21 | throw new ArgumentException("Enlist=false must be specified for Npgsql"); 22 | 23 | _connectionString = connectionString; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/BenchmarkDb/PeregrineAdoDriver.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System.Collections.Generic; 5 | using System.Data.Common; 6 | using System.Threading.Tasks; 7 | using Peregrine.Ado; 8 | 9 | namespace BenchmarkDb 10 | { 11 | public sealed class PeregrineAdoDriver : AdoDriver 12 | { 13 | public PeregrineAdoDriver(DbProviderFactory providerFactory) 14 | : base(providerFactory) 15 | { 16 | } 17 | 18 | public override async Task DoWorkAsync() 19 | { 20 | while (Program.IsRunning) 21 | { 22 | var results = new List(); 23 | 24 | using (var connection = _providerFactory.CreateConnection()) 25 | { 26 | connection.ConnectionString = _connectionString; 27 | 28 | await connection.OpenAsync(); 29 | 30 | using (var command = (PeregrineCommand)connection.CreateCommand()) 31 | { 32 | command.CommandText = Program.TestQuery; 33 | 34 | await command.PrepareAsync(); 35 | 36 | using (var reader = await command.ExecuteReaderAsync()) 37 | { 38 | while (await reader.ReadAsync()) 39 | { 40 | results.Add( 41 | new Fortune 42 | { 43 | Id = reader.GetInt32(0), 44 | Message = reader.GetString(1) 45 | }); 46 | } 47 | } 48 | } 49 | } 50 | 51 | CheckResults(results); 52 | 53 | Program.IncrementCounter(); 54 | } 55 | } 56 | 57 | public override async Task DoWorkAsyncCaching() 58 | { 59 | using (var connection = _providerFactory.CreateConnection()) 60 | { 61 | connection.ConnectionString = _connectionString; 62 | 63 | await connection.OpenAsync(); 64 | 65 | using (var command = (PeregrineCommand)connection.CreateCommand()) 66 | { 67 | command.CommandText = Program.TestQuery; 68 | 69 | await command.PrepareAsync(); 70 | 71 | while (Program.IsRunning) 72 | { 73 | var results = new List(); 74 | 75 | using (var reader = await command.ExecuteReaderAsync()) 76 | { 77 | while (await reader.ReadAsync()) 78 | { 79 | results.Add( 80 | new Fortune 81 | { 82 | Id = reader.GetInt32(0), 83 | Message = reader.GetString(1) 84 | }); 85 | } 86 | } 87 | 88 | CheckResults(results); 89 | 90 | Program.IncrementCounter(); 91 | } 92 | } 93 | } 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/BenchmarkDb/PeregrineDriver.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Threading.Tasks; 7 | using Npgsql; 8 | using Peregrine; 9 | 10 | namespace BenchmarkDb 11 | { 12 | public sealed class PeregrineDriver : DriverBase, IDisposable 13 | { 14 | private PGSessionPool _sessionPool; 15 | 16 | public override Func TryGetVariation(string variationName) 17 | { 18 | switch (variationName) 19 | { 20 | case Variation.Async: 21 | return DoWorkAsync; 22 | case Variation.AsyncCaching: 23 | return DoWorkAsyncCaching; 24 | default: 25 | return NotSupportedVariation; 26 | } 27 | } 28 | 29 | public override void Initialize(string connectionString, int threadCount) 30 | { 31 | var connectionStringBuilder 32 | = new NpgsqlConnectionStringBuilder(connectionString); 33 | 34 | _sessionPool 35 | = new PGSessionPool( 36 | connectionStringBuilder.Host, 37 | connectionStringBuilder.Port, 38 | connectionStringBuilder.Database, 39 | connectionStringBuilder.Username, 40 | connectionStringBuilder.Password, 41 | threadCount) 42 | { 43 | OnCreate = async s => await s.PrepareAsync("q", "select id, message from fortune") 44 | }; 45 | } 46 | 47 | private static Fortune CreateFortune(List results) 48 | { 49 | var fortune = new Fortune(); 50 | 51 | results.Add(fortune); 52 | 53 | return fortune; 54 | } 55 | 56 | private static void BindColumn(Fortune fortune, ReadBuffer readBuffer, int index, int length) 57 | { 58 | switch (index) 59 | { 60 | case 0: 61 | fortune.Id = readBuffer.ReadInt(); 62 | break; 63 | case 1: 64 | fortune.Message = readBuffer.ReadString(length); 65 | break; 66 | } 67 | } 68 | 69 | public override async Task DoWorkAsync() 70 | { 71 | while (Program.IsRunning) 72 | { 73 | var results = new List(); 74 | 75 | var session = await _sessionPool.Rent(); 76 | 77 | try 78 | { 79 | await session.ExecuteAsync("q", results, CreateFortune, BindColumn); 80 | } 81 | finally 82 | { 83 | _sessionPool.Return(session); 84 | } 85 | 86 | CheckResults(results); 87 | 88 | Program.IncrementCounter(); 89 | } 90 | } 91 | 92 | public override async Task DoWorkAsyncCaching() 93 | { 94 | var session = await _sessionPool.Rent(); 95 | 96 | try 97 | { 98 | while (Program.IsRunning) 99 | { 100 | var results = new List(); 101 | 102 | await session.ExecuteAsync("q", results, CreateFortune, BindColumn); 103 | 104 | CheckResults(results); 105 | 106 | Program.IncrementCounter(); 107 | } 108 | } 109 | finally 110 | { 111 | _sessionPool.Return(session); 112 | } 113 | } 114 | 115 | public void Dispose() 116 | { 117 | _sessionPool?.Dispose(); 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/BenchmarkDb/Program.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Data.SqlClient; 7 | using System.IO; 8 | using System.Linq; 9 | using System.Threading; 10 | using System.Threading.Tasks; 11 | using Microsoft.Data.Sqlite; 12 | using MySql.Data.MySqlClient; 13 | using Npgsql; 14 | using Peregrine.Ado; 15 | 16 | namespace BenchmarkDb 17 | { 18 | public static class Program 19 | { 20 | private const int DefaultThreadCount = 64; 21 | private const int DefaultExecutionTimeSeconds = 10; 22 | private const int WarmupTimeSeconds = 3; 23 | 24 | public const string TestQuery = "SELECT id, message FROM fortune"; 25 | 26 | private static int _counter; 27 | 28 | public static void IncrementCounter() => Interlocked.Increment(ref _counter); 29 | 30 | private static int _running; 31 | 32 | public static bool IsRunning => _running == 1; 33 | 34 | private static readonly IDictionary _drivers 35 | = new Dictionary 36 | { 37 | { "ado-npgsql", new NpgsqlAdoDriver() }, 38 | { "ado-mysql", new MySqlAdoDriver() }, 39 | { "ado-sqlclient", new AdoDriver(SqlClientFactory.Instance) }, 40 | { "ado-sqlite", new AdoDriver(SqliteFactory.Instance) }, 41 | { "peregrine", new PeregrineDriver() }, 42 | { "ado-peregrine", new PeregrineAdoDriver(PeregrineFactory.Instance) }, 43 | { "ado-firebirdclient", new FirebirdClientAdoDriver() }, 44 | }; 45 | 46 | public static async Task Main(string[] args) 47 | { 48 | #if DEBUG 49 | Console.WriteLine("WARNING! Using DEBUG build."); 50 | #endif 51 | int Help((string option, string value) invalid = default) 52 | { 53 | Console.WriteLine("Usage: [threads] [time]"); 54 | Console.WriteLine(); 55 | Console.WriteLine("Arguments:"); 56 | Console.WriteLine($" The target database driver ({string.Join(", ", _drivers.Keys.Select(k => $"'{k}'"))})."); 57 | Console.WriteLine(" The target database connection string."); 58 | Console.WriteLine($" : The specific variation to run ({string.Join(", ", Variation.Names.Select(k => $"'{k}'"))})."); 59 | Console.WriteLine(); 60 | Console.WriteLine("Options:"); 61 | Console.WriteLine($" [threads]: The number of threads to spawn (default {DefaultThreadCount})."); 62 | Console.WriteLine($" [time]: The number of seconds to run for (default {DefaultExecutionTimeSeconds})."); 63 | Console.WriteLine(); 64 | 65 | if (invalid.option != null) 66 | { 67 | Console.WriteLine($" '{invalid.value}' is not a valid value for parameter '{invalid.option}'!"); 68 | Console.WriteLine(); 69 | } 70 | 71 | return 1; 72 | } 73 | 74 | if (args.Length < 3) 75 | { 76 | return Help(); 77 | } 78 | 79 | var driverName = args[0]; 80 | 81 | if (!_drivers.TryGetValue(driverName, out var driver)) 82 | { 83 | return Help(("driver", driverName)); 84 | } 85 | 86 | var connectionString = args[1]; 87 | 88 | if (string.IsNullOrWhiteSpace(connectionString)) 89 | { 90 | return Help(("connection-string", connectionString)); 91 | } 92 | 93 | var variationName = args[2]; 94 | 95 | var variation = driver.TryGetVariation(variationName); 96 | 97 | if (variation == null) 98 | { 99 | return Help(("variation", variationName)); 100 | } 101 | 102 | var threadCount = DefaultThreadCount; 103 | var time = DefaultExecutionTimeSeconds; 104 | 105 | if (args.Length > 3 106 | && !int.TryParse(args[3], out threadCount)) 107 | { 108 | return Help(("threads", args[3])); 109 | } 110 | 111 | if (args.Length > 4 112 | && !int.TryParse(args[4], out time)) 113 | { 114 | return Help(("time", args[4])); 115 | } 116 | 117 | driver.Initialize(connectionString, threadCount); 118 | 119 | DateTime startTime = default, stopTime = default; 120 | 121 | var totalTransactions = 0; 122 | var results = new List(); 123 | 124 | IEnumerable CreateTasks() 125 | { 126 | yield return Task.Run( 127 | async () => 128 | { 129 | Console.Write($"Warming up for {WarmupTimeSeconds}s..."); 130 | 131 | await Task.Delay(TimeSpan.FromSeconds(WarmupTimeSeconds)); 132 | 133 | Console.SetCursorPosition(0, Console.CursorTop); 134 | 135 | Interlocked.Exchange(ref _counter, 0); 136 | 137 | startTime = DateTime.UtcNow; 138 | var lastDisplay = startTime; 139 | 140 | while (IsRunning) 141 | { 142 | await Task.Delay(200); 143 | 144 | var now = DateTime.UtcNow; 145 | var tps = (int)(_counter / (now - lastDisplay).TotalSeconds); 146 | var remaining = (int)(time - (now - startTime).TotalSeconds); 147 | 148 | results.Add(tps); 149 | 150 | Console.Write($"{tps} tps, {remaining}s "); 151 | Console.SetCursorPosition(0, Console.CursorTop); 152 | 153 | lastDisplay = now; 154 | totalTransactions += Interlocked.Exchange(ref _counter, 0); 155 | } 156 | }); 157 | 158 | yield return Task.Run( 159 | async () => 160 | { 161 | await Task.Delay(TimeSpan.FromSeconds(WarmupTimeSeconds + time)); 162 | 163 | Interlocked.Exchange(ref _running, 0); 164 | 165 | stopTime = DateTime.UtcNow; 166 | }); 167 | 168 | foreach (var task in Enumerable.Range(0, threadCount) 169 | .Select(_ => Task.Factory.StartNew(variation, TaskCreationOptions.LongRunning).Unwrap())) 170 | { 171 | yield return task; 172 | } 173 | } 174 | 175 | if (variation == DriverBase.NotSupportedVariation) 176 | { 177 | Console.WriteLine($"Driver '{driverName}' does not support variation '{variationName}'."); 178 | 179 | return 0; 180 | } 181 | 182 | Interlocked.Exchange(ref _running, 1); 183 | 184 | await Task.WhenAll(CreateTasks()); 185 | 186 | var totalTps = (int)(totalTransactions / (stopTime - startTime).TotalSeconds); 187 | 188 | results.Sort(); 189 | results.RemoveAt(0); 190 | results.RemoveAt(results.Count - 1); 191 | 192 | double CalculateStdDev(ICollection values) 193 | { 194 | var avg = values.Average(); 195 | var sum = values.Sum(d => Math.Pow(d - avg, 2)); 196 | 197 | return Math.Sqrt(sum / values.Count); 198 | } 199 | 200 | var stdDev = CalculateStdDev(results); 201 | 202 | Console.SetCursorPosition(0, Console.CursorTop); 203 | Console.WriteLine($"{driverName} {variationName} {threadCount:D2} Threads, tps: {totalTps:F2}, stddev(w/o best+worst): {stdDev:F2}"); 204 | 205 | var desc = $"{driverName}+{variationName}+{threadCount}"; 206 | 207 | using (var sw = File.AppendText("results.md")) 208 | { 209 | sw.WriteLine($"|{desc}|{variationName}|{threadCount:D2}|{totalTps:F0}|{stdDev:F0}|"); 210 | } 211 | 212 | using (var sw = File.AppendText("results.csv")) 213 | { 214 | sw.WriteLine($"{desc},{variationName},{threadCount:D2},{totalTps:F0},{stdDev:F0}"); 215 | } 216 | 217 | (driver as IDisposable)?.Dispose(); 218 | 219 | return 0; 220 | } 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /src/BenchmarkDb/Variation.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System.Collections.Generic; 5 | 6 | namespace BenchmarkDb 7 | { 8 | public static class Variation 9 | { 10 | public const string Sync = "sync"; 11 | public const string SyncCaching = "sync-caching"; 12 | public const string Async = "async"; 13 | public const string AsyncCaching = "async-caching"; 14 | 15 | public static IEnumerable Names 16 | { 17 | get 18 | { 19 | yield return Sync; 20 | yield return SyncCaching; 21 | yield return Async; 22 | yield return AsyncCaching; 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/BenchmarkDb/aspnet-Benchmarks.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aspnet/DataAccessPerformance/82a0d2b32c404b0c5c4d9b1db7c48898c30fa88f/src/BenchmarkDb/aspnet-Benchmarks.db -------------------------------------------------------------------------------- /src/BenchmarkDb/firebird-fortune.sql: -------------------------------------------------------------------------------- 1 | create table fortune ( 2 | id int primary key, 3 | message varchar(2048) character set utf8 not null 4 | ); 5 | grant select, update on fortune to benchmarkdbuser; 6 | 7 | 8 | INSERT INTO Fortune (id, message) VALUES (1, 'fortune: No such file or directory'); 9 | INSERT INTO Fortune (id, message) VALUES (2, 'A computer scientist is someone who fixes things that aren''t broken.'); 10 | INSERT INTO Fortune (id, message) VALUES (3, 'After enough decimal places, nobody gives a damn.'); 11 | INSERT INTO Fortune (id, message) VALUES (4, 'A bad random number generator: 1, 1, 1, 1, 1, 4.33e+67, 1, 1, 1'); 12 | INSERT INTO Fortune (id, message) VALUES (5, 'A computer program does what you tell it to do, not what you want it to do.'); 13 | INSERT INTO Fortune (id, message) VALUES (6, 'Emacs is a nice operating system, but I prefer UNIX. — Tom Christaensen'); 14 | INSERT INTO Fortune (id, message) VALUES (7, 'Any program that runs right is obsolete.'); 15 | INSERT INTO Fortune (id, message) VALUES (8, 'A list is only as strong as its weakest link. — Donald Knuth'); 16 | INSERT INTO Fortune (id, message) VALUES (9, 'Feature: A bug with seniority.'); 17 | INSERT INTO Fortune (id, message) VALUES (10, 'Computers make very fast, very accurate mistakes.'); 18 | INSERT INTO Fortune (id, message) VALUES (11, ''); 19 | INSERT INTO Fortune (id, message) VALUES (12, 'フレームワークのベンチマーク'); 20 | -------------------------------------------------------------------------------- /src/BenchmarkDb/firebird-full.sql: -------------------------------------------------------------------------------- 1 | create table world ( 2 | id int primary key, 3 | randomnumber int default 0 not null 4 | ); 5 | grant select, update on world to benchmarkdbuser; 6 | 7 | set term ^; 8 | 9 | execute block 10 | as 11 | declare counter int; 12 | begin 13 | counter = 1; 14 | while (counter <= 10000) do 15 | begin 16 | insert into world (id, randomnumber) values (:counter, floor(rand() * 10000 + 1)); 17 | counter = counter + 1; 18 | end 19 | end^ 20 | 21 | set term ;^ 22 | 23 | create table fortune ( 24 | id int primary key, 25 | message varchar(2048) character set utf8 not null 26 | ); 27 | grant select, update on fortune to benchmarkdbuser; 28 | 29 | 30 | INSERT INTO Fortune (id, message) VALUES (1, 'fortune: No such file or directory'); 31 | INSERT INTO Fortune (id, message) VALUES (2, 'A computer scientist is someone who fixes things that aren''t broken.'); 32 | INSERT INTO Fortune (id, message) VALUES (3, 'After enough decimal places, nobody gives a damn.'); 33 | INSERT INTO Fortune (id, message) VALUES (4, 'A bad random number generator: 1, 1, 1, 1, 1, 4.33e+67, 1, 1, 1'); 34 | INSERT INTO Fortune (id, message) VALUES (5, 'A computer program does what you tell it to do, not what you want it to do.'); 35 | INSERT INTO Fortune (id, message) VALUES (6, 'Emacs is a nice operating system, but I prefer UNIX. — Tom Christaensen'); 36 | INSERT INTO Fortune (id, message) VALUES (7, 'Any program that runs right is obsolete.'); 37 | INSERT INTO Fortune (id, message) VALUES (8, 'A list is only as strong as its weakest link. — Donald Knuth'); 38 | INSERT INTO Fortune (id, message) VALUES (9, 'Feature: A bug with seniority.'); 39 | INSERT INTO Fortune (id, message) VALUES (10, 'Computers make very fast, very accurate mistakes.'); 40 | INSERT INTO Fortune (id, message) VALUES (11, ''); 41 | INSERT INTO Fortune (id, message) VALUES (12, 'フレームワークのベンチマーク'); 42 | --------------------------------------------------------------------------------