Example #1
0
def PreprocessAST(cnxn,
                  query_ast,
                  project_ids,
                  services,
                  harmonized_config,
                  is_member=True):
    """Preprocess the query by doing lookups so that the SQL query is simpler.

  Args:
    cnxn: connection to SQL database.
    query_ast: user query abstract syntax tree parsed by query2ast.py.
    project_ids: collection of int project IDs to use to look up status values
        and labels.
    services: Connections to persistence layer for users and configs.
    harmonized_config: harmonized config for all projects being searched.
    is_member: True if user is a member of all the projects being searched,
        so they can do user substring searches.

  Returns:
    A new QueryAST PB with simplified conditions.  Specifically, string values
    for labels, statuses, and components are replaced with the int IDs of
    those items.  Also, is:open is distilled down to
    status_id != closed_status_ids.
  """
    new_conjs = []
    for conj in query_ast.conjunctions:
        new_conds = [
            _PreprocessCond(cnxn, cond, project_ids, services,
                            harmonized_config, is_member)
            for cond in conj.conds
        ]
        new_conjs.append(ast_pb2.Conjunction(conds=new_conds))

    return ast_pb2.QueryAST(conjunctions=new_conjs)
Example #2
0
def ParseUserQuery(
    query, scope, builtin_fields, harmonized_config, warnings=None,
    now=None):
  """Parse a user query and return a set of structure terms.

  Args:
    query: string with user's query.  E.g., 'Priority=High'.
    scope: string search terms that define the scope in which the
        query should be executed.  They are expressed in the same
        user query language.  E.g., adding the canned query.
    builtin_fields: dict {field_name: FieldDef(field_name, type)}
        mapping field names to FieldDef objects for built-in fields.
    harmonized_config: config for all the projects being searched.
        @@@ custom field name is not unique in cross project search.
         - custom_fields = {field_name: [fd, ...]}
         - query build needs to OR each possible interpretation
         - could be label in one project and field in another project.
        @@@ what about searching across all projects?
    warnings: optional list to accumulate warning messages.
    now: optional timestamp for tests, otherwise time.time() is used.

  Returns:
    A QueryAST with conjunctions (usually just one), where each has a list of
    Condition PBs with op, fields, str_values and int_values.  E.g., the query
    [priority=high leak OR stars>100] over open issues would return
    QueryAST(
      Conjunction(Condition(EQ, [open_fd], [], [1]),
                  Condition(EQ, [label_fd], ['priority-high'], []),
                  Condition(TEXT_HAS, any_field_fd, ['leak'], [])),
      Conjunction(Condition(EQ, [open_fd], [], [1]),
                  Condition(GT, [stars_fd], [], [100])))

  Raises:
    InvalidQueryError: If a problem was detected in the user's query.
  """
  if warnings is None:
    warnings = []
  if _HasParens(query):
    warnings.append('Parentheses are ignored in user queries.')

  if _HasParens(scope):
    warnings.append('Parentheses are ignored in saved queries.')

  # Convert the overall query into one or more OR'd subqueries.
  subqueries = query.split(' OR ')

  # Make a dictionary of all fields: built-in + custom in each project.
  combined_fields = collections.defaultdict(
      list, {field_name: [field_def]
             for field_name, field_def in builtin_fields.iteritems()})
  for fd in harmonized_config.field_defs:
    if fd.field_type != tracker_pb2.FieldTypes.ENUM_TYPE:
      # Only do non-enum fields because enums are stored as labels
      combined_fields[fd.field_name.lower()].append(fd)

  conjunctions = [
      _ParseConjunction(sq, scope, combined_fields, warnings, now=now)
      for sq in subqueries]
  logging.info('search warnings: %r', warnings)
  return ast_pb2.QueryAST(conjunctions=conjunctions)
 def testBuildSQLQuery_Normal(self):
     owner_field = BUILTIN_ISSUE_FIELDS['owner']
     reporter_id_field = BUILTIN_ISSUE_FIELDS['reporter_id']
     conds = [
         ast_pb2.MakeCond(ast_pb2.QueryOp.TEXT_HAS, [owner_field],
                          ['example.com'], []),
         ast_pb2.MakeCond(ast_pb2.QueryOp.EQ, [reporter_id_field], [],
                          [111L])
     ]
     ast = ast_pb2.QueryAST(conjunctions=[ast_pb2.Conjunction(conds=conds)])
     left_joins, where = ast2select.BuildSQLQuery(ast)
     self.assertEqual([('User AS Cond0 ON (Issue.owner_id = Cond0.user_id '
                        'OR Issue.derived_owner_id = Cond0.user_id)', [])],
                      left_joins)
     self.assertTrue(sql._IsValidJoin(left_joins[0][0]))
     self.assertEqual([('(LOWER(Cond0.email) LIKE %s)', ['%example.com%']),
                       ('Issue.reporter_id = %s', [111L])], where)
     self.assertTrue(sql._IsValidWhereCond(where[0][0]))
Example #4
0
    def testPreprocessAST_Normal(self):
        open_field = BUILTIN_ISSUE_FIELDS['open']
        label_field = BUILTIN_ISSUE_FIELDS['label']
        label_id_field = BUILTIN_ISSUE_FIELDS['label_id']
        status_id_field = BUILTIN_ISSUE_FIELDS['status_id']
        conds = [
            ast_pb2.MakeCond(ast_pb2.QueryOp.EQ, [open_field], [], []),
            ast_pb2.MakeCond(ast_pb2.QueryOp.EQ, [label_field], ['Hot'], [])
        ]

        ast = ast_pb2.QueryAST()
        ast.conjunctions.append(ast_pb2.Conjunction(conds=conds))
        new_ast = ast2ast.PreprocessAST(self.cnxn, ast, [789], self.services,
                                        self.config)
        self.assertEqual(2, len(new_ast.conjunctions[0].conds))
        new_cond_1, new_cond_2 = new_ast.conjunctions[0].conds
        self.assertEqual(ast_pb2.QueryOp.NE, new_cond_1.op)
        self.assertEqual([status_id_field], new_cond_1.field_defs)
        self.assertEqual([7, 8, 9], new_cond_1.int_values)
        self.assertEqual([], new_cond_1.str_values)
        self.assertEqual(ast_pb2.QueryOp.EQ, new_cond_2.op)
        self.assertEqual([label_id_field], new_cond_2.field_defs)
        self.assertEqual([0], new_cond_2.int_values)
        self.assertEqual([], new_cond_2.str_values)
Example #5
0
 def testPreprocessAST_EmptyAST(self):
     ast = ast_pb2.QueryAST()  # No conjunctions in it.
     new_ast = ast2ast.PreprocessAST(self.cnxn, ast, [789], self.services,
                                     self.config)
     self.assertEqual(ast, new_ast)
 def testBuildSQLQuery_EmptyAST(self):
     ast = ast_pb2.QueryAST(conjunctions=[ast_pb2.Conjunction()
                                          ])  # No conds
     left_joins, where = ast2select.BuildSQLQuery(ast)
     self.assertEqual([], left_joins)
     self.assertEqual([], where)