Repository: kayak/pypika
Pull request #544 (2021-01-22 12:29:16)

feat: add missing join query builder methods
Added the following methods:

* left_outer_join
* right_outer_join
* full_outer_join

lyz-code: Seems that the coveralls CI is broken, but the tests pass
gl3nn: Please apply the black formatter and amend your commit.

Patch:
diff --git a/README.rst b/README.rst
index d0cbd90..6d7a4aa 100644
--- a/README.rst
+++ b/README.rst
@@ -408,9 +408,12 @@ All join types are supported by |Brand|.
         .from_(base_table)
         ...
         .left_join(join_table) \
+        .left_outer_join(join_table) \
         .right_join(join_table) \
+        .right_outer_join(join_table) \
         .inner_join(join_table) \
         .outer_join(join_table) \
+        .full_outer_join(join_table) \
         .cross_join(join_table) \
         .hash_join(join_table) \
         ...
diff --git a/pypika/queries.py b/pypika/queries.py
index 9abd624..ec8db2e 100644
--- a/pypika/queries.py
+++ b/pypika/queries.py
@@ -987,12 +987,21 @@ class QueryBuilder(Selectable, Term):
     def left_join(self, item: Union[Table, "QueryBuilder", AliasedQuery]) -> "Joiner":
         return self.join(item, JoinType.left)
 
+    def left_outer_join(self, item: Union[Table, "QueryBuilder", AliasedQuery]) -> "Joiner":
+        return self.join(item, JoinType.left_outer)
+
     def right_join(self, item: Union[Table, "QueryBuilder", AliasedQuery]) -> "Joiner":
         return self.join(item, JoinType.right)
 
+    def right_outer_join(self, item: Union[Table, "QueryBuilder", AliasedQuery]) -> "Joiner":
+        return self.join(item, JoinType.right_outer)
+
     def outer_join(self, item: Union[Table, "QueryBuilder", AliasedQuery]) -> "Joiner":
         return self.join(item, JoinType.outer)
 
+    def full_outer_join(self, item: Union[Table, "QueryBuilder", AliasedQuery]) -> "Joiner":
+        return self.join(item, JoinType.full_outer)
+
     def cross_join(self, item: Union[Table, "QueryBuilder", AliasedQuery]) -> "Joiner":
         return self.join(item, JoinType.cross)
 


Test patch:
diff --git a/pypika/tests/test_joins.py b/pypika/tests/test_joins.py
index 1d3bbdc..6e54f88 100644
--- a/pypika/tests/test_joins.py
+++ b/pypika/tests/test_joins.py
@@ -128,43 +128,65 @@ class SelectQueryJoinTests(unittest.TestCase):
             self.assertEqual(expected, str(query))
 
     def test_left_outer_join(self):
-        q = (
-            Query.from_(self.table0)
-            .join(self.table1, how=JoinType.left_outer)
-            .on(self.table0.foo == self.table1.bar)
-            .select("*")
-        )
+        expected = 'SELECT * FROM "abc" LEFT OUTER JOIN "efg" ON "abc"."foo"="efg"."bar"'
+        with self.subTest("join with enum"):
+            query = (
+                Query.from_(self.table0)
+                .join(self.table1, how=JoinType.left_outer)
+                .on(self.table0.foo == self.table1.bar)
+                .select("*")
+            )
 
-        self.assertEqual(
-            'SELECT * FROM "abc" LEFT OUTER JOIN "efg" ON "abc"."foo"="efg"."bar"',
-            str(q),
-        )
+            self.assertEqual(expected, str(query))
+
+        with self.subTest("join function"):
+            query = (
+                Query.from_(self.table0).left_outer_join(self.table1).on(self.table0.foo == self.table1.bar).select("*")
+            )
+
+            self.assertEqual(expected, str(query))
 
     def test_right_outer_join(self):
-        q = (
-            Query.from_(self.table0)
-            .join(self.table1, how=JoinType.right_outer)
-            .on(self.table0.foo == self.table1.bar)
-            .select("*")
-        )
+        expected = 'SELECT * FROM "abc" RIGHT OUTER JOIN "efg" ON "abc"."foo"="efg"."bar"'
+        with self.subTest("join with enum"):
+            query = (
+                Query.from_(self.table0)
+                .join(self.table1, how=JoinType.right_outer)
+                .on(self.table0.foo == self.table1.bar)
+                .select("*")
+            )
 
-        self.assertEqual(
-            'SELECT * FROM "abc" RIGHT OUTER JOIN "efg" ON "abc"."foo"="efg"."bar"',
-            str(q),
-        )
+            self.assertEqual(expected, str(query))
+
+        with self.subTest("join function"):
+            query = (
+                Query.from_(self.table0)
+                .right_outer_join(self.table1)
+                .on(self.table0.foo == self.table1.bar)
+                .select("*")
+            )
+
+            self.assertEqual(expected, str(query))
 
     def test_full_outer_join(self):
-        q = (
-            Query.from_(self.table0)
-            .join(self.table1, how=JoinType.full_outer)
-            .on(self.table0.foo == self.table1.bar)
-            .select("*")
-        )
+        expected = 'SELECT * FROM "abc" FULL OUTER JOIN "efg" ON "abc"."foo"="efg"."bar"'
 
-        self.assertEqual(
-            'SELECT * FROM "abc" FULL OUTER JOIN "efg" ON "abc"."foo"="efg"."bar"',
-            str(q),
-        )
+        with self.subTest("join with enum"):
+            query = (
+                Query.from_(self.table0)
+                .join(self.table1, how=JoinType.full_outer)
+                .on(self.table0.foo == self.table1.bar)
+                .select("*")
+            )
+
+            self.assertEqual(expected, str(query))
+
+        with self.subTest("join function"):
+            query = (
+                Query.from_(self.table0).full_outer_join(self.table1).on(self.table0.foo == self.table1.bar).select("*")
+            )
+
+            self.assertEqual(expected, str(query))
 
     def test_join_on_field_single(self):
         query = Query.from_(self.table0).join(self.table1).on_field("foo").select("*")