Skip to content

Commit 33709e3

Browse files
committed
v2.20.0 initial
1 parent 4b90409 commit 33709e3

39 files changed

+1515
-246
lines changed

.github/workflows/build-test-publish.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ jobs:
4343
env:
4444
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
4545
with:
46-
tag_name: v2.19.0-client-v2.12.0
47-
release_name: "AOT Client v2.12.0 NpgsqlRest v2.19.0"
46+
tag_name: v2.20.0-client-v2.13.0
47+
release_name: "AOT Client v2.13.0 NpgsqlRest v2.20.0"
4848
draft: true
4949
prerelease: true
5050

BenchmarkTests/BenchmarkTests.csproj

+5-1
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,16 @@
1010

1111
<ItemGroup>
1212
<PackageReference Include="BenchmarkDotNet" Version="0.14.0" />
13-
<PackageReference Include="Npgsql" Version="9.0.2" />
13+
<PackageReference Include="Npgsql" Version="9.0.3" />
1414
<PackageReference Include="System.Text.Json" Version="9.0.2" />
1515
</ItemGroup>
1616

1717
<ItemGroup>
1818
<Folder Include="BenchmarkDotNet.Artifacts\" />
1919
</ItemGroup>
2020

21+
<ItemGroup>
22+
<ProjectReference Include="..\NpgsqlRestClient\NpgsqlRestClient.csproj" />
23+
</ItemGroup>
24+
2125
</Project>

BenchmarkTests/FormatStringTests.cs

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
using System;
2+
using System.Net;
3+
using System.Text;
4+
using System.Text.RegularExpressions;
5+
using BenchmarkDotNet.Attributes;
6+
using NpgsqlRestClient;
7+
8+
namespace BenchmarkTests;
9+
10+
public class FormatStringTests
11+
{
12+
private readonly Dictionary<string, string> replacements = new Dictionary<string, string>
13+
{
14+
{ "name", "John" },
15+
{ "place", "NpgsqlRest" }
16+
};
17+
18+
[Benchmark]
19+
public ReadOnlySpan<char> FormatStringMethod()
20+
{
21+
ReadOnlySpan<char> input = "Hello, {name}! Welcome to {place}.";
22+
return DefaultResponseParser.FormatString(input, replacements);
23+
}
24+
25+
[Benchmark]
26+
public string RegexMethod()
27+
{
28+
ReadOnlySpan<char> input = "Hello, {name}! Welcome to {place}.";
29+
string pattern = @"\{(\w+)\}";
30+
return Regex.Replace(input.ToString(), pattern, match =>
31+
{
32+
string key = match.Groups[1].Value;
33+
return replacements.TryGetValue(key, out var value) ? value : match.Value;
34+
});
35+
}
36+
37+
[Benchmark]
38+
public string RegexCodeGenMethod()
39+
{
40+
ReadOnlySpan<char> input = "Hello, {name}! Welcome to {place}.";
41+
return ReplaceRegex.Value().Replace(input.ToString(), match =>
42+
{
43+
string key = match.Groups[1].Value;
44+
return replacements.TryGetValue(key, out var value) ? value : match.Value;
45+
});
46+
}
47+
}
48+
49+
public static partial class ReplaceRegex
50+
{
51+
[GeneratedRegex(@"\{(\w+)\}", RegexOptions.Compiled)]
52+
public static partial Regex Value();
53+
}

BenchmarkTests/Program.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
using BenchmarkTests;
77
using Perfolizer.Horology;
88

9-
BenchmarkRunner.Run<ConnectionParametersTests>();
9+
BenchmarkRunner.Run<FormatStringTests>();
1010

1111
//BenchmarkRunner
1212
// .Run<HttpClientTests>(

NpgsqlRest/Defaults/DefaultCommentParser.cs

+55
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,22 @@ internal static class DefaultCommentParser
127127

128128
private const string CacheKey = "cached";
129129

130+
private static readonly string[] parseResponseKey = [
131+
"parse",
132+
"parseresponse",
133+
"parse_response",
134+
"parse-response"
135+
];
136+
137+
private static readonly string[] cacheExpiresInKey = [
138+
"cacheexpires",
139+
"cacheexpiresin",
140+
"cache-expires",
141+
"cache-expires-in",
142+
"cache_expires",
143+
"cache_expires_in",
144+
];
145+
130146
public static RoutineEndpoint? Parse(Routine routine, RoutineEndpoint routineEndpoint, NpgsqlRestOptions options, ILogger? logger)
131147
{
132148
if (options.CommentsMode == CommentsMode.Ignore)
@@ -577,6 +593,23 @@ internal static class DefaultCommentParser
577593
}
578594
}
579595

