'Returns an alias for the join in \'connection\', either reusing an
existing alias for that join or creating a new one. \'connection\' is a
tuple (lhs, table, lhs_col, col) where \'lhs\' is either an existing
table alias or a table name. The join correspods to the SQL equivalent
of::
lhs.lhs_col = table.col
If \'always_create\' is True and \'reuse\' is None, a new alias is always
created, regardless of whether one already exists or not. If
\'always_create\' is True and \'reuse\' is a set, an alias in \'reuse\' that
matches the connection will be returned, if possible.  If
\'always_create\' is False, the first existing alias that matches the
\'connection\' is returned, if any. Otherwise a new join is created.
If \'exclusions\' is specified, it is something satisfying the container
protocol ("foo in exclusions" must work) and specifies a list of
aliases that should not be returned, even if they satisfy the join.
If \'promote\' is True, the join type for the alias will be LOUTER (if
the alias previously existed, the join type will be promoted from INNER
to LOUTER, if necessary).
If \'outer_if_first\' is True and a new join is created, it will have the
LOUTER join type. This is used when joining certain types of querysets
and Q-objects together.
If \'nullable\' is True, the join can potentially involve NULL values and
is a candidate for promotion (to "left outer") when combining querysets.'

def join(self, connection, always_create=False, exclusions=(), promote=False, outer_if_first=False, nullable=False, reuse=None):

    (lhs, table, lhs_col, col) = connection
    if (lhs in self.alias_map):
        lhs_table = self.alias_map[lhs][TABLE_NAME]
    else:
        lhs_table = lhs
    if (reuse and always_create and (table in self.table_map)):
        exclusions = set(self.table_map[table]).difference(reuse).union(set(exclusions))
        always_create = False
    t_ident = (lhs_table, table, lhs_col, col)
    if (not always_create):
        for alias in self.join_map.get(t_ident, ()):
            if (alias not in exclusions):
                if (lhs_table and (not self.alias_refcount[self.alias_map[alias][LHS_ALIAS]])):
                    continue
                if (self.alias_map[alias][LHS_ALIAS] != lhs):
                    continue
                self.ref_alias(alias)
                if promote:
                    self.promote_alias(alias)
                return alias
    (alias, _) = self.table_alias(table, True)
    if (not lhs):
        join_type = None
    elif (promote or outer_if_first):
        join_type = self.LOUTER
    else:
        join_type = self.INNER
    join = (table, alias, join_type, lhs, lhs_col, col, nullable)
    self.alias_map[alias] = join
    if (t_ident in self.join_map):
        self.join_map[t_ident] += (alias,)
    else:
        self.join_map[t_ident] = (alias,)
    self.rev_join_map[alias] = t_ident
    return alias
