docs: def BatchConvert(self, metadata_value_pairs, token=None):
      """Converts a batch of GrrMessages into a set of RDFValues at once.
  
      Args:
        metadata_value_pairs: a list or a generator of tuples (metadata, value),
          where metadata is ExportedMetadata to be used for conversion and value
          is a GrrMessage to be converted.
        token: Security token.
  
      Returns:
        Resulting RDFValues. Empty list is a valid result and means that
        conversion wasn't possible.
      """
  
      # Group messages by source (i.e. by client urn).
      msg_dict = {}
      for metadata, msg in metadata_value_pairs:
        msg_dict.setdefault(msg.source, []).append((metadata, msg))
  
      metadata_objects = []
      metadata_to_fetch = []
  
      # Open the clients we don't have metadata for and fetch metadata.
      for client_urn in msg_dict:
        try:
          metadata_objects.append(self.cached_metadata[client_urn])
        except KeyError:
          metadata_to_fetch.append(client_urn)
  
      if metadata_to_fetch:
        if data_store.RelationalDBEnabled():
          client_ids = set(urn.Basename() for urn in metadata_to_fetch)
          infos = data_store.REL_DB.MultiReadClientFullInfo(client_ids)
  
          fetched_metadata = [
              GetMetadata(client_id, info) for client_id, info in infos.items()
          ]
        else:
          client_fds = aff4.FACTORY.MultiOpen(
              metadata_to_fetch, mode="r", token=token)
  
          fetched_metadata = [
              GetMetadataLegacy(client_fd, token=token)
              for client_fd in client_fds
          ]
  
        for metadata in fetched_metadata:
          self.cached_metadata[metadata.client_urn] = metadata
        metadata_objects.extend(fetched_metadata)
  
      data_by_type = {}
      for metadata in metadata_objects:
        try:
          for original_metadata, message in msg_dict[metadata.client_urn]:
            # Get source_urn and annotations from the original metadata
            # provided and original_timestamp from the payload age.
            new_metadata = ExportedMetadata(metadata)
            new_metadata.source_urn = original_metadata.source_urn
            new_metadata.annotations = original_metadata.annotations
            new_metadata.original_timestamp = message.payload.age
            cls_name = message.payload.__class__.__name__
  
            # Create a dict of values for conversion keyed by type, so we can
            # apply the right converters to the right object types
            if cls_name not in data_by_type:
              converters_classes = ExportConverter.GetConvertersByValue(
                  message.payload)
              data_by_type[cls_name] = {
                  "converters": [cls(self.options) for cls in converters_classes],
                  "batch_data": [(new_metadata, message.payload)]
              }
            else:
              data_by_type[cls_name]["batch_data"].append(
                  (new_metadata, message.payload))
  
        except KeyError:
          pass
  
      # Run all converters against all objects of the relevant type
      converted_batch = []
      for dataset in itervalues(data_by_type):
        for converter in dataset["converters"]:
          converted_batch.extend(
              converter.BatchConvert(dataset["batch_data"], token=token))
  
      return converted_batch