596+
// parse
597+
// parseresponse
598+
// parse_response
599+
// parse-response
600+
else if (haveTag is true && StrEqualsToArray(words[0], parseResponseKey))
601+
{
602+
if (!(routine.ReturnsSet == false && routine.ColumnCount == 1 && routine.ReturnsRecordType is false))
603+
{
604+
logger?.CommentInvalidParseResponse(routine.Type, routine.Schema, routine.Name);
605+
}
606+
routineEndpoint.ParseResponse = true;
607+
if (options.LogAnnotationSetInfo)
608+
{
609+
logger?.CommentParseResponse(routine.Type, routine.Schema, routine.Name);
610+
}
611+
}
612+
580613
// cached
581614
// cached [ param1, param2, param3 [, ...] ]
582615
else if (haveTag is true && StrEquals(words[0], CacheKey))
@@ -610,6 +643,28 @@ internal static class DefaultCommentParser
610643
}
611644
}
612645

646+
// cacheexpires
647+
// cacheexpiresin
648+
// cache-expires
649+
// cache-expires-in
650+
// cache_expires
651+
else if (haveTag is true && len >= 2 && StrEqualsToArray(words[0], cacheExpiresInKey))
652+
{
653+
var value = TimeSpanParser.ParsePostgresInterval(string.Join(Consts.Space, words[1..]));
654+
if (value is not null)
655+
{
656+
routineEndpoint.CacheExpiresIn = value.Value;
657+
if (options.LogAnnotationSetInfo)
658+
{
659+
logger?.CommentCacheExpiresIn(routine.Type, routine.Schema, routine.Name, value.Value);
660+
}
661+
}
662+
else
663+
{
664+
logger?.InvalidCacheExpiresIn(routine.Type, routine.Schema, routine.Name, string.Join(Consts.Space, words[1..]));
665+
}
666+
}
667+
613668
// key: value
614669
// Content-Type: application/json
615670
else if (haveTag is true && line.Contains(Consts.Colon))

NpgsqlRest/Defaults/DefaultEndpoint.cs

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ internal static class DefaultEndpoint
2626
RequestParamType.BodyJson;
2727

2828
RoutineEndpoint routineEndpoint = new(
29+
routine,
2930
url: url,
3031
method: method,
3132
requestParamType: requestParamType,

NpgsqlRest/Enums.cs

+16
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,20 @@ public enum RequestHeadersMode
5656
/// This parameter has to have the default value (null) in the routine and have to be text or json type.
5757
/// </summary>
5858
Parameter
59+
}
60+
61+
public enum PostgresConnectionNoticeLoggingMode
62+
{
63+
/// <summary>
64+
/// Log only connection messages.
65+
/// </summary>
66+
MessageOnly,
67+
/// <summary>
68+
/// Log last stack trace and message.
69+
/// </summary>
70+
FirstStackFrameAndMessage,
71+
/// <summary>
72+
/// Log full stack trace and message.
73+
/// </summary>
74+
FullStackAndMessage
5975
}

NpgsqlRest/Interfaces.cs

+11-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using Npgsql;
2-
using static System.Runtime.InteropServices.JavaScript.JSType;
32

43
namespace NpgsqlRest;
54

@@ -16,12 +15,12 @@ void Setup(IApplicationBuilder builder, ILogger? logger, NpgsqlRestOptions optio
1615
/// <summary>
1716
/// After successful endpoint creation.
1817
/// </summary>
19-
void Handle(Routine routine, RoutineEndpoint endpoint) { }
18+
void Handle(RoutineEndpoint endpoint) { }
2019

2120
/// <summary>
2221
/// After all endpoints are created.
2322
/// </summary>
24-
void Cleanup((Routine routine, RoutineEndpoint endpoint)[] endpoints) { }
23+
void Cleanup(RoutineEndpoint[] endpoints) { }
2524

2625
/// <summary>
2726
/// After all endpoints are created.
@@ -153,3 +152,12 @@ public interface IRoutineSource
153152
/// </summary>
154153
string[]? ExcludeNames { get; set; }
155154
}
155+
156+
public interface IResponseParser
157+
{
158+
/// <summary>
159+
/// Parse response from PostgreSQL.
160+
/// </summary>
161+
/// <returns>Response string</returns>
162+
ReadOnlySpan<char> Parse(ReadOnlySpan<char> input, RoutineEndpoint endpoint, HttpContext context);
163+
}

NpgsqlRest/NpgsqlRest.csproj

