code: private void generateAccessPaths(JoinNode joinNode) {
          assert(joinNode != null);
          if (joinNode instanceof BranchNode) {
              generateOuterAccessPaths((BranchNode)joinNode);
              generateInnerAccessPaths((BranchNode)joinNode);
              // An empty access path for the root
              joinNode.m_accessPaths.add(new AccessPath());
              return;
          }
          // This is a select from a single table
          joinNode.m_accessPaths.addAll(getRelevantAccessPathsForTable(joinNode.getTableScan(),
                  joinNode.m_joinInnerList, joinNode.m_whereInnerList, null));
      }
docs: Generate all possible access paths for all nodes in the tree.
  The join and filter expressions are kept at the parent node
  1- The OUTER-only join conditions - Testing the outer-only conditions COULD be considered as an
  optimal first step to processing each outer tuple - PreJoin predicate for NLJ or NLIJ
  2 -The INNER-only and INNER_OUTER join conditions are used for finding a matching inner tuple(s) for a
  given outer tuple. Index and end-Index expressions for NLIJ and join predicate for NLJ.
  3 -The OUTER-only filter conditions. - Can be pushed down to pre-qualify the outer tuples before they enter
  the join - Where condition for the left child
  4. The INNER-only and INNER_OUTER where conditions are used for filtering joined tuples. -
  Post join predicate for NLIJ and NLJ .  Possible optimization -
  if INNER-only condition is NULL-rejecting (inner_tuple is NOT NULL or inner_tuple > 0)
  it can be pushed down as a filter expression to the inner child
  
  @param joinNode A root to the join tree to generate access paths to all nodes in that tree.