Skip to content

Commit df9d5ef

Browse files
committed
add BufferRows option
1 parent 0710d32 commit df9d5ef

4 files changed

Lines changed: 115 additions & 4 deletions

File tree

NpgsqlRest/MiddlewareExtension.cs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -698,12 +698,14 @@ await options.ValidateParametersAsync(new ParameterValidationValues(
698698
}
699699
bool first = true;
700700
var routineReturnRecordCount = routine.ColumnCount;
701+
701702
StringBuilder row = new();
703+
ulong rowCount = 0;
702704
while (await reader.ReadAsync())
703705
{
706+
rowCount++;
704707
if (!first)
705708
{
706-
row.Clear();
707709
row.Append(',');
708710
}
709711
else
@@ -796,14 +798,23 @@ await options.ValidateParametersAsync(new ParameterValidationValues(
796798
}
797799
} // end for
798800

799-
await context.Response.WriteAsync(row.ToString());
801+
if (rowCount % options.BufferRows == 0)
802+
{
803+
await context.Response.WriteAsync(row.ToString());
804+
row.Clear();
805+
}
800806
} // end while
807+
808+
if (row.Length > 0)
809+
{
810+
await context.Response.WriteAsync(row.ToString());
811+
}
812+
801813
if (routine.ReturnsSet)
802814
{
803815
await context.Response.WriteAsync("]");
804816
}
805817

806-
807818
await context.Response.CompleteAsync();
808819
return;
809820
} // end if (routine.ReturnsRecord == true)

NpgsqlRest/NpgsqlRestOptions.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ public class NpgsqlRestOptions(
4242
IEnumerable<IEndpointCreateHandler>? endpointCreateHandlers = null,
4343
Action<List<IRoutineSource>>? sourcesCreated = null,
4444
TextResponseNullHandling textResponseNullHandling = TextResponseNullHandling.EmptyString,
45-
QueryStringNullHandling queryStringNullHandling = QueryStringNullHandling.Ignore)
45+
QueryStringNullHandling queryStringNullHandling = QueryStringNullHandling.Ignore,
46+
uint bufferRows = 25)
4647
{
4748
/// <summary>
4849
/// Options for the NpgsqlRest middleware.
@@ -240,5 +241,12 @@ public NpgsqlRestOptions() : this(null)
240241
/// Sets the default behavior on how to pass the `NULL` values with query strings. `EmptyString` empty string values are interpreted as `NULL` values. This limits sending empty strings via query strings. `NullLiteral` literal string values `NULL` (case insensitive) are interpreted as `NULL` values. `Ignore` (default) `NULL` values are ignored, query string receives only empty strings. This option for individual endpoints can be changed with the `EndpointCreated` function callback, or by using comment annotations.
241242
/// </summary>
242243
public QueryStringNullHandling QueryStringNullHandling { get; set; } = queryStringNullHandling;
244+
245+
/// <summary>
246+
/// How many rows are buffered in the response stream. Higher values will increase memory usage and improve performance.
247+
/// This applies only to endpoints returning multiple rows in JSON array.
248+
/// The Default is 25 rows. Set to 0 to disable buffering.
249+
/// </summary>
250+
public ulong BufferRows { get; set; } = bufferRows;
243251
internal List<IRoutineSource> RoutineSources { get; set; } = [new RoutineSource()];
244252
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
#pragma warning disable CS8602 // Dereference of a possibly null reference.
2+
namespace NpgsqlRestTests;
3+
4+
public static partial class Database
5+
{
6+
public static void ReturnLongTableTests()
7+
{
8+
script.Append("""
9+
create function case_get_long_table1(_records int)
10+
returns table (
11+
int_field int,
12+
text_field text
13+
)
14+
language sql
15+
as
16+
$_$
17+
select
18+
i, i::text
19+
from
20+
generate_series(1, _records) as i;
21+
$_$;
22+
""");
23+
}
24+
}
25+
26+
27+
[Collection("TestFixture")]
28+
public class ReturnLongTableTests(TestFixture test)
29+
{
30+
[Fact]
31+
public async Task Test_case_get_long_table1_return10()
32+
{
33+
using var result = await test.Client.GetAsync("/api/case-get-long-table1/?records=10");
34+
var response = await result.Content.ReadAsStringAsync();
35+
36+
result?.StatusCode.Should().Be(HttpStatusCode.OK);
37+
result?.Content?.Headers?.ContentType?.MediaType.Should().Be("application/json");
38+
39+
var array = JsonNode.Parse(response).AsArray();
40+
array.Count.Should().Be(10);
41+
}
42+
43+
[Fact]
44+
public async Task Test_case_get_long_table1_return25()
45+
{
46+
using var result = await test.Client.GetAsync("/api/case-get-long-table1/?records=25");
47+
var response = await result.Content.ReadAsStringAsync();
48+
49+
result?.StatusCode.Should().Be(HttpStatusCode.OK);
50+
result?.Content?.Headers?.ContentType?.MediaType.Should().Be("application/json");
51+
52+
var array = JsonNode.Parse(response).AsArray();
53+
array.Count.Should().Be(25);
54+
}
55+
56+
[Fact]
57+
public async Task Test_case_get_long_table1_return75()
58+
{
59+
using var result = await test.Client.GetAsync("/api/case-get-long-table1/?records=75");
60+
var response = await result.Content.ReadAsStringAsync();
61+
62+
result?.StatusCode.Should().Be(HttpStatusCode.OK);
63+
result?.Content?.Headers?.ContentType?.MediaType.Should().Be("application/json");
64+
65+
var array = JsonNode.Parse(response).AsArray();
66+
array.Count.Should().Be(75);
67+
}
68+
69+
[Fact]
70+
public async Task Test_case_get_long_table1_return0()
71+
{
72+
using var result = await test.Client.GetAsync("/api/case-get-long-table1/?records=0");
73+
var response = await result.Content.ReadAsStringAsync();
74+
75+
result?.StatusCode.Should().Be(HttpStatusCode.OK);
76+
result?.Content?.Headers?.ContentType?.MediaType.Should().Be("application/json");
77+
78+
var array = JsonNode.Parse(response).AsArray();
79+
array.Count.Should().Be(0);
80+
}
81+
}

options.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -540,3 +540,14 @@ Sets the default behavior on how to pass the `NULL` values with query strings:
540540

541541
This option for individual endpoints can be changed with the `EndpointCreated` function callback, or by using comment annotations.
542542

543+
## BufferRows
544+
545+
- Type: `ulong`
546+
- Default: `25`
547+
548+
How many rows are buffered in the response stream?
549+
Higher values will increase memory usage and improve performance.
550+
551+
This applies only to endpoints returning multiple rows in a JSON array (returning tables or records).
552+
553+
The Default is 25 rows. Set to 0 to disable buffering.

0 commit comments

Comments
 (0)