Skip to content

Commit

Permalink
Add support for newer pg8000.
Browse files Browse the repository at this point in the history
Fixes #438
  • Loading branch information
jamadden committed Apr 22, 2021
1 parent db20155 commit 79ac51b
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 6 deletions.
2 changes: 2 additions & 0 deletions CHANGES.rst
Expand Up @@ -11,6 +11,8 @@
- Make more conflict errors include information about the OIDs and
TIDs that may have been involved in the conflict.

- Add support for pg8000 1.17 and newer; tested with 1.19.2. See
:issue:`438`.

3.4.2 (2021-04-21)
==================
Expand Down
3 changes: 1 addition & 2 deletions setup.py
Expand Up @@ -258,8 +258,7 @@ def read_file(*path):
# pure-python
# pg8000 on Python 2.7 or PyPy. We get coverage from Travis, and we also
# get Windows.
# 1.17 breaks us: https://github.com/zodb/relstorage/issues/438
'pg8000 >= 1.11.0, <1.17.0; python_version == "2.7" or platform_python_implementation == "PyPy"',
'pg8000 >= 1.11.0 ; python_version == "2.7" or platform_python_implementation == "PyPy"',
# CFFI, runs on all implementations.
# We get coverage from 3.5 on Travis and verification it works on Windows from Travis.
# We get 2.7 testing from PyPy on Travis.
Expand Down
32 changes: 28 additions & 4 deletions src/relstorage/adapters/postgresql/drivers/pg8000.py
Expand Up @@ -92,10 +92,12 @@ class Cursor(self.driver_module.Cursor):
def __init__(self, conn):
super(Cursor, self).__init__(conn)
# pylint:disable=access-member-before-definition
assert isinstance(self._cached_rows, deque)
# Make sure rows are tuples, not lists.
# BTrees don't like lists.
self._cached_rows = _tuple_deque()
if hasattr(self, '_cached_rows'):
# This went away in 1.17
assert isinstance(self._cached_rows, deque)
# Make sure rows are tuples, not lists.
# BTrees don't like lists.
self._cached_rows = _tuple_deque()

@property
def connection(self):
Expand All @@ -106,6 +108,15 @@ def connection(self):
def copy_expert(self, sql, stream):
return self.execute(sql, stream=stream)

def execute(self, *args, **kwargs):
result = super(Cursor, self).execute(*args, **kwargs)
if hasattr(self, '_row_iter'):
# pylint:disable=attribute-defined-outside-init
self._row_iter = iter([
tuple(row) for row in self._row_iter
])
return result

class Connection(LobConnectionMixin,
self.driver_module.Connection):
readonly = False
Expand Down Expand Up @@ -151,6 +162,19 @@ def __init__(self,
# 1.16.0 dropped the ``max_prepared_statements`` argument.
del kwargs['max_prepared_statements']
super(Connection, self).__init__(**kwargs)
if (
hasattr(self, 'py_types')
and list not in self.py_types
and not hasattr(self, 'make_param')
):
# We're trying to detect pg8000 1.17+ to
# fix https://github.com/zodb/relstorage/issues/438#issuecomment-825116452:
# pg8000 can guess a the parameter type wrong for arrays
# (I only directly observed this issue on pg8000 1.19.2)
# pylint:disable=no-name-in-module,import-error
from pg8000.converters import BIGINT_ARRAY
from pg8000.converters import int_array_out
self.py_types[list] = (BIGINT_ARRAY, int_array_out)

def cursor(self):
return Cursor(self)
Expand Down
66 changes: 66 additions & 0 deletions src/relstorage/storage/tpc/tests/test_temporary_storage.py
@@ -0,0 +1,66 @@
# -*- coding: utf-8 -*-
"""
Tests for temporary_storage.py
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import unittest

class TestTemporaryStorage(unittest.TestCase):

def _makeOne(self):
from ..temporary_storage import TemporaryStorage
return TemporaryStorage()

def setUp(self):
super(TestTemporaryStorage, self).setUp()
from .. import temporary_storage
temporary_storage.id = lambda _: 0xDEADBEEF

def tearDown(self):
from .. import temporary_storage
del temporary_storage.id
super(TestTemporaryStorage, self).tearDown()

_EMPTY_STR = '<relstorage.storage.tpc.temporary_storage.TemporaryStorage at 0xdeadbeef len: 0>'

def test_empty_str(self):
temp = self._makeOne()
s = str(temp)
self.assertEqual(
s,
self._EMPTY_STR
)

def test_closed_str(self):
temp = self._makeOne()
temp.close()
s = str(temp)
self.assertEqual(s, self._EMPTY_STR)

def test_str_with_data(self):
from textwrap import dedent
self.maxDiff = None
temp = self._makeOne()
temp.store_temp(6547, b'defghijkl', 23)
temp.store_temp(1, b'abc')
temp.store_temp(2, b'def', 42)

s = str(temp)

self.assertEqual(
s,
dedent("""\
<relstorage.storage.tpc.temporary_storage.TemporaryStorage at 0xdeadbeef len: 3>
================================================================================
| OID | Length | Previous TID |
================================================================================
1 | 3 | 0
2 | 3 | 42
6547 | 9 | 23
"""
)
)

0 comments on commit 79ac51b

Please sign in to comment.