Skip to content

Commit 5fe7b9d

Browse files
committed
Default ls and query to capped results
Fixes #20
1 parent 898e205 commit 5fe7b9d

7 files changed

Lines changed: 154 additions & 54 deletions

File tree

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// ------------------------------------------------------------
2+
// Copyright (c) Microsoft Corporation. All rights reserved.
3+
// ------------------------------------------------------------
4+
5+
namespace CosmosShell.Tests.CommandTests;
6+
7+
using Azure.Data.Cosmos.Shell.Util;
8+
9+
public class ResultLimitTests
10+
{
11+
[Fact]
12+
public void ResolveMaxItemCount_UsesDefaultWhenNotSpecified()
13+
{
14+
Assert.Equal(ResultLimit.DefaultMaxItemCount, ResultLimit.ResolveMaxItemCount(null));
15+
}
16+
17+
[Fact]
18+
public void ResolveMaxItemCount_UsesExplicitPositiveValue()
19+
{
20+
Assert.Equal(25, ResultLimit.ResolveMaxItemCount(25));
21+
}
22+
23+
[Theory]
24+
[InlineData(0)]
25+
[InlineData(-1)]
26+
[InlineData(-100)]
27+
public void ResolveMaxItemCount_UsesUnlimitedForZeroOrNegativeValues(int requestedMax)
28+
{
29+
Assert.Null(ResultLimit.ResolveMaxItemCount(requestedMax));
30+
}
31+
32+
[Theory]
33+
[InlineData(99, 100, false)]
34+
[InlineData(100, 100, true)]
35+
[InlineData(101, 100, true)]
36+
public void IsLimitReached_ReturnsExpectedResult(int count, int limit, bool expected)
37+
{
38+
Assert.Equal(expected, ResultLimit.IsLimitReached(count, limit));
39+
}
40+
41+
[Fact]
42+
public void IsLimitReached_ReturnsFalseWhenUnlimited()
43+
{
44+
Assert.False(ResultLimit.IsLimitReached(1000, null));
45+
}
46+
}

CosmosDBShell/Azure.Data.Cosmos.Shell.Commands/ListCommand.cs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ namespace Azure.Data.Cosmos.Shell.Commands;
1616
[CosmosExample("ls", Description = "List all databases, containers, or items depending on current context")]
1717
[CosmosExample("ls *Test*", Description = "Filter results using wildcard pattern")]
1818
[CosmosExample("ls -max=10", Description = "Limit results to maximum of 10 items")]
19+
[CosmosExample("ls -max=0", Description = "List all matching items without a limit")]
1920
[CosmosExample("ls --database=MyDB --container=Products", Description = "List items from specific database and container")]
2021
[CosmosExample("ls \"*active*\" --format=table", Description = "Filter and display results in table format")]
2122
[CosmosExample("ls active --key=status", Description = "Filter items where 'status' field equals 'active'")]
@@ -142,9 +143,10 @@ private async Task<CommandState> ListContainerItemsAsync(CosmosClient client, st
142143
var container = client.GetDatabase(databaseName).GetContainer(containerName);
143144
AnsiConsole.MarkupLine(MessageService.GetString("command-ls-container", new Dictionary<string, object> { { "container", Theme.ContainerNamePromt(container.Id) } }));
144145
var opt = new QueryRequestOptions();
145-
if (this.Max.HasValue)
146+
var effectiveMaxItemCount = ResultLimit.ResolveMaxItemCount(this.Max);
147+
if (effectiveMaxItemCount.HasValue)
146148
{
147-
opt.MaxItemCount = this.Max.Value;
149+
opt.MaxItemCount = effectiveMaxItemCount.Value;
148150
}
149151

150152
var containerResponse = await container.ReadContainerAsync(cancellationToken: token);
@@ -160,6 +162,7 @@ private async Task<CommandState> ListContainerItemsAsync(CosmosClient client, st
160162
var returnState = new CommandState();
161163
returnState.SetFormat(this.OutputFormat ?? Environment.GetEnvironmentVariable("COSMOS_SHELL_FORMAT"));
162164
var list = new List<JsonElement>();
165+
var limitReached = false;
163166
while (feedIterator.HasMoreResults)
164167
{
165168
var response = await feedIterator.ReadNextAsync(token);
@@ -182,15 +185,26 @@ private async Task<CommandState> ListContainerItemsAsync(CosmosClient client, st
182185
list.Add(element);
183186
}
184187

185-
if (opt.MaxItemCount >= 0 && list.Count >= opt.MaxItemCount)
188+
if (ResultLimit.IsLimitReached(list.Count, effectiveMaxItemCount))
186189
{
190+
limitReached = true;
187191
break;
188192
}
189193
}
194+
195+
if (limitReached)
196+
{
197+
break;
198+
}
190199
}
191200

192201
returnState.Result = new ShellJson(JsonSerializer.SerializeToElement(new { items = list }));
193202
AnsiConsole.MarkupLine(MessageService.GetString("command-ls-found_items", new Dictionary<string, object> { { "count", "[white]" + list.Count + "[/]" } }));
203+
if (limitReached && effectiveMaxItemCount.HasValue)
204+
{
205+
AnsiConsole.MarkupLine(MessageService.GetString("command-results-limit_reached", new Dictionary<string, object> { { "count", effectiveMaxItemCount.Value } }));
206+
}
207+
194208
return returnState;
195209
}
196210

