QueryResult w_query_execute()

in watchman/query/eval.cpp [241:453]


QueryResult w_query_execute(
    const Query* query,
    const std::shared_ptr<Root>& root,
    QueryGenerator generator,
    SavedStateFactory savedStateFactory) {
  QueryResult res;
  ClockSpec resultClock(ClockPosition{});
  bool disableFreshInstance{false};
  auto requestId = query->request_id;

  PerfSample sample("query_execute");
  if (requestId && !requestId.empty()) {
    log(DBG, "request_id = ", requestId, "\n");
    sample.add_meta("request_id", w_string_to_json(requestId));
  }

  // We want to check this before we sync, as the SCM may generate changes
  // in the filesystem when running the underlying commands to query it.
  if (query->since_spec && query->since_spec->hasScmParams()) {
    auto scm = root->view()->getSCM();

    if (!scm) {
      throw QueryExecError("This root does not support SCM-aware queries.");
    }

    // Populate transition counter at start of query. This allows us to
    // determine if SCM operations ocurred concurrent with query execution.
    res.stateTransCountAtStartOfQuery = root->stateTransCount.load();
    resultClock.scmMergeBaseWith = query->since_spec->scmMergeBaseWith;
    resultClock.scmMergeBase =
        scm->mergeBaseWith(resultClock.scmMergeBaseWith, requestId);
    // Always update the saved state storage type and key, but conditionally
    // update the saved state commit id below based on wether the mergebase has
    // changed.
    if (query->since_spec->hasSavedStateParams()) {
      resultClock.savedStateStorageType =
          query->since_spec->savedStateStorageType;
      resultClock.savedStateConfig = query->since_spec->savedStateConfig;
    }

    if (resultClock.scmMergeBase != query->since_spec->scmMergeBase) {
      // The merge base is different, so on the assumption that a lot of
      // things have changed between the prior and current state of
      // the world, we're just going to ask the SCM to tell us about
      // the changes, then we're going to feed that change list through
      // a simpler watchman query.
      auto modifiedMergebase = resultClock.scmMergeBase;
      if (query->since_spec->hasSavedStateParams()) {
        // Find the most recent saved state to the new mergebase and return
        // changed files since that saved state, if available.
        auto savedStateInterface = savedStateFactory(
            query->since_spec->savedStateStorageType,
            query->since_spec->savedStateConfig,
            scm,
            root->config,
            [root](PerfSample& sample) {
              root->addPerfSampleMetadata(sample);
            });
        auto savedStateResult = savedStateInterface->getMostRecentSavedState(
            resultClock.scmMergeBase);
        res.savedStateInfo = savedStateResult.savedStateInfo;
        if (savedStateResult.commitId) {
          resultClock.savedStateCommitId = savedStateResult.commitId;
          // Modify the mergebase to be the saved state mergebase so we can
          // return changed files since the saved state.
          modifiedMergebase = savedStateResult.commitId;
        } else {
          // Setting the saved state commit id to the empty string alerts the
          // client that the mergebase changed, yet no saved state was
          // available. The changed files list will be relative to the prior
          // clock as if scm-aware queries were not being used at all, to ensure
          // clients have all changed files they need.
          resultClock.savedStateCommitId = w_string();
          modifiedMergebase = nullptr;
        }
      }
      // If the modified mergebase is null then we had no saved state available
      // so we need to fall back to the normal behavior of returning all changes
      // since the prior clock, so we should not update the generator in that
      // case.
      if (modifiedMergebase) {
        disableFreshInstance = true;
        generator = [root, modifiedMergebase, requestId](
                        const Query* q,
                        const std::shared_ptr<Root>& r,
                        QueryContext* c) {
          auto changedFiles =
              root->view()->getSCM()->getFilesChangedSinceMergeBaseWith(
                  modifiedMergebase, requestId);

          auto pathList = json_array_of_size(changedFiles.size());
          for (auto& f : changedFiles) {
            json_array_append_new(pathList, w_string_to_json(f));
          }

          auto spec = r->view()->getMostRecentRootNumberAndTickValue();
          ClockStamp clock{spec.ticks, ::time(nullptr)};
          for (auto& pathEntry : pathList.array()) {
            auto path = json_to_w_string(pathEntry);

            auto fullPath = w_string::pathCat({r->root_path, path});
            if (!c->fileMatchesRelativeRoot(fullPath)) {
              continue;
            }
            // Note well!  At the time of writing the LocalFileResult class
            // assumes that removed entries must have been regular files.
            // We don't have enough information returned from
            // getFilesChangedSinceMergeBaseWith() to distinguish between
            // deleted files and deleted symlinks.  Also, it is not possible
            // to see a directory returned from that call; we're only going
            // to enumerate !dirs for this case.
            w_query_process_file(
                q,
                c,
                std::make_unique<LocalFileResult>(
                    fullPath, clock, r->case_sensitive));
          }
        };
      } else if (query->fail_if_no_saved_state) {
        throw QueryExecError(
            "The merge base changed but no corresponding saved state was "
            "found for the new merge base. fail_if_no_saved_state was set "
            "in the query so treating this as an error");
      }
    } else {
      if (query->since_spec->hasSavedStateParams()) {
        // If the mergebase has not changed, then preserve the input value for
        // the saved state commit id so it will be accurate in subscriptions.
        resultClock.savedStateCommitId = query->since_spec->savedStateCommitId;
      }
    }
  }
  // We should skip asking SCM for the changed files if the query
  // indicated to omit those. To do so, lets just make an empty
  // generator.
  if (query->omit_changed_files) {
    generator = [](const Query*, const std::shared_ptr<Root>&, QueryContext*) {
    };
  }
  QueryContext ctx{query, root, disableFreshInstance};

  // Track the query against the root.
  // This is to enable the `watchman debug-status` diagnostic command.
  // It promises only to read the read-only fields in ctx and ctx.query.
  root->queries.wlock()->insert(&ctx);
  SCOPE_EXIT {
    root->queries.wlock()->erase(&ctx);
  };
  if (query->settle_timeouts) {
    auto future = root->waitForSettle(query->settle_timeouts->settle_period);
    try {
      std::move(future).get(query->settle_timeouts->settle_timeout);
    } catch (const folly::FutureTimeout&) {
      throw QueryExecError(fmt::format(
          "waitForSettle: timed out waiting for settle {} in {}",
          query->settle_timeouts->settle_period,
          query->settle_timeouts->settle_timeout));
    }
  }
  if (query->sync_timeout.count()) {
    ctx.state = QueryContextState::WaitingForCookieSync;
    ctx.stopWatch.reset();
    try {
      auto result = root->syncToNow(query->sync_timeout);
      res.debugInfo.cookieFileNames = std::move(result.cookieFileNames);
    } catch (const std::exception& exc) {
      throw QueryExecError("synchronization failed: ", exc.what());
    }
    ctx.cookieSyncDuration = ctx.stopWatch.lap();
  }

  /* The first stage of execution is generation.
   * We generate a series of file inputs to pass to
   * the query executor.
   *
   * We evaluate each of the generators one after the
   * other.  If multiple generators are used, it is
   * possible and expected that the same file name
   * will be evaluated multiple times if those generators
   * both emit the same file.
   */

  ctx.clockAtStartOfQuery =
      ClockSpec(root->view()->getMostRecentRootNumberAndTickValue());
  ctx.lastAgeOutTickValueAtStartOfQuery =
      root->view()->getLastAgeOutTickValue();

  // Copy in any scm parameters
  res.clockAtStartOfQuery = resultClock;
  // then update the clock position portion
  std::get<ClockSpec::Clock>(res.clockAtStartOfQuery.spec) =
      std::get<ClockSpec::Clock>(ctx.clockAtStartOfQuery.spec);

  // Evaluate the cursor for this root
  ctx.since = query->since_spec ? query->since_spec->evaluate(
                                      ctx.clockAtStartOfQuery.position(),
                                      ctx.lastAgeOutTickValueAtStartOfQuery,
                                      &root->inner.cursors)
                                : QuerySince{};

  if (query->bench_iterations > 0) {
    for (uint32_t i = 0; i < query->bench_iterations; ++i) {
      QueryContext c{query, root, ctx.disableFreshInstance};
      QueryResult r;
      c.clockAtStartOfQuery = ctx.clockAtStartOfQuery;
      c.since = ctx.since;
      execute_common(&c, nullptr, &r, generator);
    }
  }

  execute_common(&ctx, &sample, &res, generator);
  return res;
}