|
12 | 12 | using static NpgsqlRest.ParameterParser;
|
13 | 13 |
|
14 | 14 | using NpgsqlRest.Auth;
|
| 15 | +using System.Linq; |
15 | 16 |
|
16 | 17 | namespace NpgsqlRest;
|
17 | 18 |
|
@@ -209,6 +210,12 @@ public async Task InvokeAsync(HttpContext context)
|
209 | 210 | Dictionary<string, JsonNode?>? bodyDict = null;
|
210 | 211 | string? body = null;
|
211 | 212 | 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 | + } |
212 | 219 |
|
213 | 220 | if (endpoint.RequestParamType == RequestParamType.QueryString)
|
214 | 221 | {
|
@@ -309,6 +316,13 @@ await options.ValidateParametersAsync(new ParameterValidationValues(
|
309 | 316 | return;
|
310 | 317 | }
|
311 | 318 | }
|
| 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 | + } |
312 | 326 | command.Parameters.Add(parameter);
|
313 | 327 |
|
314 | 328 | if (hasNulls is false && parameter.Value == DBNull.Value)
|
@@ -378,6 +392,13 @@ await options.ValidateParametersAsync(new ParameterValidationValues(
|
378 | 392 | return;
|
379 | 393 | }
|
380 | 394 | }
|
| 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 | + } |
381 | 402 | command.Parameters.Add(parameter);
|
382 | 403 |
|
383 | 404 | if (hasNulls is false && parameter.Value == DBNull.Value)
|
@@ -462,6 +483,13 @@ await options.ValidateParametersAsync(new ParameterValidationValues(
|
462 | 483 | return;
|
463 | 484 | }
|
464 | 485 | }
|
| 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 | + } |
465 | 493 | command.Parameters.Add(parameter);
|
466 | 494 |
|
467 | 495 | if (hasNulls is false && parameter.Value == DBNull.Value)
|
@@ -574,6 +602,13 @@ await options.ValidateParametersAsync(new ParameterValidationValues(
|
574 | 602 | return;
|
575 | 603 | }
|
576 | 604 | }
|
| 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 | + } |
577 | 612 | command.Parameters.Add(parameter);
|
578 | 613 |
|
579 | 614 | if (hasNulls is false && parameter.Value == DBNull.Value)
|
@@ -689,6 +724,13 @@ await options.ValidateParametersAsync(new ParameterValidationValues(
|
689 | 724 | return;
|
690 | 725 | }
|
691 | 726 | }
|
| 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 | + } |
692 | 734 | command.Parameters.Add(parameter);
|
693 | 735 |
|
694 | 736 | if (hasNulls is false && parameter.Value == DBNull.Value)
|
@@ -800,6 +842,13 @@ await options.ValidateParametersAsync(new ParameterValidationValues(
|
800 | 842 | return;
|
801 | 843 | }
|
802 | 844 | }
|
| 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 | + } |
803 | 852 | command.Parameters.Add(parameter);
|
804 | 853 |
|
805 | 854 | if (hasNulls is false && parameter.Value == DBNull.Value)
|
@@ -902,12 +951,6 @@ await options.ValidateParametersAsync(new ParameterValidationValues(
|
902 | 951 | }
|
903 | 952 | command.CommandText = commandText;
|
904 | 953 |
|
905 |
| - |
906 |
| - if (shouldLog) |
907 |
| - { |
908 |
| - NpgsqlRestLogger.LogEndpoint(logger, endpoint, cmdLog?.ToString() ?? "", command.CommandText); |
909 |
| - } |
910 |
| - |
911 | 954 | if (endpoint.CommandTimeout.HasValue)
|
912 | 955 | {
|
913 | 956 | command.CommandTimeout = endpoint.CommandTimeout.Value;
|
@@ -948,77 +991,123 @@ await options.ValidateParametersAsync(new ParameterValidationValues(
|
948 | 991 | {
|
949 | 992 | command.AllResultTypesAreUnknown = true;
|
950 | 993 |
|
951 |
| - await using var reader = await command.ExecuteReaderAsync(CommandBehavior.SequentialAccess); |
952 | 994 | if (routine.ReturnsSet == false && routine.ColumnCount == 1 && routine.ReturnsRecordType is false)
|
953 | 995 | {
|
954 |
| - if (await reader.ReadAsync()) |
| 996 | + string? valueResult; |
| 997 | + if (endpoint.Cached is true) |
955 | 998 | {
|
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) |
959 | 1000 | {
|
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 | + } |
964 | 1025 | }
|
965 |
| - else if (descriptor.IsJson || descriptor.IsArray) |
| 1026 | + } |
| 1027 | + else |
| 1028 | + { |
| 1029 | + await using var reader = await command.ExecuteReaderAsync(CommandBehavior.SequentialAccess); |
| 1030 | + if (shouldLog) |
966 | 1031 | {
|
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; |
968 | 1037 | }
|
969 | 1038 | else
|
970 | 1039 | {
|
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; |
972 | 1043 | }
|
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) |
974 | 1065 | {
|
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); |
981 | 1069 | }
|
| 1070 | + } |
982 | 1071 |
|
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) |
985 | 1085 | {
|
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)))); |
987 | 1087 | }
|
988 | 1088 | else
|
989 | 1089 | {
|
990 |
| - if (descriptor.IsArray && value is not null) |
| 1090 | + if (endpoint.TextResponseNullHandling == TextResponseNullHandling.NullLiteral) |
991 | 1091 | {
|
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)))); |
993 | 1093 | }
|
994 |
| - if (value is not null) |
| 1094 | + else if (endpoint.TextResponseNullHandling == TextResponseNullHandling.NoContent) |
995 | 1095 | {
|
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; |
1009 | 1097 | }
|
| 1098 | + // else OK empty string |
1010 | 1099 | }
|
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; |
1018 | 1100 | }
|
| 1101 | + return; |
| 1102 | + |
1019 | 1103 | }
|
1020 | 1104 | else // end if (routine.ReturnsRecord == false)
|
1021 | 1105 | {
|
| 1106 | + await using var reader = await command.ExecuteReaderAsync(CommandBehavior.SequentialAccess); |
| 1107 | + if (shouldLog) |
| 1108 | + { |
| 1109 | + NpgsqlRestLogger.LogEndpoint(logger, endpoint, cmdLog?.ToString() ?? "", command.CommandText); |
| 1110 | + } |
1022 | 1111 | if (endpoint.ResponseContentType is not null)
|
1023 | 1112 | {
|
1024 | 1113 | context.Response.ContentType = endpoint.NeedsParsing ?
|
|
0 commit comments