'Promotes recursively the join type of given aliases and its children to
an outer join. If \'unconditional\' is False, the join is only promoted if
it is nullable or the parent join is an outer join.
Note about join promotion: When promoting any alias, we make sure all
joins which start from that alias are promoted, too. When adding a join
in join(), we make sure any join added to already existing LOUTER join
is generated as LOUTER. This ensures we don\'t ever have broken join
chains which contain first a LOUTER join, then an INNER JOIN, that is
this kind of join should never be generated: a LOUTER b INNER c. The
reason for avoiding this type of join chain is that the INNER after
the LOUTER will effectively remove any effect the LOUTER had.'

def promote_joins(self, aliases, unconditional=False):

    aliases = list(aliases)
    while aliases:
        alias = aliases.pop(0)
        if (self.alias_map[alias].join_cols[0][1] is None):
            continue
        parent_alias = self.alias_map[alias].lhs_alias
        parent_louter = (parent_alias and (self.alias_map[parent_alias].join_type == self.LOUTER))
        already_louter = (self.alias_map[alias].join_type == self.LOUTER)
        if ((unconditional or self.alias_map[alias].nullable or parent_louter) and (not already_louter)):
            data = self.alias_map[alias]._replace(join_type=self.LOUTER)
            self.alias_map[alias] = data
            aliases.extend((join for join in self.alias_map.keys() if ((self.alias_map[join].lhs_alias == alias) and (join not in aliases))))
