public static List<TableSchemaAttributeDetail> resolve(
      SyntaxTreeListNode<SyntaxTreeNode> projectionList, TableSchema schema)
      throws SQLSemanticException {
    SyntaxTreeNode node = projectionList.getNode();
    List<TableSchemaAttributeDetail> result = new LinkedList<TableSchemaAttributeDetail>();
    if (node.getClass().equals(SyntaxTreeRenameNode.class)) { // Case 0 :: renamed aggregate
      SyntaxTreeRenameNode rdnode = (SyntaxTreeRenameNode) node;
      if (rdnode.getChild().getClass().equals(SyntaxTreeIdentifierNode.class)) {
        SyntaxTreeIdentifierNode inode = (SyntaxTreeIdentifierNode) rdnode.getChild();
        if (inode.generatingToken.tokenClass
            == SQLToken.SQLTokenClass
                .AGGREGATE) { // aggregates are not renamed in the projection, but in the
                              // aggregation
          int index = schema.indexOfAttributeName(rdnode.name, 0);
          result.add(schema.getAttributes().get(index));
        } else {
          throw new SQLSemanticException(
              SQLSemanticException.Type.InternalError); // only rename aggregates
        }
      } else {
        throw new SQLSemanticException(SQLSemanticException.Type.InternalError);
      }

    } else if (node.getClass()
        .equals(SyntaxTreeIdentifierNode.class)) { // Case 1 :: identifier or unnamed aggregate
      SyntaxTreeIdentifierNode idnode = (SyntaxTreeIdentifierNode) node;
      if (idnode.generatingToken.tokenClass
          == SQLToken.SQLTokenClass.STAR) { // Case 1a ::add all attributes from the child schema
        result = schema.getAttributes();

      } else if (idnode.generatingToken.tokenClass
          == SQLToken.SQLTokenClass
              .QSTARID) { // Case 1b :: add all attributes that have the right qualifier
        List<TableSchemaAttributeDetail> allAttributes = schema.getAttributes();
        String attributeQualifier = idnode.generatingToken.getQualifierForIdentifier();
        if (!schema.getQualifiers().contains(attributeQualifier)) {
          throw new SQLSemanticException(
              SQLSemanticException.Type.NoSuchTableException, attributeQualifier);
        }

        for (TableSchemaAttributeDetail attribute : allAttributes) {
          if (attribute.qualifier.equals(attributeQualifier)) {
            result.add(attribute);
          }
        }

      } else if (idnode.generatingToken.tokenClass
          == SQLToken.SQLTokenClass
              .QID) { // Case 1c :: add the first attribute that has the right name and right
                      // qualifier
        Pair<String, String> nameParts = idnode.generatingToken.getFragmentsForIdentifier();
        int currentIndex = schema.indexOfQualifiedAttributeName(nameParts.first, nameParts.second);
        if (currentIndex < 0)
          throw new SQLSemanticException(
              SQLSemanticException.Type.NoSuchAttributeException, nameParts.second);
        result.add(schema.getAttributes().get(currentIndex));

      } else if (idnode.generatingToken.tokenClass
          == SQLToken.SQLTokenClass
              .UID) { // Case 1d :: add the first attribute that has the right name
        Pair<String, String> nameParts = idnode.generatingToken.getFragmentsForIdentifier();
        int index = schema.indexOfAttributeName(nameParts.second, 0);
        if (index < 0)
          throw new SQLSemanticException(
              SQLSemanticException.Type.NoSuchAttributeException, nameParts.second);
        result.add(schema.getAttributes().get(index));

      } else {
        throw new SQLSemanticException(SQLSemanticException.Type.InternalError);
      }

    } else {
      throw new SQLSemanticException(SQLSemanticException.Type.InternalError);
    }

    if (projectionList.getNext() != null) { // recursively resolve rest of the list and append
      result.addAll(resolve(projectionList.getNext(), schema));
    }

    return result;
  }