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