+5-5
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,10 @@
2929
<GenerateDocumentationFile>true</GenerateDocumentationFile>
3030
<PackageReadmeFile>README.MD</PackageReadmeFile>
3131
<DocumentationFile>bin\$(Configuration)\$(AssemblyName).xml</DocumentationFile>
32-
<Version>2.19.0</Version>
33-
<AssemblyVersion>2.19.0</AssemblyVersion>
34-
<FileVersion>2.19.0</FileVersion>
35-
<PackageVersion>2.19.0</PackageVersion>
32+
<Version>2.20.0</Version>
33+
<AssemblyVersion>2.20.0</AssemblyVersion>
34+
<FileVersion>2.20.0</FileVersion>
35+
<PackageVersion>2.20.0</PackageVersion>
3636
</PropertyGroup>
3737

3838
<PropertyGroup Condition="'$(GITHUB_ACTIONS)' == 'true'">
@@ -41,7 +41,7 @@
4141

4242
<ItemGroup>
4343
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All" />
44-
<PackageReference Include="Npgsql" Version="9.0.2" />
44+
<PackageReference Include="Npgsql" Version="9.0.3" />
4545
<PackageReferenceFiles Include="bin\$(Configuration)\$(AssemblyName).xml" />
4646
</ItemGroup>
4747

NpgsqlRest/NpgsqlRestLogger.cs

+81-16
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,18 @@ public static partial class Log
119119

120120
[LoggerMessage(Level = LogLevel.Information, Message = "{type} {schema}.{name} has set CACHED with parameters {cachedParams} by the comment annotation.")]
121121
public static partial void CommentCached(this ILogger logger, RoutineType type, string schema, string name, IEnumerable<string> cachedParams);
122+
123+
[LoggerMessage(Level = LogLevel.Information, Message = "{type} {schema}.{name} has set PARSE RESPONSE to true by the comment annotation.")]
124+
public static partial void CommentParseResponse(this ILogger logger, RoutineType type, string schema, string name);
125+
126+
[LoggerMessage(Level = LogLevel.Warning, Message = "{type} {schema}.{name} has set PARSE RESPONSE to true by the comment annotation but routine doesn't return a single value. Routine will NOT be parsed. Only single values can be parsed.")]
127+
public static partial void CommentInvalidParseResponse(this ILogger logger, RoutineType type, string schema, string name);
128+
129+
[LoggerMessage(Level = LogLevel.Information, Message = "{type} {schema}.{name} has set CACHE EXPIRES IN to {value} by the comment annotation.")]
130+
public static partial void CommentCacheExpiresIn(this ILogger logger, RoutineType type, string schema, string name, TimeSpan value);
131+
132+
[LoggerMessage(Level = LogLevel.Warning, Message = "{type} {schema}.{name} can't set CACHE EXPIRES IN value by the comment annotation. Invalid interval value: {value}")]
133+
public static partial void InvalidCacheExpiresIn(this ILogger logger, RoutineType type, string schema, string name, string value);
122134
}
123135

124136
public static class NpgsqlRestLogger
@@ -130,7 +142,7 @@ public static class NpgsqlRestLogger
130142
private const string Debug = "DEBUG";
131143
private const string Error = "ERROR";
132144
private const string Panic = "PANIC";
133-
private const string LogPattern = "{where} {message}";
145+
private const string LogPattern = "{where}:\n{message}";
134146

135147
internal static readonly LogDefineOptions LogDefineOptions = new() { SkipEnabledCheck = true };
136148

@@ -197,36 +209,89 @@ public static void LogEndpoint(ILogger? logger, RoutineEndpoint endpoint, string
197209
}
198210
}
199211

