code: def exec(self, *args, **kwargs) -> Iterator[Batch]:
      outer = self.children[0]
      inner = self.children[1]
      for row1 in outer.exec(**kwargs):
          for row2 in inner.exec(**kwargs):
              result_batch = Batch.join(row1, row2)
              result_batch.reset_index()
              result_batch = apply_predicate(
                  result_batch, self.predicate
              )
              if not result_batch.empty():
                  yield result_batch
  
      # instrument required stats
      if self.predicate:
          instrument_function_expression_cost(
              self.predicate, self.catalog()
          )
  
file_path: evadb/evadb/executor/nested_loop_join_executor.py
imports: from typing import Iterator
  from evadb.database import EvaDBDatabase
  from evadb.executor.abstract_executor import AbstractExecutor
  from evadb.executor.executor_utils import apply_predicate, instrument_function_expression_cost
  from evadb.models.storage.batch import Batch
  from evadb.plan_nodes.nested_loop_join_plan import NestedLoopJoinPlan
label_desc: Use image node_id 2 for calling the NestedLoopJoinExecutor obj's underlying member method code with example usage: obj.exec() without return types