code: def parse_batch_jobs_query_v1(
      batch_id: int, q: str, last_job_id: Optional[int]
  ) -> Tuple[str, List[Any]]:
      # batch has already been validated
      where_conditions = [
          "(jobs.batch_id = %s AND batch_updates.committed)"
      ]
      where_args: List[Any] = [batch_id]
  
      if last_job_id is not None:
          where_conditions.append("(jobs.job_id > %s)")
          where_args.append(last_job_id)
  
      terms = q.split()
      for _t in terms:
          if _t[0] == "!":
              negate = True
              t = _t[1:]
          else:
              negate = False
              t = _t
  
          args: List[Any]
  
          if "=" in t:
              k, v = t.split("=", 1)
              if k == "job_id":
                  condition = "(jobs.job_id = %s)"
                  args = [v]
              else:
                  condition = """
  ((jobs.batch_id, jobs.job_id) IN
   (SELECT batch_id, job_id FROM job_attributes
    WHERE `key` = %s AND `value` = %s))
  """
                  args = [k, v]
          elif t.startswith("has:"):
              k = t[4:]
              condition = """
  ((jobs.batch_id, jobs.job_id) IN
   (SELECT batch_id, job_id FROM job_attributes
    WHERE `key` = %s))
  """
              args = [k]
          elif t in job_state_search_term_to_states:
              values = job_state_search_term_to_states[t]
              condition = " OR ".join(
                  ["(jobs.state = %s)" for _ in values]
              )
              condition = f"({condition})"
              args = [v.value for v in values]
          else:
              raise QueryError(f"Invalid search term: {t}.")
  
          if negate:
              condition = f"(NOT {condition})"
  
          where_conditions.append(condition)
          where_args.extend(args)
  
      sql = f"""
  WITH base_t AS
  (
    SELECT jobs.*, batches.user, batches.billing_project, batches.format_version,
      job_attributes.value AS name
    FROM jobs
    INNER JOIN batches ON jobs.batch_id = batches.id
    INNER JOIN batch_updates ON jobs.batch_id = batch_updates.batch_id AND jobs.update_id = batch_updates.update_id
    LEFT JOIN job_attributes
    ON jobs.batch_id = job_attributes.batch_id AND
      jobs.job_id = job_attributes.job_id AND
      job_attributes.`key` = 'name'
    WHERE {' AND '.join(where_conditions)}
    LIMIT 50
  )
  SELECT base_t.*, cost_t.cost, cost_t.cost_breakdown
  FROM base_t
  LEFT JOIN LATERAL (
  SELECT COALESCE(SUM(`usage` * rate), 0) AS cost, JSON_OBJECTAGG(resources.resource, COALESCE(`usage` * rate, 0)) AS cost_breakdown
  FROM (SELECT aggregated_job_resources_v3.batch_id, aggregated_job_resources_v3.job_id, resource_id, CAST(COALESCE(SUM(`usage`), 0) AS SIGNED) AS `usage`
    FROM aggregated_job_resources_v3
    WHERE aggregated_job_resources_v3.batch_id = base_t.batch_id AND aggregated_job_resources_v3.job_id = base_t.job_id
    GROUP BY aggregated_job_resources_v3.batch_id, aggregated_job_resources_v3.job_id, aggregated_job_resources_v3.resource_id
  ) AS usage_t
  LEFT JOIN resources ON usage_t.resource_id = resources.resource_id
  GROUP BY usage_t.batch_id, usage_t.job_id
  ) AS cost_t ON TRUE;
  """
  
      return (sql, where_args)
  
imports: from typing import Any, List, Optional, Tuple
  from ...exceptions import QueryError
  from .query import job_state_search_term_to_states
label_desc: Use image node_id 2 for calling a global function with example usage: parse_batch_jobs_query_v1(batch_id, q, last_job_id) and returns: sql,  where_args