Skip to content

Commit d67e70b

Browse files
committed
Extract and test query execution
1 parent ece7bd3 commit d67e70b

3 files changed

Lines changed: 148 additions & 27 deletions

File tree

flask_phpbb3/backends/base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,11 @@ def __init__(
2525
):
2626
# type: (...) -> None
2727
self._functions = {} # type: typing.Dict[str, str]
28+
self._connection = None
2829
self._cache = cache
2930
self._config = config
3031

3132
self._prepare_statements()
32-
self._setup_connection()
3333

3434
def _setup_connection(self):
3535
# type: () -> None

flask_phpbb3/backends/psycopg2.py

Lines changed: 39 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ def _setup_connection(self):
2929
def _db(self):
3030
# type: () -> psycopg2.extensions.connection
3131
if not self._connection:
32-
raise NotImplementedError
32+
self._setup_connection()
3333

3434
return self._connection
3535

@@ -130,45 +130,58 @@ def _sql_query(
130130
# Woops :S
131131
pass
132132

133-
# FIXME Driver specific code!
134-
c = self._db.cursor()
135-
136133
if operation == 'fetch':
137-
# Add skip and limit
138-
query += ' OFFSET {:d}'.format(skip)
139-
if limit:
140-
query += 'LIMIT {:d}'.format(limit)
134+
query = self._paginate_query(query, skip, limit)
135+
136+
output = self._execute_operation(operation, query, kwargs)
137+
138+
if cache_key:
139+
try:
140+
self._cache.set(cache_key,
141+
json.dumps(output),
142+
cache_ttl)
143+
except ValueError:
144+
# Woops :S
145+
pass
146+
147+
return output
148+
149+
def _paginate_query(self, query, skip, limit):
150+
# type: (str, int, typing.Optional[int]) -> str
151+
output = query + ' OFFSET {:d}'.format(skip)
152+
if limit:
153+
output += ' LIMIT {:d}'.format(limit)
154+
return output
141155

142-
c.execute(
156+
def _execute_operation(
157+
self,
158+
operation, # type: str
159+
query, # type: str
160+
params # type: typing.Dict[str, typing.Union[str, int]]
161+
):
162+
# type: (...) -> typing.Any
163+
cursor = self._db.cursor()
164+
165+
cursor.execute(
143166
query.format(TABLE_PREFIX=self._config['TABLE_PREFIX']),
144-
kwargs
167+
params
145168
)
146169

147170
if operation == 'get':
148-
output = c.fetchone()
171+
output = cursor.fetchone()
149172
if output is not None:
150173
output = dict(output)
151174
elif operation == 'has':
152-
output = bool(c.fetchone())
175+
output = bool(cursor.fetchone())
153176
elif operation == 'fetch':
154-
# FIXME a more performant option
155-
output = [dict(i) for i in c]
177+
output = [dict(i) for i in cursor]
156178
elif operation == 'set':
157179
# It is an update
158-
output = c.statusmessage
180+
output = cursor.statusmessage
159181
self._db.commit()
160182
else:
161-
raise ValueError('Unknown operation {}'.format(operation))
162-
c.close()
163-
164-
if cache_key:
165-
try:
166-
self._cache.set(cache_key,
167-
json.dumps(output),
168-
cache_ttl)
169-
except ValueError:
170-
# Woops :S
171-
pass
183+
output = None
184+
cursor.close()
172185

173186
return output
174187

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
from __future__ import absolute_import
2+
3+
import unittest
4+
5+
import flask_phpbb3.backends.psycopg2
6+
7+
import mock
8+
9+
import werkzeug.contrib.cache
10+
11+
12+
@mock.patch('flask_phpbb3.backends.psycopg2.Psycopg2Backend._db')
13+
class TestExecuteOperation(unittest.TestCase):
14+
def setUp(self):
15+
# type: () -> None
16+
self.connection = flask_phpbb3.backends.psycopg2.Psycopg2Backend(
17+
werkzeug.contrib.cache.SimpleCache(),
18+
{
19+
'TABLE_PREFIX': '',
20+
}
21+
)
22+
23+
def test_get_query(self, mocked_db):
24+
# type: (mock.Mock) -> None
25+
parameters = mock.Mock()
26+
cursor = mock.Mock()
27+
cursor.fetchone.return_value = {'key': 'value'}
28+
mocked_db.cursor.return_value = cursor
29+
30+
actual_value = self.connection._execute_operation(
31+
'get',
32+
'select * from somewhere',
33+
parameters
34+
)
35+
36+
self.assertEqual(actual_value, {'key': 'value'})
37+
cursor.execute.assert_called_once_with(
38+
'select * from somewhere',
39+
parameters
40+
)
41+
42+
def test_has_query_negative(self, mocked_db):
43+
# type: (mock.Mock) -> None
44+
cursor = mock.Mock()
45+
cursor.fetchone.return_value = 0
46+
mocked_db.cursor.return_value = cursor
47+
48+
actual_value = self.connection._execute_operation(
49+
'has',
50+
'select * from somewhere',
51+
{}
52+
)
53+
54+
self.assertFalse(actual_value)
55+
56+
def test_has_query_positive(self, mocked_db):
57+
# type: (mock.Mock) -> None
58+
cursor = mock.Mock()
59+
cursor.fetchone.return_value = 1
60+
mocked_db.cursor.return_value = cursor
61+
62+
actual_value = self.connection._execute_operation(
63+
'has',
64+
'select * from somewhere',
65+
{}
66+
)
67+
68+
self.assertTrue(actual_value)
69+
70+
def test_fetch_query(self, mocked_db):
71+
# type: (mock.Mock) -> None
72+
expected_value = [{'key': 1}, {'key': 2}]
73+
cursor = mock.Mock()
74+
cursor.__iter__ = mock.Mock(return_value=iter(expected_value))
75+
mocked_db.cursor.return_value = cursor
76+
77+
actual_value = self.connection._execute_operation(
78+
'fetch',
79+
'select * from somewhere',
80+
{}
81+
)
82+
83+
self.assertEqual(actual_value, expected_value)
84+
85+
def test_set_query(self, mocked_db):
86+
# type: (mock.Mock) -> None
87+
cursor = mock.Mock()
88+
mocked_db.cursor.return_value = cursor
89+
90+
actual_value = self.connection._execute_operation(
91+
'set',
92+
'select * from somewhere',
93+
{}
94+
)
95+
96+
self.assertEqual(actual_value, cursor.statusmessage)
97+
mocked_db.commit.assert_called_once_with()
98+
99+
def test_unknown_query(self, mocked_db):
100+
# type: (mock.Mock) -> None
101+
actual_value = self.connection._execute_operation(
102+
'unknown_op',
103+
'select * from somewhere',
104+
{}
105+
)
106+
107+
# Internal function, this should never happen
108+
self.assertIsNone(actual_value)

0 commit comments

Comments
 (0)