Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement maximum_results option #1215

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions watchman/InMemoryView.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -588,6 +588,10 @@ void InMemoryView::timeGenerator(const Query* query, QueryContext* ctx) const {
continue;
}

if (ctx->num_results_over_maximum > 0) {
continue;
}

w_query_process_file(
query, ctx, std::make_unique<InMemoryFileResult>(f, caches_));
}
Expand Down Expand Up @@ -899,6 +903,10 @@ void InMemoryView::allFilesGenerator(const Query* query, QueryContext* ctx)
continue;
}

if (ctx->num_results_over_maximum > 0) {
continue;
}

w_query_process_file(
query, ctx, std::make_unique<InMemoryFileResult>(f, caches_));
}
Expand Down
3 changes: 3 additions & 0 deletions watchman/cmds/query.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ static UntypedResponse cmd_query(Client* client, const json_ref& args) {
if (res.savedStateInfo) {
response.set("saved-state-info", std::move(*res.savedStateInfo));
}
if (res.exceededMaximumResults) {
response.set("exceededMaximumResults", json_boolean(res.exceededMaximumResults));
}

add_root_warnings_to_response(response, root);

Expand Down
4 changes: 4 additions & 0 deletions watchman/cmds/since.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ static UntypedResponse cmd_since(Client* client, const json_ref& args) {
if (res.savedStateInfo) {
response.set("saved-state-info", std::move(*res.savedStateInfo));
}
if (res.exceededMaximumResults) {
response.set("exceededMaximumResults", json_boolean(res.exceededMaximumResults));
}


add_root_warnings_to_response(response, root);
return response;
Expand Down
10 changes: 10 additions & 0 deletions watchman/query/Query.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,16 @@ struct Query {
bool dedup_results = false;
uint32_t bench_iterations = 0;

/**
* If provided, queries with more than `maximum_results` results will
* return an empty results list and a flag indicating the response
* has been truncated.
*
* This is similar to the `empty_on_fresh_instance` option, but for
* all back-ends and responses.
*/
std::optional<uint32_t> maximum_results = std::nullopt;

/**
* Optional full path to relative root, without and with trailing slash.
*/
Expand Down
7 changes: 7 additions & 0 deletions watchman/query/QueryContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,13 @@ struct QueryContext : QueryContextBase {

// How many times we suppressed a result due to dedup checking
uint32_t num_deduped{0};
/**
* How many results were dropped due to `maximum_results` being exceeded.
*
* Note this may be at most 1 due to generators short-circuiting work once the limit
* is exceeded.
*/
uint32_t num_results_over_maximum{0};

// Disable fresh instance queries
bool disableFreshInstance{false};
Expand Down
4 changes: 4 additions & 0 deletions watchman/query/QueryResult.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ struct QueryResult {
uint32_t stateTransCountAtStartOfQuery;
std::optional<json_ref> savedStateInfo;
QueryDebugInfo debugInfo;
/**
* True if the result has been truncated due to the `maximum_results` query option.
*/
bool exceededMaximumResults = false;
};

} // namespace watchman
10 changes: 10 additions & 0 deletions watchman/query/eval.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,13 @@ void w_query_process_file(
}
}

// Handle `Query::maximum_results` in order to avoid handling a huge list of results.
// Once this is exceeded, all work could stop.
if (ctx->query->maximum_results && ctx->resultsArray.size() >= ctx->query->maximum_results) {
ctx->num_results_over_maximum += 1;
return;
}

ctx->maybeRender(std::move(ctx->file));
}

Expand Down Expand Up @@ -225,6 +232,7 @@ static void execute_common(
auto meta = json_object({
{"fresh_instance", json_boolean(res->isFreshInstance)},
{"num_deduped", json_integer(ctx->num_deduped)},
{"exceeded_maximum", json_boolean(ctx->num_results_over_maximum > 0)},
{"num_results", json_integer(ctx->resultsArray.size())},
{"num_walked", json_integer(ctx->getNumWalked())},
});
Expand All @@ -237,6 +245,8 @@ static void execute_common(

res->resultsArray = ctx->renderResults();
res->dedupedFileNames = std::move(ctx->dedup);

res->exceededMaximumResults = ctx->num_results_over_maximum > 0;
}

// Capability indicating support for scm-aware since queries
Expand Down
23 changes: 23 additions & 0 deletions watchman/query/parse.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,13 @@ void parse_empty_on_fresh_instance(Query* res, const json_ref& query) {
parse_bool_param(query, "empty_on_fresh_instance", false);
}

void parse_maximum_results(Query* res, const json_ref& query) {
auto maximum_results = query.get_optional("maximum_results");
if (maximum_results) {
res->maximum_results = parse_nonnegative_integer("maximum_results", maximum_results.value());
}
}

void parse_always_include_directories(Query* res, const json_ref& query) {
res->alwaysIncludeDirectories =
parse_bool_param(query, "always_include_directories", false);
Expand Down Expand Up @@ -274,6 +281,7 @@ std::shared_ptr<Query> parseQuery(
parse_lock_timeout(res, query);
parse_relative_root(root, res, query);
parse_empty_on_fresh_instance(res, query);
parse_maximum_results(res, query);
parse_fail_if_no_saved_state(res, query);
parse_omit_changed_files(res, query);
parse_always_include_directories(res, query);
Expand Down Expand Up @@ -333,6 +341,9 @@ void w_query_legacy_field_list(QueryFieldList* flist) {
// Translate from the legacy array into the new style, then
// delegate to the main parser.
// We build a big anyof expression
//
// If the old format query has an object that object's fields are merged into the new style:
// For example: `["since", "target", "spec", { "more_fields": "here" }]`
std::shared_ptr<Query> parseQueryLegacy(
const std::shared_ptr<Root>& root,
const json_ref& args,
Expand All @@ -355,6 +366,14 @@ std::shared_ptr<Query> parseQueryLegacy(
auto& args_array = args.array();

for (i = start; i < args_array.size(); i++) {
if (args_array[i].isObject()) {
const auto& object = args_array[i];
for (const auto& entry : object.object()) {
query_obj.set(entry.first, json_ref(entry.second));
}
continue;
}

const char* arg = json_string_value(args_array[i]);
if (!arg) {
/* not a string value! */
Expand All @@ -364,6 +383,10 @@ std::shared_ptr<Query> parseQueryLegacy(
}

for (i = start; i < json_array_size(args); i++) {
if (!args_array[i].isString()) {
continue;
}

const char* arg = json_string_value(args_array[i]);
if (!strcmp(arg, "--")) {
i++;
Expand Down