'Return an alias for the \'join\', either reusing an existing alias for
that join or creating a new one. \'join\' is either a
sql.datastructures.BaseTable or Join.
The \'reuse\' parameter can be either None which means all joins are
reusable, or it can be a set containing the aliases that can be reused.
A join is always created as LOUTER if the lhs alias is LOUTER to make
sure chains like t1 LOUTER t2 INNER t3 aren\'t generated. All new
joins are created as LOUTER if the join is nullable.'

def join(self, join, reuse=None):

    reuse = [a for (a, j) in self.alias_map.items() if (((reuse is None) or (a in reuse)) and (j == join))]
    if reuse:
        self.ref_alias(reuse[0])
        return reuse[0]
    (alias, _) = self.table_alias(join.table_name, create=True)
    if join.join_type:
        if ((self.alias_map[join.parent_alias].join_type == LOUTER) or join.nullable):
            join_type = LOUTER
        else:
            join_type = INNER
        join.join_type = join_type
    join.table_alias = alias
    self.alias_map[alias] = join
    return alias