200-
public static void LogConnectionNotice(ILogger? logger, NpgsqlNoticeEventArgs args)
212+
public static void LogConnectionNotice(ILogger? logger, NpgsqlNoticeEventArgs args, PostgresConnectionNoticeLoggingMode mode)
201213
{
202214
if (logger is null)
203215
{
204216
return;
205217
}
206-
var severity = args.Notice.Severity;
207-
var where = string.Concat(args.Notice.Where, " ", severity);
208-
if (string.Equals(Info, severity, StringComparison.OrdinalIgnoreCase) ||
209-
string.Equals(Log, severity, StringComparison.OrdinalIgnoreCase) ||
210-
string.Equals(Notice, severity, StringComparison.OrdinalIgnoreCase))
218+
if (string.Equals(Info, args.Notice.Severity, StringComparison.OrdinalIgnoreCase) ||
219+
string.Equals(Log, args.Notice.Severity, StringComparison.OrdinalIgnoreCase) ||
220+
string.Equals(Notice, args.Notice.Severity, StringComparison.OrdinalIgnoreCase))
211221
{
212-
LogInformation(logger, where, args.Notice.MessageText);
222+
if (mode == PostgresConnectionNoticeLoggingMode.MessageOnly)
223+
{
224+
logger.LogInformation(args.Notice.MessageText);
225+
}
226+
else if (mode == PostgresConnectionNoticeLoggingMode.FirstStackFrameAndMessage)
227+
{
228+
LogInformation(logger, args.Notice?.Where?.Split('\n').LastOrDefault() ?? "", args.Notice?.MessageText!);
229+
}
230+
else if (mode == PostgresConnectionNoticeLoggingMode.FullStackAndMessage)
231+
{
232+
LogInformation(logger, args.Notice?.Where, args.Notice?.MessageText!);
233+
}
213234
}
214-
else if (string.Equals(Warning, severity, StringComparison.OrdinalIgnoreCase))
235+
else if (string.Equals(Warning, args.Notice.Severity, StringComparison.OrdinalIgnoreCase))
215236
{
216-
LogWarning(logger, where, args.Notice.MessageText);
237+
if (mode == PostgresConnectionNoticeLoggingMode.MessageOnly)
238+
{
239+
logger.LogWarning(args.Notice.MessageText);
240+
}
241+
else if (mode == PostgresConnectionNoticeLoggingMode.FirstStackFrameAndMessage)
242+
{
243+
LogWarning(logger, args.Notice?.Where?.Split('\n').Last() ?? "", args.Notice?.MessageText!);
244+
}
245+
else if (mode == PostgresConnectionNoticeLoggingMode.FullStackAndMessage)
246+
{
247+
LogWarning(logger, args.Notice?.Where, args.Notice?.MessageText!);
248+
}
217249
}
218-
else if (string.Equals(Debug, severity, StringComparison.OrdinalIgnoreCase))
250+
else if (string.Equals(Debug, args.Notice.Severity, StringComparison.OrdinalIgnoreCase))
219251
{
220-
LogDebug(logger, where, args.Notice.MessageText);
252+
if (mode == PostgresConnectionNoticeLoggingMode.MessageOnly)
253+
{
254+
logger.LogDebug(args.Notice.MessageText);
255+
}
256+
else if (mode == PostgresConnectionNoticeLoggingMode.FirstStackFrameAndMessage)
257+
{
258+
LogDebug(logger, args.Notice?.Where?.Split('\n').Last() ?? "", args.Notice?.MessageText!);
259+
}
260+
else if (mode == PostgresConnectionNoticeLoggingMode.FullStackAndMessage)
261+
{
262+
LogDebug(logger, args.Notice?.Where, args.Notice?.MessageText!);
263+
}
221264
}
222-
else if (string.Equals(Error, severity, StringComparison.OrdinalIgnoreCase) ||
223-
string.Equals(Panic, severity, StringComparison.OrdinalIgnoreCase))
265+
else if (string.Equals(Error, args.Notice.Severity, StringComparison.OrdinalIgnoreCase) ||
266+
string.Equals(Panic, args.Notice.Severity, StringComparison.OrdinalIgnoreCase))
224267
{
225-
LogError(logger, where, args.Notice.MessageText);
268+
if (mode == PostgresConnectionNoticeLoggingMode.MessageOnly)
269+
{
270+
logger.LogError(args.Notice.MessageText);
271+
}
272+
else if (mode == PostgresConnectionNoticeLoggingMode.FirstStackFrameAndMessage)
273+
{
274+
LogError(logger, args.Notice?.Where?.Split('\n').Last() ?? "", args.Notice?.MessageText!);
275+
}
276+
else if (mode == PostgresConnectionNoticeLoggingMode.FullStackAndMessage)
277+
{
278+
LogError(logger, args.Notice?.Where, args.Notice?.MessageText!);
279+
}
226280
}
227281
else
228282
{
229-
LogTrace(logger, where, args.Notice.MessageText);
283+
if (mode == PostgresConnectionNoticeLoggingMode.MessageOnly)
284+
{
285+
logger.LogTrace(args.Notice.MessageText);
286+
}
287+
else if (mode == PostgresConnectionNoticeLoggingMode.FirstStackFrameAndMessage)
288+
{
289+
LogTrace(logger, args.Notice?.Where?.Split('\n').Last() ?? "", args.Notice?.MessageText!);
290+
}
291+
else if (mode == PostgresConnectionNoticeLoggingMode.FullStackAndMessage)
292+
{
293+
LogTrace(logger, args.Notice?.Where, args.Notice?.MessageText!);
294+
}
230295
}
231296
}
232297
}

0 commit comments

Comments
 (0)