Uploaded image for project: 'Jira Data Center'
  1. Jira Data Center
  2. JRASERVER-23141

Don't call .size of .isEmpty in QueryVisitor.visit(TerminalClause) - it slows things down a lot

    XMLWordPrintable

Details

    Description

      Here is the QueryVisitor.visit(TerminalClause) visit method:

      public QueryFactoryResult visit(final TerminalClause terminalClause)
          {
              if (rootClause)
              {
                  return new QueryFactoryResult(createQuery(terminalClause));
              }
              final Collection<ClauseQueryFactory> clauseQueryFactory = this.queryRegistry.getClauseQueryFactory(queryCreationContext, terminalClause);
      
              if (clauseQueryFactory.isEmpty())
              {
                  // This time we really mean it! When we are asked to run a query for things that no longer exist we will
                  // run the query the best we can, adding false to the tree for bits that we can not resolve.
                  return QueryFactoryResult.createFalseResult();
              }
              try
              {
                  if (clauseQueryFactory.size() == 1)
                  {
                      ClauseQueryFactory factory = clauseQueryFactory.iterator().next();
                      return factory.getQuery(queryCreationContext, terminalClause);
                  }
                  else
                  {
                      BooleanQuery query = new BooleanQuery();
                      for (ClauseQueryFactory factory : clauseQueryFactory)
                      {
                          query.add(makeQuery(factory.getQuery(queryCreationContext, terminalClause)), BooleanClause.Occur.SHOULD);
                      }
                      return new QueryFactoryResult(query);
                  }
              }
              catch (BooleanQuery.TooManyClauses tooManyClauses)
              {
                  throw new JqlTooComplex(terminalClause);
              }
              // This could happen for Factories that use their own QueryVisitors, e.g. SavedFilterCQF
              catch (JqlTooComplex jqlTooComplex)
              {
                  throw new JqlTooComplex(terminalClause);
              }
          }
      

      This looks pretty simple. Unfortunately the collection returned from this.queryRegistry.getClauseQueryFactory(queryCreationContext, terminalClause) is actually generated via Collections2.filter like this:

          public Collection<ClauseHandler> getClauseHandler(final User user, final String jqlClauseName)
          {
              return filter(getClauseHandler(jqlClauseName), new PermissionToUse(user));
          }
      

      So when we call .isEmpty we find the first element in the list where PermissionToUse returns true. We then call .size() and as such end up iterating across the collection evaluating PermissionToUse to work out the size of the collection (this includes iterating over the first element again). We then can iterate over the collection again evaluating PermissionToUse during the for loop (i.e. the collection returned from filter does not remember its results). This means we can end up evaluating the PermissionToUse (2x + 1) as many times as we need to. This can cause significant overhead:

      Don't do this.

      Attachments

        Issue Links

          Activity

            People

              pleschev Peter Leschev
              bbain bain
              Votes:
              2 Vote for this issue
              Watchers:
              3 Start watching this issue

              Dates

                Created:
                Updated:
                Resolved: