Repository: modin-project/modin

Issue:
`DataFrame.join` doesn't support `on` parameter
When `on` for `DataFrame.join` is set it defaults to pandas.


Discussion:

Resolving patch:
diff --git a/modin/backends/base/query_compiler.py b/modin/backends/base/query_compiler.py
--- a/modin/backends/base/query_compiler.py
+++ b/modin/backends/base/query_compiler.py
@@ -291,6 +291,27 @@ def merge(self, right, **kwargs):
         """
         pass
 
+    @abc.abstractmethod
+    def join(self, right, **kwargs):
+        """
+        Join columns of another DataFrame.
+
+        Parameters
+        ----------
+        right : BaseQueryCompiler
+            The query compiler of the right DataFrame to join with.
+
+        Returns
+        -------
+        BaseQueryCompiler
+            A new query compiler that contains result of the join.
+
+        Notes
+        -----
+        See pd.DataFrame.join for more info on kwargs.
+        """
+        pass
+
     # END Abstract inter-data operations
 
     # Abstract Transpose
diff --git a/modin/backends/pandas/query_compiler.py b/modin/backends/pandas/query_compiler.py
--- a/modin/backends/pandas/query_compiler.py
+++ b/modin/backends/pandas/query_compiler.py
@@ -442,6 +442,41 @@ def map_func(left, right=right, kwargs=kwargs):
         else:
             return self.default_to_pandas(pandas.DataFrame.merge, right, **kwargs)
 
+    def join(self, right, **kwargs):
+        """
+        Join columns of another DataFrame.
+
+        Parameters
+        ----------
+        right : BaseQueryCompiler
+            The query compiler of the right DataFrame to join with.
+
+        Returns
+        -------
+        BaseQueryCompiler
+            A new query compiler that contains result of the join.
+
+        Notes
+        -----
+        See pd.DataFrame.join for more info on kwargs.
+        """
+        on = kwargs.get("on", None)
+        how = kwargs.get("how", "left")
+        sort = kwargs.get("sort", False)
+
+        if how in ["left", "inner"]:
+            right = right.to_pandas()
+
+            def map_func(left, right=right, kwargs=kwargs):
+                return pandas.DataFrame.join(left, right, **kwargs)
+
+            new_self = self.__constructor__(
+                self._modin_frame._apply_full_axis(1, map_func)
+            )
+            return new_self.sort_rows_by_column_values(on) if sort else new_self
+        else:
+            return self.default_to_pandas(pandas.DataFrame.join, right, **kwargs)
+
     # END Inter-Data operations
 
     # Reindex/reset_index (may shuffle data)
diff --git a/modin/pandas/dataframe.py b/modin/pandas/dataframe.py
--- a/modin/pandas/dataframe.py
+++ b/modin/pandas/dataframe.py
@@ -1298,22 +1298,21 @@ def join(self, other, on=None, how="left", lsuffix="", rsuffix="", sort=False):
         DataFrame
             A dataframe containing columns from both the caller and other.
         """
-        if on is not None:
-            if isinstance(other, DataFrame):
-                other = other._query_compiler.to_pandas()
-            return self._default_to_pandas(
-                pandas.DataFrame.join,
-                other,
-                on=on,
-                how=how,
-                lsuffix=lsuffix,
-                rsuffix=rsuffix,
-                sort=sort,
-            )
         if isinstance(other, Series):
             if other.name is None:
                 raise ValueError("Other Series must have a name")
             other = DataFrame({other.name: other})
+        if on is not None:
+            return self.__constructor__(
+                query_compiler=self._query_compiler.join(
+                    other._query_compiler,
+                    on=on,
+                    how=how,
+                    lsuffix=lsuffix,
+                    rsuffix=rsuffix,
+                    sort=sort,
+                )
+            )
         if isinstance(other, DataFrame):
             # Joining the empty DataFrames with either index or columns is
             # fast. It gives us proper error checking for the edge cases that


Test patch:
diff --git a/modin/pandas/test/test_dataframe.py b/modin/pandas/test/test_dataframe.py
--- a/modin/pandas/test/test_dataframe.py
+++ b/modin/pandas/test/test_dataframe.py
@@ -5789,7 +5789,76 @@ def test_combine(self, data):
             pandas_df + 1, lambda s1, s2: s1 if s1.count() < s2.count() else s2
         )
 
-    def test_join(self):
+    @pytest.mark.parametrize(
+        "test_data, test_data2",
+        [
+            (
+                np.random.uniform(0, 100, size=(2 ** 6, 2 ** 6)),
+                np.random.uniform(0, 100, size=(2 ** 7, 2 ** 6)),
+            ),
+            (
+                np.random.uniform(0, 100, size=(2 ** 7, 2 ** 6)),
+                np.random.uniform(0, 100, size=(2 ** 6, 2 ** 6)),
+            ),
+            (
+                np.random.uniform(0, 100, size=(2 ** 6, 2 ** 6)),
+                np.random.uniform(0, 100, size=(2 ** 6, 2 ** 7)),
+            ),
+            (
+                np.random.uniform(0, 100, size=(2 ** 6, 2 ** 7)),
+                np.random.uniform(0, 100, size=(2 ** 6, 2 ** 6)),
+            ),
+        ],
+    )
+    def test_join(self, test_data, test_data2):
+        modin_df = pd.DataFrame(
+            test_data,
+            columns=["col{}".format(i) for i in range(test_data.shape[1])],
+            index=pd.Index([i for i in range(1, test_data.shape[0] + 1)], name="key"),
+        )
+        pandas_df = pandas.DataFrame(
+            test_data,
+            columns=["col{}".format(i) for i in range(test_data.shape[1])],
+            index=pandas.Index(
+                [i for i in range(1, test_data.shape[0] + 1)], name="key"
+            ),
+        )
+        modin_df2 = pd.DataFrame(
+            test_data2,
+            columns=["col{}".format(i) for i in range(test_data2.shape[1])],
+            index=pd.Index([i for i in range(1, test_data2.shape[0] + 1)], name="key"),
+        )
+        pandas_df2 = pandas.DataFrame(
+            test_data2,
+            columns=["col{}".format(i) for i in range(test_data2.shape[1])],
+            index=pandas.Index(
+                [i for i in range(1, test_data2.shape[0] + 1)], name="key"
+            ),
+        )
+
+        hows = ["inner", "left", "right", "outer"]
+        ons = ["col33", "col34"]
+        sorts = [False, True]
+        for i in range(4):
+            for j in range(2):
+                modin_result = modin_df.join(
+                    modin_df2,
+                    how=hows[i],
+                    on=ons[j],
+                    sort=sorts[j],
+                    lsuffix="_caller",
+                    rsuffix="_other",
+                )
+                pandas_result = pandas_df.join(
+                    pandas_df2,
+                    how=hows[i],
+                    on=ons[j],
+                    sort=sorts[j],
+                    lsuffix="_caller",
+                    rsuffix="_other",
+                )
+                df_equals(modin_result, pandas_result)
+
         frame_data = {
             "col1": [0, 1, 2, 3],
             "col2": [4, 5, 6, 7],