Skip to content

Commit 4b90409

Browse files
committed
2.19.0 initial
1 parent 7307fd0 commit 4b90409

19 files changed

+341
-115
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.18.0-client-v2.11.0
47-
release_name: "AOT Client v2.11.0 NpgsqlRest v2.18.0"
46+
tag_name: v2.19.0-client-v2.12.0
47+
release_name: "AOT Client v2.12.0 NpgsqlRest v2.19.0"
4848
draft: true
4949
prerelease: true
5050

NpgsqlRest/Defaults/DefaultCommentParser.cs

+35
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,8 @@ internal static class DefaultCommentParser
125125
"column-names"
126126
];
127127

128+
private const string CacheKey = "cached";
129+
128130
public static RoutineEndpoint? Parse(Routine routine, RoutineEndpoint routineEndpoint, NpgsqlRestOptions options, ILogger? logger)
129131
{
130132
if (options.CommentsMode == CommentsMode.Ignore)
@@ -575,6 +577,39 @@ internal static class DefaultCommentParser
575577
}
576578
}
577579

580+
// cached
581+
// cached [ param1, param2, param3 [, ...] ]
582+
else if (haveTag is true && StrEquals(words[0], CacheKey))
583+
{
584+
if (!(routine.ReturnsSet == false && routine.ColumnCount == 1 && routine.ReturnsRecordType is false))
585+
{
586+
logger?.CommentInvalidCache(routine.Type, routine.Schema, routine.Name);
587+
}
588+
routineEndpoint.Cached = true;
589+
if (len > 1)
590+
{
591+
var names = words[1..];
592+
HashSet<string> result = new(names.Length);
593+
for(int j = 0; j < names.Length; j++)
594+
{
595+
var name = names[j];
596+
if (!routine.OriginalParamsHash.Contains(name) && !routine.ParamsHash.Contains(name))
597+
{
598+
logger?.CommentInvalidCacheParam(routine.Type, routine.Schema, routine.Name, name);
599+
} else
600+
{
601+
result.Add(name);
602+
}
603+
}
604+
routineEndpoint.CachedParams = result;
605+
}
606+
607+
if (options.LogAnnotationSetInfo)
608+
{
609+
logger?.CommentCached(routine.Type, routine.Schema, routine.Name, routineEndpoint.CachedParams ?? []);
610+
}
611+
}
612+
578613
// key: value
579614
// Content-Type: application/json
580615
else if (haveTag is true && line.Contains(Consts.Colon))

NpgsqlRest/NpgsqlRest.csproj

+4-4
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.18.0</Version>
33-
<AssemblyVersion>2.18.0</AssemblyVersion>
34-
<FileVersion>2.18.0</FileVersion>
35-
<PackageVersion>2.18.0</PackageVersion>
32+
<Version>2.19.0</Version>
33+
<AssemblyVersion>2.19.0</AssemblyVersion>
34+
<FileVersion>2.19.0</FileVersion>
35+
<PackageVersion>2.19.0</PackageVersion>
3636
</PropertyGroup>
3737

3838
<PropertyGroup Condition="'$(GITHUB_ACTIONS)' == 'true'">

NpgsqlRest/NpgsqlRestLogger.cs

+12-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
using Npgsql;
1+
using Microsoft.AspNetCore.Routing;
2+
using System.Xml.Linq;
3+
using Npgsql;
24

35
namespace NpgsqlRest;
46

@@ -108,6 +110,15 @@ public static partial class Log
108110

109111
[LoggerMessage(Level = LogLevel.Information, Message = "{type} {schema}.{name} has set COLUMN NAMES by the comment annotation.")]
110112
public static partial void CommentRawSetColumnNames(this ILogger logger, RoutineType type, string schema, string name);
113+
114+
[LoggerMessage(Level = LogLevel.Warning, Message = "{type} {schema}.{name} has set CACHED by the comment annotation, routine doesn't return a single value. Routine will NOT be cached. Only single values can be cached.")]
115+
public static partial void CommentInvalidCache(this ILogger logger, RoutineType type, string schema, string name);
116+
117+
[LoggerMessage(Level = LogLevel.Warning, Message = "{type} {schema}.{name} has set CACHED PARAMETER NAME to {param} by the comment annotation, but that parameter doesn't exists on this routine either converted or original. This cache parameter will be ignored.")]
118+
public static partial void CommentInvalidCacheParam(this ILogger logger, RoutineType type, string schema, string name, string param);
119+
120+
[LoggerMessage(Level = LogLevel.Information, Message = "{type} {schema}.{name} has set CACHED with parameters {cachedParams} by the comment annotation.")]
121+
public static partial void CommentCached(this ILogger logger, RoutineType type, string schema, string name, IEnumerable<string> cachedParams);
111122
}
112123

