Better catch for long queries, #3903

This commit is contained in:
James Cole 2020-10-17 08:53:32 +02:00
parent 388da769bb
commit 01fbe89295
No known key found for this signature in database
GPG Key ID: B5669F9493CDE38D
8 changed files with 47 additions and 47 deletions

View File

@ -32,15 +32,12 @@ use FireflyIII\Models\TransactionJournal;
use FireflyIII\Repositories\Rule\RuleRepositoryInterface; use FireflyIII\Repositories\Rule\RuleRepositoryInterface;
use FireflyIII\Support\Http\Controllers\ModelInformation; use FireflyIII\Support\Http\Controllers\ModelInformation;
use FireflyIII\Support\Http\Controllers\RuleManagement; use FireflyIII\Support\Http\Controllers\RuleManagement;
use FireflyIII\Support\Search\OperatorQuerySearch;
use FireflyIII\Support\Search\SearchInterface; use FireflyIII\Support\Search\SearchInterface;
use Illuminate\Contracts\View\Factory; use Illuminate\Contracts\View\Factory;
use Illuminate\Http\RedirectResponse; use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Routing\Redirector; use Illuminate\Routing\Redirector;
use Illuminate\View\View; use Illuminate\View\View;
use Log;
use Throwable;
/** /**
* Class CreateController * Class CreateController

View File

@ -25,11 +25,9 @@ namespace FireflyIII\Http\Controllers\Rule;
use FireflyIII\Http\Controllers\Controller; use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\Rule; use FireflyIII\Models\Rule;
use FireflyIII\Models\RuleGroup; use FireflyIII\Models\RuleGroup;
use FireflyIII\Models\RuleTrigger;
use FireflyIII\Repositories\Rule\RuleRepositoryInterface; use FireflyIII\Repositories\Rule\RuleRepositoryInterface;
use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface; use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface;
use FireflyIII\Support\Http\Controllers\RuleManagement; use FireflyIII\Support\Http\Controllers\RuleManagement;
use FireflyIII\Support\Search\OperatorQuerySearch;
use FireflyIII\User; use FireflyIII\User;
use Illuminate\Contracts\View\Factory; use Illuminate\Contracts\View\Factory;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;

View File

@ -69,6 +69,7 @@ class SearchController extends Controller
$ruleId = (int) $request->get('rule'); $ruleId = (int) $request->get('rule');
$rule = null; $rule = null;
$ruleChanged = false; $ruleChanged = false;
$longQueryWarning = false;
// find rule, check if query is different, offer to update. // find rule, check if query is different, offer to update.
$ruleRepository = app(RuleRepositoryInterface::class); $ruleRepository = app(RuleRepositoryInterface::class);
@ -79,7 +80,9 @@ class SearchController extends Controller
$ruleChanged = true; $ruleChanged = true;
} }
} }
if (strlen($fullQuery) > 250) {
$longQueryWarning = true;
}
// parse search terms: // parse search terms:
$searcher->parseQuery($fullQuery); $searcher->parseQuery($fullQuery);
@ -89,7 +92,7 @@ class SearchController extends Controller
$subTitle = (string) trans('breadcrumbs.search_result', ['query' => $fullQuery]); $subTitle = (string) trans('breadcrumbs.search_result', ['query' => $fullQuery]);
return view('search.index', compact('query', 'operators', 'page', 'rule', 'fullQuery', 'subTitle', 'ruleId', 'ruleChanged')); return view('search.index', compact('query', 'longQueryWarning', 'operators', 'page', 'rule', 'fullQuery', 'subTitle', 'ruleId', 'ruleChanged'));
} }
/** /**
@ -106,6 +109,7 @@ class SearchController extends Controller
$page = 0 === (int) $request->get('page') ? 1 : (int) $request->get('page'); $page = 0 === (int) $request->get('page') ? 1 : (int) $request->get('page');
$searcher->parseQuery($fullQuery); $searcher->parseQuery($fullQuery);
$searcher->setPage($page); $searcher->setPage($page);
$groups = $searcher->searchTransactions(); $groups = $searcher->searchTransactions();
$hasPages = $groups->hasPages(); $hasPages = $groups->hasPages();

View File

@ -77,6 +77,7 @@ class OperatorQuerySearch implements SearchInterface
private float $startTime; private float $startTime;
private Collection $modifiers; // obsolete private Collection $modifiers; // obsolete
private Collection $operators; private Collection $operators;
private string $originalQuery;
/** /**
* OperatorQuerySearch constructor. * OperatorQuerySearch constructor.
@ -90,6 +91,7 @@ class OperatorQuerySearch implements SearchInterface
$this->page = 1; $this->page = 1;
$this->words = []; $this->words = [];
$this->limit = 25; $this->limit = 25;
$this->originalQuery = '';
$this->validOperators = array_keys(config('firefly.search.operators')); $this->validOperators = array_keys(config('firefly.search.operators'));
$this->startTime = microtime(true); $this->startTime = microtime(true);
$this->accountRepository = app(AccountRepositoryInterface::class); $this->accountRepository = app(AccountRepositoryInterface::class);
@ -143,6 +145,7 @@ class OperatorQuerySearch implements SearchInterface
public function setPage(int $page): void public function setPage(int $page): void
{ {
$this->page = $page; $this->page = $page;
$this->collector->setPage($this->page);
} }
/** /**
@ -163,20 +166,14 @@ class OperatorQuerySearch implements SearchInterface
Log::debug(sprintf('Now in parseQuery(%s)', $query)); Log::debug(sprintf('Now in parseQuery(%s)', $query));
$parser = new QueryParser(); $parser = new QueryParser();
$this->query = $parser->parse($query); $this->query = $parser->parse($query);
$this->originalQuery = $query;
$this->collector = app(GroupCollectorInterface::class);
$this->collector->setUser($this->user);
$this->collector->setLimit($this->limit)->setPage($this->page);
$this->collector->withAccountInformation()->withCategoryInformation()->withBudgetInformation();
Log::debug(sprintf('Found %d node(s)', count($this->query->getNodes()))); Log::debug(sprintf('Found %d node(s)', count($this->query->getNodes())));
foreach ($this->query->getNodes() as $searchNode) { foreach ($this->query->getNodes() as $searchNode) {
$this->handleSearchNode($searchNode); $this->handleSearchNode($searchNode);
} }
$this->collector->setSearchWords($this->words); $this->collector->setSearchWords($this->words);
} }
/** /**
@ -211,8 +208,12 @@ class OperatorQuerySearch implements SearchInterface
$this->billRepository->setUser($user); $this->billRepository->setUser($user);
$this->categoryRepository->setUser($user); $this->categoryRepository->setUser($user);
$this->budgetRepository->setUser($user); $this->budgetRepository->setUser($user);
$this->collector = app(GroupCollectorInterface::class);
$this->collector->setUser($this->user);
$this->collector->withAccountInformation()->withCategoryInformation()->withBudgetInformation();
$this->setLimit((int) app('preferences')->getForUser($user, 'listPageSize', 50)->data); $this->setLimit((int) app('preferences')->getForUser($user, 'listPageSize', 50)->data);
} }
/** /**
@ -268,7 +269,7 @@ class OperatorQuerySearch implements SearchInterface
*/ */
private function updateCollector(string $operator, string $value): bool private function updateCollector(string $operator, string $value): bool
{ {
Log::debug(sprintf('updateCollector(%s, %s)', $operator, $value)); Log::debug(sprintf('updateCollector("%s", "%s")', $operator, $value));
// check if alias, replace if necessary: // check if alias, replace if necessary:
$operator = self::getRootOperator($operator); $operator = self::getRootOperator($operator);
@ -579,7 +580,7 @@ class OperatorQuerySearch implements SearchInterface
*/ */
private function searchAccount(string $value, int $searchDirection, int $stringPosition): void private function searchAccount(string $value, int $searchDirection, int $stringPosition): void
{ {
Log::debug(sprintf('searchAccount(%s, %d, %d)', $value, $stringPosition, $searchDirection)); Log::debug(sprintf('searchAccount("%s", %d, %d)', $value, $stringPosition, $searchDirection));
// search direction (default): for source accounts // search direction (default): for source accounts
$searchTypes = [AccountType::ASSET, AccountType::MORTGAGE, AccountType::LOAN, AccountType::DEBT, AccountType::REVENUE]; $searchTypes = [AccountType::ASSET, AccountType::MORTGAGE, AccountType::LOAN, AccountType::DEBT, AccountType::REVENUE];
@ -767,6 +768,7 @@ class OperatorQuerySearch implements SearchInterface
public function setLimit(int $limit): void public function setLimit(int $limit): void
{ {
$this->limit = $limit; $this->limit = $limit;
$this->collector->setLimit($this->limit);
} }
/** /**

View File

@ -249,20 +249,17 @@ class SearchRuleEngine implements RuleEngineInterface
Log::debug(sprintf('SearchRuleEngine:: add local added operator: %s:"%s"', $operator['type'], $operator['value'])); Log::debug(sprintf('SearchRuleEngine:: add local added operator: %s:"%s"', $operator['type'], $operator['value']));
$searchArray[$operator['type']] = sprintf('"%s"', $operator['value']); $searchArray[$operator['type']] = sprintf('"%s"', $operator['value']);
} }
$toJoin = [];
foreach ($searchArray as $type => $value) {
$toJoin[] = sprintf('%s:%s', $type, $value);
}
$searchQuery = join(' ', $toJoin);
Log::debug(sprintf('SearchRuleEngine:: Search strict query for rule #%d ("%s") = %s', $rule->id, $rule->title, $searchQuery));
// build and run the search engine. // build and run the search engine.
$searchEngine = app(SearchInterface::class); $searchEngine = app(SearchInterface::class);
$searchEngine->setUser($this->user); $searchEngine->setUser($this->user);
$searchEngine->setPage(1); $searchEngine->setPage(1);
$searchEngine->setLimit(31337); $searchEngine->setLimit(31337);
$searchEngine->parseQuery($searchQuery);
foreach ($searchArray as $type => $value) {
$searchEngine->parseQuery(sprintf('%s:%s', $type, $value));
}
$result = $searchEngine->searchTransactions(); $result = $searchEngine->searchTransactions();
$collection = $result->getCollection(); $collection = $result->getCollection();
@ -302,21 +299,16 @@ class SearchRuleEngine implements RuleEngineInterface
Log::debug(sprintf('SearchRuleEngine:: add local added operator: %s:"%s"', $operator['type'], $operator['value'])); Log::debug(sprintf('SearchRuleEngine:: add local added operator: %s:"%s"', $operator['type'], $operator['value']));
$searchArray[$operator['type']] = sprintf('"%s"', $operator['value']); $searchArray[$operator['type']] = sprintf('"%s"', $operator['value']);
} }
$toJoin = [];
foreach ($searchArray as $type => $value) {
$toJoin[] = sprintf('%s:%s', $type, $value);
}
$searchQuery = join(' ', $toJoin);
Log::debug(sprintf('SearchRuleEngine:: Search strict query for non-strict rule #%d ("%s") = %s', $rule->id, $rule->title, $searchQuery));
// build and run a search:
// build and run the search engine. // build and run the search engine.
$searchEngine = app(SearchInterface::class); $searchEngine = app(SearchInterface::class);
$searchEngine->setUser($this->user); $searchEngine->setUser($this->user);
$searchEngine->setPage(1); $searchEngine->setPage(1);
$searchEngine->setLimit(31337); $searchEngine->setLimit(31337);
$searchEngine->parseQuery($searchQuery);
foreach ($searchArray as $type => $value) {
$searchEngine->parseQuery(sprintf('%s:%s', $type, $value));
}
$result = $searchEngine->searchTransactions(); $result = $searchEngine->searchTransactions();
$collection = $result->getCollection(); $collection = $result->getCollection();

View File

@ -459,6 +459,7 @@ return [
'description_ends' => ['alias' => false, 'needs_context' => true,], 'description_ends' => ['alias' => false, 'needs_context' => true,],
'description_contains' => ['alias' => false, 'needs_context' => true,], 'description_contains' => ['alias' => false, 'needs_context' => true,],
'description_is' => ['alias' => false, 'needs_context' => true,], 'description_is' => ['alias' => false, 'needs_context' => true,],
'description' => ['alias' => true, 'alias_for' => 'description_contains', 'needs_context' => true,],
'currency_is' => ['alias' => false, 'needs_context' => true,], 'currency_is' => ['alias' => false, 'needs_context' => true,],
'foreign_currency_is' => ['alias' => false, 'needs_context' => true,], 'foreign_currency_is' => ['alias' => false, 'needs_context' => true,],

View File

@ -260,6 +260,7 @@ return [
// search // search
'search' => 'Search', 'search' => 'Search',
'long_query_warning' => 'Your search query is very long, and may not work as expected.',
'search_query' => 'Query', 'search_query' => 'Query',
'search_found_transactions' => 'Firefly III found :count transaction in :time seconds.|Firefly III found :count transactions in :time seconds.', 'search_found_transactions' => 'Firefly III found :count transaction in :time seconds.|Firefly III found :count transactions in :time seconds.',
'search_found_more_transactions' => 'Firefly III found more than :count transactions in :time seconds.', 'search_found_more_transactions' => 'Firefly III found more than :count transactions in :time seconds.',

View File

@ -20,7 +20,7 @@
<div class="form-group"> <div class="form-group">
<label for="query" class="col-sm-1 control-label">{{ 'search_query'|_ }}</label> <label for="query" class="col-sm-1 control-label">{{ 'search_query'|_ }}</label>
<div class="col-sm-10"> <div class="col-sm-10">
<input autocomplete="off" type="text" name="search" id="query" value="{{ fullQuery }}" class="form-control" <input autocomplete="off" maxlength="255" type="text" name="search" id="query" value="{{ fullQuery }}" class="form-control"
placeholder="{{ fullQuery }}"> placeholder="{{ fullQuery }}">
</div> </div>
</div> </div>
@ -37,6 +37,11 @@
<input type="hidden" name="rule" value="{{ ruleId }}" /> <input type="hidden" name="rule" value="{{ ruleId }}" />
{% endif %} {% endif %}
</form> </form>
{% if longQueryWarning %}
<p class="text-danger">
{{ 'long_query_warning'|_ }}
</p>
{% endif %}
{% if '' != query %} {% if '' != query %}
<p> <p>
{{ trans('firefly.search_for_query', {query: query|escape})|raw}} {{ trans('firefly.search_for_query', {query: query|escape})|raw}}