private Joinable parseGraphPattern(
      final GraphPattern g, final HashMap<String, String> variableBindings) throws QueryException {

    LOG.debug("parsing graph pattern " + g);
    /* First, organize the filters by variable so that we can map them */
    HashMap<String, Set<MappableNodeFilter>> filters =
        new HashMap<String, Set<MappableNodeFilter>>();

    /* We're given NodeFilters, but need MappableNodeFilters. Convert. */
    Set<MappableNodeFilter<Node>> givenFilters = new HashSet<MappableNodeFilter<Node>>();
    for (NodeFilter<Node> filter : g.getFilters()) {
      givenFilters.add(new MappableNodeFilter<Node>(filter));
    }

    /*
     * For each given filter, if the value or constraint is a variable,
     * place it in the filter pool.
     */
    for (MappableNodeFilter f : givenFilters) {
      if (f.getNode().isVariable()) {
        if (!filters.containsKey(f.getNode().getVarName())) {
          LOG.debug("Adding " + f.getNode().getVarName() + " To filter pool..\n");
          filters.put(f.getNode().getVarName(), new HashSet<MappableNodeFilter>());
        }
        filters.get(f.getNode().getVarName()).add(f);
      }
      if (f.getConstraint().isVariable()) {
        if (!filters.containsKey(f.getConstraint().getVarName())) {
          LOG.debug("Adding " + f.getConstraint().getVarName() + " To filter pool..\n");
          filters.put(f.getConstraint().getVarName(), new HashSet<MappableNodeFilter>());
        }
        filters.get(f.getConstraint().getVarName()).add(f);
      }

      if (!(f.getNode().isVariable() || f.getConstraint().isVariable())) {
        throw new IllegalArgumentException(
            "Triple filters must "
                + "contain a variable.  Neither "
                + f.getNode().getVarName()
                + " nor "
                + f.getConstraint().getVarName()
                + " is a variable!");
      }
    }

    /* Next, process each triple pattern in this graph pattern */
    LinkedList<MappableTriplePattern> steps = new LinkedList<MappableTriplePattern>();
    for (TriplePattern p : g.getTriplePatterns()) {
      steps.add(new MappableTriplePattern(p));
    }

    /*
     * We create an initial join sequence from the first triple pattern NB:
     * We require that all triple patterns in a graph pattern can be joined,
     * so it is OK to do this
     */

    MappableTriplePattern step = null;
    boolean successfullyBoundFirstStep = false;
    while (!steps.isEmpty()) {
      step = steps.removeFirst();

      if (!bindPattern(step, variableBindings)) {
        continue;
      } else {
        successfullyBoundFirstStep = true;
        break;
      }
    }

    if (!successfullyBoundFirstStep) {
      LOG.info("Pattern is entirely redundant.  Ignoring: " + g);
      return null;
    }

    JoinSequence joins = new JoinSequence(new JoinTable(step));

    /* We keep track of all tje variables that are available to join on */
    Set<MappableNodePattern> joinableVars = joins.joinVars();

    /* For all the remaining steps.. */
    while (!steps.isEmpty()) {
      step = getJoinablePattern(steps, variableBindings);
      if (step == null) {
        throw new QueryException(
            "Cannot bind all query steps! \n"
                + "remaining:\n"
                + steps
                + "\nvariables already bound:\n "
                + variableBindings.keySet()
                + "\n");
      }
      steps.remove(step);

      bindPattern(step, variableBindings);
      JoinTable table = new JoinTable(step);

      joinableVars.addAll(table.joinVars());
      JoinConditions conditions = new JoinConditions();

      /* Create all possible joins */
      addJoinConditions(conditions, step, variableBindings);

      /* Add any filter constraints */
      addFilterConditions(conditions, filters, joinableVars, variableBindings);

      /* Fold in any remaining constant bindings */
      for (MappableNodePattern var : joinableVars) {
        if (valueBindings.containsKey(var.boundTable().alias())) {

          for (String condition : valueBindings.get(var.boundTable().alias())) {
            LOG.debug(
                "parseGraphPattern: Adding remaining " + "constant conditions " + condition + "\n");
            conditions.addCondition(condition);
          }
          valueBindings.remove(var.boundTable().alias());
        }
      }

      /* Finally, add the join to the sequence */
      joins.addJoin(JoinType.INNER_JOIN, table, conditions);
    }

    /*
     * If we still have filters yet unapplied, that is legitimate only if
     * this pattern has a length of 1 AND it contains a variable that
     * matches the filter...
     */
    if (filters.values().size() > 0 && g.getTriplePatterns().size() > 1) {
      throw new QueryException("Filter is unbound");
    }

    /* .. If so, then get that one pattern */
    MappableTriplePattern p = new MappableTriplePattern(g.getTriplePatterns().get(0));

    /* ... and Process any remaining filters */
    for (String varName : filters.keySet()) {
      for (MappableNodeFilter f : filters.get(varName)) {
        processFilter(p, varName, f);
      }
    }
    return joins;
  }
  /**
   * Returns a query in ANSI SQL
   *
   * <p>Translates the GraphQuery defined in the constructor, along with any specified orderings ,
   * into a set of SQL statements. The union of all SQL statement results, executed in order, will
   * represent the entire result set.
   *
   * @return list of SQL statements
   * @throws QueryException if there is some error translating the query to SQL.
   */
  public List<String> getSQL() throws QueryException {

    this.manager = new MappingManager(tableManager);
    this.encounteredPatterns = new HashSet<MappableTriplePattern>();

    valueBindings = new HashMap<String, Set<String>>();

    /* These are bindings of variables in the 'required' query portions */
    HashMap<String, String> requiredBindings = new HashMap<String, String>();

    /* All variable bindings, including optional */
    HashMap<String, String> allBindings = new HashMap<String, String>();

    /* This is the main sequence of joins constituting the query */
    JoinSequence joinSeq = null;

    /* Process required elements first */
    for (QueryElement e : query.getRequired()) {

      LOG.debug("Processing required element: " + e);

      /* Disallow subqueries for the time being */
      if (e.getType().equals(QueryElement.Type.GraphQuery)) {
        throw new QueryException("Currently, we do not support " + "subqueries");
      } else if (!e.getType().equals(QueryElement.Type.GraphPattern)) {
        /* Currently, this will never happen */
        throw new QueryException("Unknown query element type " + e.getType());
      }

      /* All required elements of the query are inner joined */
      if (joinSeq == null) {
        joinSeq = new JoinSequence(parseGraphPattern((GraphPattern) e, requiredBindings));
      } else {
        joinSeq.addJoin(
            JoinType.INNER_JOIN,
            parseGraphPattern((GraphPattern) e, requiredBindings),
            requiredBindings);
      }
    }

    allBindings.putAll(requiredBindings);

    for (QueryElement e : query.getOptional()) {

      LOG.debug("processing optional path: " + e);

      HashMap<String, String> optionalBindings = requiredBindings;

      /* Disallow subqueries for the time being */
      if (e.getType().equals(QueryElement.Type.GraphQuery)) {
        throw new QueryException("Currently, we do not support " + "subqueries");
      } else if (!e.getType().equals(QueryElement.Type.GraphPattern)) {
        /* Currently, this will never happen */
        throw new QueryException("Unknown query element type " + e.getType());
      }

      joinSeq.addJoin(
          JoinType.LEFT_OUTER_JOIN,
          parseGraphPattern((GraphPattern) e, optionalBindings),
          requiredBindings);

      addNewMappings(optionalBindings, allBindings);
    }

    StringBuilder sql = new StringBuilder();

    if (joinSeq == null) {
      /* No tables to join means no query parts submitted */
      return Arrays.asList("SELECT 1 WHERE 1=0");
    } else {
      sql.append("SELECT " + generateTargets(allBindings) + " FROM " + joinSeq);
    }
    /*
     * If there are any values or constraints that remain to be added to the
     * query, add them in a WHERE clause. NB: They better not be from an
     * optional clause: It would probably be wise to either check here, or
     * prove that an exception would have been thrown already.
     */

    StringBuilder additional = new StringBuilder();
    if (valueBindings.size() > 0) {
      additional.append(" WHERE ");
      ArrayList<String> valueKeys = new ArrayList<String>(valueBindings.keySet());
      for (int i = 0; i < valueKeys.size(); i++) {
        ArrayList<String> values = new ArrayList<String>(valueBindings.get(valueKeys.get(i)));
        for (int j = 0; j < values.size(); j++) {
          if (i > 0 || j > 0) {
            sql.append(" AND ");
          }

          LOG.debug(
              "Adding remaining unused binding for "
                  + valueKeys.get(i)
                  + ", "
                  + values.get(j)
                  + "\n");
          additional.append(values.get(j));
        }
      }
    }

    if (!additional.toString().equals(" WHERE ")) {
      sql.append(additional.toString());
    }

    if (ordering != null) {
      sql.append(" ORDER BY " + allBindings.get(ordering) + " " + orderingDirection);
    }

    ArrayList<String> sqlList = new ArrayList<String>();
    sqlList.add(sql.toString());
    return sqlList;
  }