113124
public static class NpgsqlRestLogger

NpgsqlRest/NpgsqlRestMiddleware.cs

+140-51
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using static NpgsqlRest.ParameterParser;
1313

1414
using NpgsqlRest.Auth;
15+
using System.Linq;
1516

1617
namespace NpgsqlRest;
1718

@@ -209,6 +210,12 @@ public async Task InvokeAsync(HttpContext context)
209210
Dictionary<string, JsonNode?>? bodyDict = null;
210211
string? body = null;
211212
Dictionary<string, StringValues>? queryDict = null;
213+
StringBuilder? cacheKeys = null;
214+
if (endpoint.Cached is true)
215+
{
216+
cacheKeys = new(endpoint.CachedParams?.Count ?? 0 + 1);
217+
cacheKeys.Append(routine.Expression);
218+
}
212219

213220
if (endpoint.RequestParamType == RequestParamType.QueryString)
214221
{
@@ -309,6 +316,13 @@ await options.ValidateParametersAsync(new ParameterValidationValues(
309316
return;
310317
}
311318
}
319+
if (endpoint.Cached is true && endpoint.CachedParams is not null)
320+
{
321+
if (endpoint.CachedParams.Contains(parameter.ConvertedName) || endpoint.CachedParams.Contains(parameter.ActualName))
322+
{
323+
cacheKeys?.Append(parameter.Value?.ToString() ?? "");
324+
}
325+
}
312326
command.Parameters.Add(parameter);
313327

314328
if (hasNulls is false && parameter.Value == DBNull.Value)
@@ -378,6 +392,13 @@ await options.ValidateParametersAsync(new ParameterValidationValues(
378392
return;
379393
}
380394
}
395+
if (endpoint.Cached is true && endpoint.CachedParams is not null)
396+
{
397+
if (endpoint.CachedParams.Contains(parameter.ConvertedName) || endpoint.CachedParams.Contains(parameter.ActualName))
398+
{
399+
cacheKeys?.Append(parameter.Value?.ToString() ?? "");
400+
}
401+
}
381402
command.Parameters.Add(parameter);
382403

383404
if (hasNulls is false && parameter.Value == DBNull.Value)
@@ -462,6 +483,13 @@ await options.ValidateParametersAsync(new ParameterValidationValues(
462483
return;
463484
}
464485
}
486+
if (endpoint.Cached is true && endpoint.CachedParams is not null)
487+
{
488+
if (endpoint.CachedParams.Contains(parameter.ConvertedName) || endpoint.CachedParams.Contains(parameter.ActualName))
489+
{
490+
cacheKeys?.Append(parameter.Value?.ToString() ?? "");
491+
}
492+
}
465493
command.Parameters.Add(parameter);
466494

467495
if (hasNulls is false && parameter.Value == DBNull.Value)
@@ -574,6 +602,13 @@ await options.ValidateParametersAsync(new ParameterValidationValues(
574602
return;
575603
}
576604
}
605+
if (endpoint.Cached is true && endpoint.CachedParams is not null)
606+
{
607+
if (endpoint.CachedParams.Contains(parameter.ConvertedName) || endpoint.CachedParams.Contains(parameter.ActualName))
608+
{
609+
cacheKeys?.Append(parameter.Value?.ToString() ?? "");
610+
}
611+
}
577612
command.Parameters.Add(parameter);
578613

579614
if (hasNulls is false && parameter.Value == DBNull.Value)
@@ -689,6 +724,13 @@ await options.ValidateParametersAsync(new ParameterValidationValues(
689724
return;
690725
}
691726
}
727+
if (endpoint.Cached is true && endpoint.CachedParams is not null)
728+
{
729+
if (endpoint.CachedParams.Contains(parameter.ConvertedName) || endpoint.CachedParams.Contains(parameter.ActualName))
730+
{
731+
cacheKeys?.Append(parameter.Value?.ToString() ?? "");
732+
}
733+
}
692734
command.Parameters.Add(parameter);
693735

694736
if (hasNulls is false && parameter.Value == DBNull.Value)
@@ -800,6 +842,13 @@ await options.ValidateParametersAsync(new ParameterValidationValues(
800842
return;
801843
}
802844
}
845+
if (endpoint.Cached is true && endpoint.CachedParams is not null)
846+
{
847+
if (endpoint.CachedParams.Contains(parameter.ConvertedName) || endpoint.CachedParams.Contains(parameter.ActualName))
848+
{
849+
cacheKeys?.Append(parameter.Value?.ToString() ?? "");
850+
}
851+
}
803852
command.Parameters.Add(parameter);
804853

805854
if (hasNulls is false && parameter.Value == DBNull.Value)
@@ -902,12 +951,6 @@ await options.ValidateParametersAsync(new ParameterValidationValues(
902951
}
903952
command.CommandText = commandText;
904953

905-
906-
if (shouldLog)
907-
{
908-
NpgsqlRestLogger.LogEndpoint(logger, endpoint, cmdLog?.ToString() ?? "", command.CommandText);
909-
}
910-
911954
if (endpoint.CommandTimeout.HasValue)
912955
{
913956
command.CommandTimeout = endpoint.CommandTimeout.Value;
@@ -948,77 +991,123 @@ await options.ValidateParametersAsync(new ParameterValidationValues(
948991
{
949992
command.AllResultTypesAreUnknown = true;
950993

951-
await using var reader = await command.ExecuteReaderAsync(CommandBehavior.SequentialAccess);
952994
if (routine.ReturnsSet == false && routine.ColumnCount == 1 && routine.ReturnsRecordType is false)
953995
{
954-
if (await reader.ReadAsync())
996+
string? valueResult;
997+
if (endpoint.Cached is true)
955998
{
956-
string? value = reader.GetValue(0) as string;
957-
TypeDescriptor descriptor = routine.ColumnsTypeDescriptor[0];
958-
if (endpoint.ResponseContentType is not null)
999+
if (RoutineCache.Get(cacheKeys?.ToString()!, out valueResult) is false)
9591000
{
960-
context.Response.ContentType = endpoint.NeedsParsing ?
961-
PgConverters.ParseParameters(command.Parameters, endpoint.ResponseContentType) :
962-
endpoint.ResponseContentType;
963-
1001+
await using var reader = await command.ExecuteReaderAsync(CommandBehavior.SequentialAccess);
1002+
if (shouldLog)
1003+
{
1004+
NpgsqlRestLogger.LogEndpoint(logger, endpoint, cmdLog?.ToString() ?? "", command.CommandText);
1005+
}
1006+
if (await reader.ReadAsync())
1007+
{
1008+
valueResult = reader.GetValue(0) as string;
1009+
RoutineCache.AddOrUpdate(cacheKeys?.ToString()!, valueResult);
1010+
}
1011+
else
1012+
{
1013+
logger?.CouldNotReadCommand(command.CommandText, context.Request.Method, context.Request.Path);
1014+
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
1015+
return;
1016+
}
1017+
}
1018+
else
1019+
{
1020+
if (shouldLog)
1021+
{
1022+
cmdLog?.Append(" [from cache] ");
1023+
NpgsqlRestLogger.LogEndpoint(logger, endpoint, cmdLog?.ToString() ?? "", command.CommandText);
1024+
}
9641025
}
965-
else if (descriptor.IsJson || descriptor.IsArray)
1026+
}
1027+
else
1028+
{
1029+
await using var reader = await command.ExecuteReaderAsync(CommandBehavior.SequentialAccess);
1030+
if (shouldLog)
9661031
{
967-
context.Response.ContentType = Application.Json;
1032+
NpgsqlRestLogger.LogEndpoint(logger, endpoint, cmdLog?.ToString() ?? "", command.CommandText);
1033+
}
1034+
if (await reader.ReadAsync())
1035+
{
1036+
valueResult = reader.GetValue(0) as string;
9681037
}
9691038
else
9701039
{
971-
context.Response.ContentType = Text.Plain;
1040+
logger?.CouldNotReadCommand(command.CommandText, context.Request.Method, context.Request.Path);
1041+
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
1042+
return;
9721043
}
973-
if (endpoint.ResponseHeaders.Count > 0)
1044+
}
1045+
1046+
TypeDescriptor descriptor = routine.ColumnsTypeDescriptor[0];
1047+
if (endpoint.ResponseContentType is not null)
1048+
{
1049+
context.Response.ContentType = endpoint.NeedsParsing ?
1050+
PgConverters.ParseParameters(command.Parameters, endpoint.ResponseContentType) :
1051+
endpoint.ResponseContentType;
1052+
1053+
}
1054+
else if (descriptor.IsJson || descriptor.IsArray)
1055+
{
1056+
context.Response.ContentType = Application.Json;
1057+
}
1058+
else
1059+
{
1060+
context.Response.ContentType = Text.Plain;
1061+
}
1062+
if (endpoint.ResponseHeaders.Count > 0)
1063+
{
1064+
foreach ((string headerKey, StringValues headerValue) in endpoint.ResponseHeaders)
9741065
{
975-
foreach ((string headerKey, StringValues headerValue) in endpoint.ResponseHeaders)
976-
{
977-
context.Response.Headers.Append(headerKey,
978-
endpoint.NeedsParsing ?
979-
PgConverters.ParseParameters(command.Parameters, headerValue.ToString()) : headerValue);
980-
}
1066+
context.Response.Headers.Append(headerKey,
1067+
endpoint.NeedsParsing ?
1068+
PgConverters.ParseParameters(command.Parameters, headerValue.ToString()) : headerValue);
9811069
}
1070+
}
9821071

983-
// if raw
984-
if (endpoint.Raw)
1072+
// if raw
1073+
if (endpoint.Raw)
1074+
{
1075+
var span = (valueResult ?? "").AsSpan();
1076+
writer.Advance(Encoding.UTF8.GetBytes(span, writer.GetSpan(Encoding.UTF8.GetMaxByteCount(span.Length))));
1077+
}
1078+
else
1079+
{
1080+
if (descriptor.IsArray && valueResult is not null)
1081+
{
1082+
valueResult = PgConverters.PgArrayToJsonArray(valueResult.AsSpan(), descriptor).ToString();
1083+
}
1084+
if (valueResult is not null)
9851085
{
986-
writer.Advance(Encoding.UTF8.GetBytes((value ?? "").AsSpan(), writer.GetSpan(Encoding.UTF8.GetMaxByteCount((value ?? "").Length))));
1086+
writer.Advance(Encoding.UTF8.GetBytes(valueResult.AsSpan(), writer.GetSpan(Encoding.UTF8.GetMaxByteCount(valueResult.Length))));
9871087
}
9881088
else
9891089
{
990-
if (descriptor.IsArray && value is not null)
1090+
if (endpoint.TextResponseNullHandling == TextResponseNullHandling.NullLiteral)
9911091
{
992-
value = PgConverters.PgArrayToJsonArray(value.AsSpan(), descriptor).ToString();
1092+
writer.Advance(Encoding.UTF8.GetBytes(Consts.Null.AsSpan(), writer.GetSpan(Encoding.UTF8.GetMaxByteCount(Consts.Null.Length))));
9931093
}
994-
if (value is not null)
1094+
else if (endpoint.TextResponseNullHandling == TextResponseNullHandling.NoContent)
9951095
{
996-
writer.Advance(Encoding.UTF8.GetBytes(value.AsSpan(), writer.GetSpan(Encoding.UTF8.GetMaxByteCount(value.Length))));
997-
}
998-
else
999-
{
1000-
if (endpoint.TextResponseNullHandling == TextResponseNullHandling.NullLiteral)
1001-
{
1002-
writer.Advance(Encoding.UTF8.GetBytes(Consts.Null.AsSpan(), writer.GetSpan(Encoding.UTF8.GetMaxByteCount(Consts.Null.Length))));
1003-
}
1004-
else if (endpoint.TextResponseNullHandling == TextResponseNullHandling.NoContent)
1005-
{
1006-
context.Response.StatusCode = (int)HttpStatusCode.NoContent;
1007-
}
1008-
// else OK empty string
1096+
context.Response.StatusCode = (int)HttpStatusCode.NoContent;
10091097
}
1098+
// else OK empty string
10101099
}
1011-
return;
1012-
}
1013-
else
1014-
{
1015-
logger?.CouldNotReadCommand(command.CommandText, context.Request.Method, context.Request.Path);
1016-
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
1017-
return;
10181100
}
1101+
return;
1102+
10191103
}
10201104
else // end if (routine.ReturnsRecord == false)
10211105
{
1106+
await using var reader = await command.ExecuteReaderAsync(CommandBehavior.SequentialAccess);
1107+
if (shouldLog)
1108+
{
1109+
NpgsqlRestLogger.LogEndpoint(logger, endpoint, cmdLog?.ToString() ?? "", command.CommandText);
1110+
}
10221111
if (endpoint.ResponseContentType is not null)
10231112
{
10241113
context.Response.ContentType = endpoint.NeedsParsing ?

0 commit comments

Comments
 (0)