Joins a table to another table based on a condition and adds fields from the joined table
        to the returned fields.

        :type right_table: str or dict or :class:`Table <querybuilder.tables.Table>`
        :param right_table: The table being joined with. This can be a string of the table
            name, a dict of {'alias': table}, or a ``Table`` instance

        :type fields: str or tuple or list or :class:`Field <querybuilder.fields.Field>`
        :param fields: The fields to select from ``right_table``. Defaults to `None`. This can be
            a single field, a tuple of fields, or a list of fields. Each field can be a string
            or ``Field`` instance

        :type condition: str
        :param condition: The join condition specifying the fields being joined. If the two tables being
            joined are instances of ``ModelTable`` then the condition should be created automatically.

        :type join_type: str
        :param join_type: The type of join (JOIN, LEFT JOIN, INNER JOIN, etc). Defaults to 'JOIN'

        :type schema: str
        :param schema: This is not implemented, but it will be a string of the db schema name

        :type left_table: str or dict or Table
        :param left_table: The left table being joined with. This can be a string of the table
            name, a dict of {'alias': table}, or a ``Table`` instance. Defaults to the first table
            in the query.

        :type extract_fields: bool
        :param extract_fields: If True and joining with a ``ModelTable``, then '*'
            fields will be converted to individual fields for each column in the table. Defaults
            to True.

        :type prefix_fields: bool
        :param prefix_fields: If True, then the joined table will have each of its field names
            prefixed with the field_prefix. If not field_prefix is specified, a name will be
            generated based on the join field name. This is usually used with nesting results
            in order to create models in python or javascript. Defaults to True.

        :type field_prefix: str
        :param field_prefix: The field prefix to be used in front of each field name if prefix_fields
            is set to True. If no field_prefix is set, one will be automatically created based on
            the join field name.

        :rtype: :class:`Query <querybuilder.query.Query>`
        :return: self

def join(self, right_table=None, fields=None, condition=None, join_type='JOIN',
             schema=None, left_table=None, extract_fields=True, prefix_fields=False, field_prefix=None,
             allow_duplicates=False):
        """
        Joins a table to another table based on a condition and adds fields from the joined table
        to the returned fields.

        :type right_table: str or dict or :class:`Table <querybuilder.tables.Table>`
        :param right_table: The table being joined with. This can be a string of the table
            name, a dict of {'alias': table}, or a ``Table`` instance

        :type fields: str or tuple or list or :class:`Field <querybuilder.fields.Field>`
        :param fields: The fields to select from ``right_table``. Defaults to `None`. This can be
            a single field, a tuple of fields, or a list of fields. Each field can be a string
            or ``Field`` instance

        :type condition: str
        :param condition: The join condition specifying the fields being joined. If the two tables being
            joined are instances of ``ModelTable`` then the condition should be created automatically.

        :type join_type: str
        :param join_type: The type of join (JOIN, LEFT JOIN, INNER JOIN, etc). Defaults to 'JOIN'

        :type schema: str
        :param schema: This is not implemented, but it will be a string of the db schema name

        :type left_table: str or dict or Table
        :param left_table: The left table being joined with. This can be a string of the table
            name, a dict of {'alias': table}, or a ``Table`` instance. Defaults to the first table
            in the query.

        :type extract_fields: bool
        :param extract_fields: If True and joining with a ``ModelTable``, then '*'
            fields will be converted to individual fields for each column in the table. Defaults
            to True.

        :type prefix_fields: bool
        :param prefix_fields: If True, then the joined table will have each of its field names
            prefixed with the field_prefix. If not field_prefix is specified, a name will be
            generated based on the join field name. This is usually used with nesting results
            in order to create models in python or javascript. Defaults to True.

        :type field_prefix: str
        :param field_prefix: The field prefix to be used in front of each field name if prefix_fields
            is set to True. If no field_prefix is set, one will be automatically created based on
            the join field name.

        :rtype: :class:`Query <querybuilder.query.Query>`
        :return: self
        """
        # self.mark_dirty()
        # TODO: fix bug when joining from simple table to model table with no condition
        # it assumes left_table.model

        # if there is no left table, assume the query's first table
        # TODO: add test for auto left table to replace old auto left table
        # if left_table is None and len(self.tables):
        #     left_table = self.tables[0]

        # left_table = TableFactory(left_table)
        # right_table = TableFactory(right_table)

        # create the join item
        new_join_item = Join(
            left_table=left_table,
            right_table=right_table,
            fields=fields,
            condition=condition,
            join_type=join_type,
            schema=schema,
            owner=self,
            extract_fields=extract_fields,
            prefix_fields=prefix_fields,
            field_prefix=field_prefix,
        )

        # check if this table is already joined upon
        # TODO: add test for this
        if allow_duplicates is False:
            for join_item in self.joins:
                if join_item.right_table.get_identifier() == new_join_item.right_table.get_identifier():
                    if join_item.left_table.get_identifier() == new_join_item.left_table.get_identifier():
                        return self

        self.joins.append(new_join_item)

        return self