CosmosDBShell/Azure.Data.Cosmos.Shell.Commands/QueryCommand.cs

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ internal enum MetricTarget
2525
[CosmosExample("query \"SELECT * FROM c\"", Description = "Query all documents from container")]
2626
[CosmosExample("query \"SELECT * FROM c WHERE c.status = 'active'\"", Description = "Query with filter condition")]
2727
[CosmosExample("query \"SELECT c.id, c.name FROM c\" -max=10", Description = "Query specific fields with result limit")]
28+
[CosmosExample("query \"SELECT * FROM c\" -max=0", Description = "Query all matching documents without a limit")]
2829
[CosmosExample("query \"SELECT * FROM c\" -metrics=Display", Description = "Query with performance metrics displayed")]
2930
[CosmosExample("query \"SELECT * FROM c\" --database=MyDB --container=Products", Description = "Query specific database and container")]
3031
[McpAnnotation(
@@ -268,6 +269,12 @@ private async Task<CommandState> ExecuteQueryAsync(Container container, ShellInt
268269
PopulateIndexMetrics = true,
269270
};
270271

272+
var effectiveMaxItemCount = ResultLimit.ResolveMaxItemCount(this.Max);
273+
if (effectiveMaxItemCount.HasValue)
274+
{
275+
options.MaxItemCount = effectiveMaxItemCount.Value;
276+
}
277+
271278
if (this.Bucket.HasValue)
272279
{
273280
options.ThroughputBucket = this.Bucket.Value;
@@ -279,15 +286,7 @@ private async Task<CommandState> ExecuteQueryAsync(Container container, ShellInt
279286
}
280287

281288
using var feedIterator = container.GetItemQueryStreamIterator(this.Query, null, options);
282-
283-
var opt = new QueryRequestOptions
284-
{
285-
PopulateIndexMetrics = true,
286-
};
287-
if (this.Max.HasValue)
288-
{
289-
opt.MaxItemCount = this.Max.Value;
290-
}
289+
var limitReached = false;
291290

292291
while (feedIterator.HasMoreResults)
293292
{
@@ -336,7 +335,7 @@ private async Task<CommandState> ExecuteQueryAsync(Container container, ShellInt
336335
ShellInterpreter.WriteLine(MessageService.GetString("command-query-fetched", new Dictionary<string, object> { { "count", queryDocument.RootElement.GetProperty("_count").ToString() } }));
337336
var queryMetrics = response.Diagnostics.GetQueryMetrics();
338337
AnsiConsole.MarkupLine(MessageService.GetString("command-query-request_charge", new Dictionary<string, object> { { "charge", queryMetrics.TotalRequestCharge.ToString() } }));
339-
aggregatedDocuments = CollectDocuments(aggregatedDocuments, queryDocument.RootElement.GetProperty("Documents"), opt.MaxItemCount);
338+
aggregatedDocuments = CollectDocuments(aggregatedDocuments, queryDocument.RootElement.GetProperty("Documents"), effectiveMaxItemCount);
340339

341340
if (this.Metrics == MetricTarget.File)
342341
{
@@ -470,12 +469,18 @@ private async Task<CommandState> ExecuteQueryAsync(Container container, ShellInt
470469
GeneratePlainResultDocument(returnState, aggregatedDocuments);
471470
}
472471

473-
if (opt.MaxItemCount >= 0 && aggregatedDocuments.Count >= opt.MaxItemCount)
472+
if (ResultLimit.IsLimitReached(aggregatedDocuments.Count, effectiveMaxItemCount))
474473
{
474+
limitReached = true;
475475
break;
476476
}
477477
}
478478

479+
if (limitReached && effectiveMaxItemCount.HasValue)
480+
{
481+
AnsiConsole.MarkupLine(MessageService.GetString("command-results-limit_reached", new Dictionary<string, object> { { "count", effectiveMaxItemCount.Value } }));
482+
}
483+
479484
return returnState;
480485
}
481486
catch (OperationCanceledException e)
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// ------------------------------------------------------------
2+
// Copyright (c) Microsoft Corporation. All rights reserved.
3+
// ------------------------------------------------------------
4+
5+
namespace Azure.Data.Cosmos.Shell.Util;
6+
7+
internal static class ResultLimit
8+
{
9+
public const int DefaultMaxItemCount = 100;
10+
11+
public static int? ResolveMaxItemCount(int? requestedMax, int defaultMaxItemCount = DefaultMaxItemCount)
12+
{
13+
if (!requestedMax.HasValue)
14+
{
15+
return defaultMaxItemCount;
16+
}
17+
18+
return requestedMax.Value <= 0 ? null : requestedMax.Value;
19+
}
20+
21+
public static bool IsLimitReached(int count, int? effectiveMaxItemCount)
22+
{
23+
return effectiveMaxItemCount.HasValue && count >= effectiveMaxItemCount.Value;
24+
}
25+
}

CosmosDBShell/lang/en.ftl

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ command-rm-no-matches = No items matched the pattern '{ $pattern }' for key '{ $
9292
9393
command-query-description = Executes a query and returns the result
9494
command-query-description-query = The query to execute
95-
command-query-description-max = Maximum Number of items
95+
command-query-description-max = Maximum number of items returned when querying items. Defaults to 100 if omitted. Use 0 or a negative value for no limit.
9696
command-query-description-metrics = Show query metrics (possible values: Display (default), File (output to data json/csv))
9797
command-query-description-bucket = The throughput bucket to use for the query
9898
command-query-description-format = Output format (json, table, csv)
@@ -182,14 +182,15 @@ command-indexpolicy-error_invalid_policy = Invalid indexing policy JSON. Please
182182
183183
command-ls-description = List current container.
184184
command-ls-description-filter = The filter pattern.
185-
command-ls-description-max = Maximum Number of items
185+
command-ls-description-max = Maximum number of items returned when listing container items. Defaults to 100 if omitted. Use 0 or a negative value for no limit.
186186
command-ls-description-format = { command-query-description-format }
187187
command-ls-description-recursive = List items recursively
188188
command-ls-description-database = The database to list from
189189
command-ls-description-container = The container to list items from
190190
command-ls-description-key = The property to match against (default: id)
191191
command-ls-container = Container { $container }
192192
command-ls-found_items = found { $count } items.
193+
command-results-limit_reached = Results limited to { $count } items. Use --max to change the limit or --max 0 for no limit.
193194
194195
command-jq-description = Commandline JSON processor
195196
command-jq-description-args = Arguments for the jq command

0 commit comments

Comments
 (0)