diff --git a/CHANGELOG.md b/CHANGELOG.md
index c87dc97a4bed599ebce4bfe12eaf100989ca17f8..bc4504eb51ab3d3efbb332aeef293c3e4c69b8f3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,9 +1,4 @@
-# v2.19.0
+# v2.19.2
+
+* added `db_call`
 
-* unit tests migrated to CircleCI
-* added __len__ to FutureCollection
-* fixed a bug in DictionaryEQAble
-* fixed a bug in ListDeleter
-* minor breaking change: changed semantics of ListDeleter
-* added `CPManager`
-* added `SetZip`
\ No newline at end of file
diff --git a/docs/db.rst b/docs/db.rst
new file mode 100644
index 0000000000000000000000000000000000000000..2df2fcb7d14341cc277f7551a1ae6ce15f5de95a
--- /dev/null
+++ b/docs/db.rst
@@ -0,0 +1,9 @@
+Python DB API 2
+===============
+
+However imperfect may it be, it's here to stay.
+
+So enjoy!
+
+.. autoclass:: satella.db.transaction
+    :members:
diff --git a/docs/index.rst b/docs/index.rst
index 601fa559a1d69a3c9a875b155c5519cbd32b7d1a..d8d58cad0768e7b0c0bf1fcfc8ea849e0e788c85 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -28,6 +28,7 @@ Visit the project's page at GitHub_!
            instrumentation/metrics
            exception_handling
            dao
+           db
            json
            posix
            import
diff --git a/satella/__init__.py b/satella/__init__.py
index 6d2db50c097c278d885331e65be7b51a2178f18b..a910817da22d06aa0244c6d488b40d30da2bfb7e 100644
--- a/satella/__init__.py
+++ b/satella/__init__.py
@@ -1 +1 @@
-__version__ = '2.19.0'
+__version__ = '2.20.0'
diff --git a/satella/db.py b/satella/db.py
new file mode 100644
index 0000000000000000000000000000000000000000..1d3ad8ef00636dd3c6c13806ce14aec41620f9f5
--- /dev/null
+++ b/satella/db.py
@@ -0,0 +1,48 @@
+import logging
+
+logger = logging.getLogger(__name__)
+
+
+class transaction:
+    """
+    A context manager for wrapping a transaction and getting a cursor from the Python DB API 2.
+
+    Use it as a context manager. commit and rollback will be automatically called for you.
+
+    Use like:
+
+    >>> with transaction(conn) as cur:
+    >>>   cur.execute('DROP DATABASE')
+
+    Leaving the context manager will automatically close the cursor for you.
+
+    :param connection: the connection object to use
+    :param close_the_connection_after: whether the connection should be closed after use, False by default
+    :param log_exception: whether to log an exception if it happens
+    """
+    def __init__(self, connection, close_the_connection_after: bool = False,
+                 log_exception: bool = True):
+        self.connection = connection
+        self.close_the_connection_after = close_the_connection_after
+        self.cursor = None
+        self.log_exception = log_exception
+
+    def __enter__(self):
+        self.cursor = self.connection.cursor()
+        return self.cursor()
+
+    def __exit__(self, exc_type, exc_val, exc_tb):
+        if exc_val is None:
+            self.connection.commit()
+        else:
+            self.connection.rollback()
+
+        if self.log_exception:
+            logger.error('Exception occurred of type %s', exc_type, exc_info=exc_val)
+
+        self.cursor.close()
+
+        if self.close_the_connection_after:
+            self.connection.close()
+
+        return False
diff --git a/tests/test_db.py b/tests/test_db.py
new file mode 100644
index 0000000000000000000000000000000000000000..c5d86b029f9c6aa32922bbe0dce40fd66305dda5
--- /dev/null
+++ b/tests/test_db.py
@@ -0,0 +1,40 @@
+import unittest
+from unittest.mock import Mock
+
+from satella.db import transaction
+
+
+class TestDB(unittest.TestCase):
+    def test_something(self):
+        class RealConnection:
+            def __init__(self):
+                self.cursor_called = 0
+                self.commit_called = 0
+                self.rollback_called = 0
+                self.close_called = 0
+
+            def cursor(self):
+                self.cursor_called += 1
+                return Mock()
+
+            def commit(self):
+                self.commit_called += 1
+
+            def rollback(self):
+                self.rollback_called += 1
+
+            def close(self):
+                self.close_called += 1
+
+        conn = RealConnection()
+        a = transaction(conn)
+        with a as cur:
+            self.assertEqual(conn.cursor_called, 1)
+            cur.execute('TEST')
+        self.assertEqual(conn.commit_called, 1)
+        try:
+            with a as cur:
+                raise ValueError()
+        except ValueError:
+            self.assertEqual(conn.commit_called, 1)
+            self.assertEqual(conn.rollback_called, 1)