@@ -297,6 +297,7 @@ await Options.ValidateParametersAsync(new ParameterValidationValues(
297297 {
298298 if ( endpoint . CachedParams . Contains ( parameter . ConvertedName ) || endpoint . CachedParams . Contains ( parameter . ActualName ) )
299299 {
300+ cacheKeys ? . Append ( NpgsqlRestParameter . GetCacheKeySeparator ( ) ) ;
300301 cacheKeys ? . Append ( parameter . GetCacheStringValue ( ) ) ;
301302 }
302303 }
@@ -384,6 +385,7 @@ await Options.ValidateParametersAsync(new ParameterValidationValues(
384385 {
385386 if ( endpoint . CachedParams . Contains ( parameter . ConvertedName ) || endpoint . CachedParams . Contains ( parameter . ActualName ) )
386387 {
388+ cacheKeys ? . Append ( NpgsqlRestParameter . GetCacheKeySeparator ( ) ) ;
387389 cacheKeys ? . Append ( parameter . GetCacheStringValue ( ) ) ;
388390 }
389391 }
@@ -529,6 +531,7 @@ await Options.ValidateParametersAsync(new ParameterValidationValues(
529531 {
530532 if ( endpoint . CachedParams . Contains ( parameter . ConvertedName ) || endpoint . CachedParams . Contains ( parameter . ActualName ) )
531533 {
534+ cacheKeys ? . Append ( NpgsqlRestParameter . GetCacheKeySeparator ( ) ) ;
532535 cacheKeys ? . Append ( parameter . GetCacheStringValue ( ) ) ;
533536 }
534537 }
@@ -675,6 +678,7 @@ await Options.ValidateParametersAsync(new ParameterValidationValues(
675678 {
676679 if ( endpoint . CachedParams . Contains ( parameter . ConvertedName ) || endpoint . CachedParams . Contains ( parameter . ActualName ) )
677680 {
681+ cacheKeys ? . Append ( NpgsqlRestParameter . GetCacheKeySeparator ( ) ) ;
678682 cacheKeys ? . Append ( parameter . GetCacheStringValue ( ) ) ;
679683 }
680684 }
@@ -849,6 +853,7 @@ await Options.ValidateParametersAsync(new ParameterValidationValues(
849853 {
850854 if ( endpoint . CachedParams . Contains ( parameter . ConvertedName ) || endpoint . CachedParams . Contains ( parameter . ActualName ) )
851855 {
856+ cacheKeys ? . Append ( NpgsqlRestParameter . GetCacheKeySeparator ( ) ) ;
852857 cacheKeys ? . Append ( parameter . GetCacheStringValue ( ) ) ;
853858 }
854859 }
@@ -993,6 +998,7 @@ await Options.ValidateParametersAsync(new ParameterValidationValues(
993998 {
994999 if ( endpoint . CachedParams . Contains ( parameter . ConvertedName ) || endpoint . CachedParams . Contains ( parameter . ActualName ) )
9951000 {
1001+ cacheKeys ? . Append ( NpgsqlRestParameter . GetCacheKeySeparator ( ) ) ;
9961002 cacheKeys ? . Append ( parameter . GetCacheStringValue ( ) ) ;
9971003 }
9981004 }
@@ -1506,11 +1512,39 @@ await command.ExecuteNonQueryWithRetryAsync(
15061512 }
15071513 else // end if (routine.ReturnsRecord == false)
15081514 {
1515+ var binary = routine . ColumnsTypeDescriptor . Length == 1 && routine . ColumnsTypeDescriptor [ 0 ] . IsBinary ;
1516+
1517+ // Check cache for records/sets (but not for binary or raw mode)
1518+ var canCacheRecordsAndSets = Options . CacheOptions . DefaultRoutineCache is not null
1519+ && endpoint . Cached is true
1520+ && binary is false
1521+ && endpoint . Raw is false ;
1522+
1523+ if ( canCacheRecordsAndSets )
1524+ {
1525+ if ( Options . CacheOptions . DefaultRoutineCache ! . Get ( endpoint , cacheKeys ? . ToString ( ) ! , out var cachedResult ) )
1526+ {
1527+ if ( shouldLog )
1528+ {
1529+ cmdLog ? . AppendLine ( "/* from cache */" ) ;
1530+ NpgsqlRestLogger . LogEndpoint ( endpoint , cmdLog ? . ToString ( ) ?? "" , commandText ?? "" ) ;
1531+ }
1532+
1533+ if ( context . Response . ContentType is null )
1534+ {
1535+ context . Response . ContentType = Application . Json ;
1536+ }
1537+
1538+ var cachedSpan = ( cachedResult as string ?? "" ) . AsSpan ( ) ;
1539+ writer . Advance ( Encoding . UTF8 . GetBytes ( cachedSpan , writer . GetSpan ( Encoding . UTF8 . GetMaxByteCount ( cachedSpan . Length ) ) ) ) ;
1540+ return ;
1541+ }
1542+ }
1543+
15091544 if ( await PrepareCommand ( connection , command , commandText , context , endpoint , true ) is false )
15101545 {
15111546 return ;
15121547 }
1513- var binary = routine . ColumnsTypeDescriptor . Length == 1 && routine . ColumnsTypeDescriptor [ 0 ] . IsBinary ;
15141548 await using var reader = await command . ExecuteReaderWithRetryAsync (
15151549 CommandBehavior . SequentialAccess ,
15161550 endpoint . RetryStrategy ,
@@ -1532,9 +1566,15 @@ await command.ExecuteNonQueryWithRetryAsync(
15321566 }
15331567 }
15341568
1569+ // For caching, we need to buffer the entire response
1570+ StringBuilder ? cacheBuffer = canCacheRecordsAndSets ? new ( ) : null ;
1571+ var maxCacheableRows = Options . CacheOptions . MaxCacheableRows ;
1572+ var shouldCache = canCacheRecordsAndSets ;
1573+
15351574 if ( routine . ReturnsSet && endpoint . Raw is false && binary is false )
15361575 {
15371576 writer . Advance ( Encoding . UTF8 . GetBytes ( Consts . OpenBracket . ToString ( ) . AsSpan ( ) , writer . GetSpan ( Encoding . UTF8 . GetMaxByteCount ( 1 ) ) ) ) ;
1577+ cacheBuffer ? . Append ( Consts . OpenBracket ) ;
15381578 }
15391579
15401580 bool first = true ;
@@ -1700,8 +1740,20 @@ await command.ExecuteNonQueryWithRetryAsync(
17001740 }
17011741 } // end for
17021742
1743+ // Check if we've exceeded the cacheable row limit
1744+ if ( shouldCache && maxCacheableRows . HasValue && rowCount > ( ulong ) maxCacheableRows . Value )
1745+ {
1746+ shouldCache = false ;
1747+ cacheBuffer = null ; // Release memory
1748+ }
1749+
17031750 if ( bufferRows != 1 && rowCount % bufferRows == 0 )
17041751 {
1752+ // Append to cache buffer before clearing row
1753+ if ( shouldCache )
1754+ {
1755+ cacheBuffer ? . Append ( row ) ;
1756+ }
17051757 WriteStringBuilderToWriter ( row , writer ) ;
17061758 await writer . FlushAsync ( ) ;
17071759 row . Clear ( ) ;
@@ -1716,12 +1768,24 @@ await command.ExecuteNonQueryWithRetryAsync(
17161768 {
17171769 if ( row . Length > 0 )
17181770 {
1771+ // Append remaining rows to cache buffer
1772+ if ( shouldCache )
1773+ {
1774+ cacheBuffer ? . Append ( row ) ;
1775+ }
17191776 WriteStringBuilderToWriter ( row , writer ) ;
17201777 await writer . FlushAsync ( ) ;
17211778 }
17221779 if ( routine . ReturnsSet && endpoint . Raw is false )
17231780 {
17241781 writer . Advance ( Encoding . UTF8 . GetBytes ( Consts . CloseBracket . ToString ( ) . AsSpan ( ) , writer . GetSpan ( Encoding . UTF8 . GetMaxByteCount ( 1 ) ) ) ) ;
1782+ cacheBuffer ? . Append ( Consts . CloseBracket ) ;
1783+ }
1784+
1785+ // Store in cache if within limits
1786+ if ( shouldCache && cacheBuffer is not null )
1787+ {
1788+ Options . CacheOptions . DefaultRoutineCache ? . AddOrUpdate ( endpoint , cacheKeys ? . ToString ( ) ! , cacheBuffer . ToString ( ) ) ;
17251789 }
17261790 }
17271791 return ;
0 commit comments