Skip to content

Commit 8c6a90d

Browse files
committed
Merge pull request #1054 from josenavas/fix-inconsistency-on-failure-updating
Fix inconsistency on failure updating
2 parents 3aa8f73 + a6d6989 commit 8c6a90d

File tree

3 files changed

+179
-20
lines changed

3 files changed

+179
-20
lines changed

qiita_db/metadata_template/base_metadata_template.py

Lines changed: 92 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,10 @@
3636
# -----------------------------------------------------------------------------
3737

3838
from __future__ import division
39-
from future.utils import viewitems
39+
from future.utils import viewitems, viewvalues
4040
from os.path import join
4141
from functools import partial
42+
from collections import defaultdict
4243
from copy import deepcopy
4344

4445
import pandas as pd
@@ -48,6 +49,7 @@
4849

4950
from qiita_db.exceptions import (QiitaDBUnknownIDError, QiitaDBColumnError,
5051
QiitaDBNotImplementedError,
52+
QiitaDBExecutionError,
5153
QiitaDBDuplicateHeaderError)
5254
from qiita_db.base import QiitaObject
5355
from qiita_db.sql_connection import SQLConnectionHandler
@@ -313,8 +315,8 @@ def handler(x):
313315
" in template %d" %
314316
(key, self._id, self._md_template.id))
315317

316-
def __setitem__(self, column, value):
317-
r"""Sets the metadata value for the category `column`
318+
def add_setitem_queries(self, column, value, conn_handler, queue):
319+
"""Adds the SQL queries needed to set a value to the provided queue
318320
319321
Parameters
320322
----------
@@ -323,16 +325,16 @@ def __setitem__(self, column, value):
323325
value : str
324326
The value to set. This is expected to be a str on the assumption
325327
that psycopg2 will cast as necessary when updating.
328+
conn_handler : SQLConnectionHandler
329+
The connection handler object connected to the DB
330+
queue : str
331+
The queue where the SQL statements will be added
326332
327333
Raises
328334
------
329335
QiitaDBColumnError
330336
If the column does not exist in the table
331-
ValueError
332-
If the value type does not match the one in the DB
333337
"""
334-
conn_handler = SQLConnectionHandler()
335-
336338
# try dynamic tables
337339
sql = """SELECT EXISTS (
338340
SELECT column_name
@@ -367,27 +369,58 @@ def __setitem__(self, column, value):
367369
raise QiitaDBColumnError("Column %s does not exist in %s" %
368370
(column, self._dynamic_table))
369371

372+
conn_handler.add_to_queue(queue, sql, (value, self._id))
373+
374+
def __setitem__(self, column, value):
375+
r"""Sets the metadata value for the category `column`
376+
377+
Parameters
378+
----------
379+
column : str
380+
The column to update
381+
value : str
382+
The value to set. This is expected to be a str on the assumption
383+
that psycopg2 will cast as necessary when updating.
384+
385+
Raises
386+
------
387+
ValueError
388+
If the value type does not match the one in the DB
389+
"""
390+
conn_handler = SQLConnectionHandler()
391+
queue_name = "set_item_%s" % self._id
392+
conn_handler.create_queue(queue_name)
393+
394+
self.add_setitem_queries(column, value, conn_handler, queue_name)
395+
370396
try:
371-
conn_handler.execute(sql, (value, self._id))
372-
except Exception as e:
397+
conn_handler.execute_queue(queue_name)
398+
except QiitaDBExecutionError as e:
373399
# catching error so we can check if the error is due to different
374400
# column type or something else
401+
type_lookup = defaultdict(lambda: 'varchar')
402+
type_lookup[int] = 'integer'
403+
type_lookup[float] = 'float8'
404+
type_lookup[str] = 'varchar'
405+
value_type = type_lookup[type(value)]
406+
407+
sql = """SELECT udt_name
408+
FROM information_schema.columns
409+
WHERE column_name = %s
410+
AND table_schema = 'qiita'
411+
AND (table_name = %s OR table_name = %s)"""
375412
column_type = conn_handler.execute_fetchone(
376-
"""SELECT data_type
377-
FROM information_schema.columns
378-
WHERE column_name=%s AND table_schema='qiita'
379-
""", (column,))[0]
380-
value_type = type(value).__name__
413+
sql, (column, self._table, self._dynamic_table))
381414

382415
if column_type != value_type:
383416
raise ValueError(
384417
'The new value being added to column: "{0}" is "{1}" '
385418
'(type: "{2}"). However, this column in the DB is of '
386419
'type "{3}". Please change the value in your updated '
387-
'template or reprocess your sample template.'.format(
420+
'template or reprocess your template.'.format(
388421
column, value, value_type, column_type))
389-
else:
390-
raise e
422+
423+
raise e
391424

392425
def __delitem__(self, key):
393426
r"""Removes the sample with sample id `key` from the database
@@ -1180,12 +1213,53 @@ def update_category(self, category, samples_and_values):
11801213
QiitaDBColumnError
11811214
If the column does not exist in the table. This is implicit, and
11821215
can be thrown by the contained Samples.
1216+
ValueError
1217+
If one of the new values cannot be inserted in the DB due to
1218+
different types
11831219
"""
11841220
if not set(self.keys()).issuperset(samples_and_values):
11851221
missing = set(self.keys()) - set(samples_and_values)
11861222
table_name = self._table_name(self.study_id)
11871223
raise QiitaDBUnknownIDError(missing, table_name)
11881224

1225+
conn_handler = SQLConnectionHandler()
1226+
queue_name = "update_category_%s_%s" % (self._id, category)
1227+
conn_handler.create_queue(queue_name)
1228+
11891229
for k, v in viewitems(samples_and_values):
11901230
sample = self[k]
1191-
sample[category] = v
1231+
sample.add_setitem_queries(category, v, conn_handler, queue_name)
1232+
1233+
try:
1234+
conn_handler.execute_queue(queue_name)
1235+
except QiitaDBExecutionError as e:
1236+
# catching error so we can check if the error is due to different
1237+
# column type or something else
1238+
type_lookup = defaultdict(lambda: 'varchar')
1239+
type_lookup[int] = 'integer'
1240+
type_lookup[float] = 'float8'
1241+
type_lookup[str] = 'varchar'
1242+
value_types = set(type_lookup[type(value)]
1243+
for value in viewvalues(samples_and_values))
1244+
1245+
sql = """SELECT udt_name
1246+
FROM information_schema.columns
1247+
WHERE column_name = %s
1248+
AND table_schema = 'qiita'
1249+
AND (table_name = %s OR table_name = %s)"""
1250+
column_type = conn_handler.execute_fetchone(
1251+
sql, (category, self._table, self._table_name(self._id)))
1252+
1253+
if any([column_type != vt for vt in value_types]):
1254+
value_str = ', '.join(
1255+
[str(value) for value in viewvalues(samples_and_values)])
1256+
value_types_str = ', '.join(value_types)
1257+
1258+
raise ValueError(
1259+
'The new values being added to column: "%s" are "%s" '
1260+
'(types: "%s"). However, this column in the DB is of '
1261+
'type "%s". Please change the values in your updated '
1262+
'template or reprocess your template.'
1263+
% (category, value_str, value_types_str, column_type))
1264+
1265+
raise e

qiita_db/metadata_template/test/test_prep_template.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,45 @@ def setUp(self):
5353

5454

5555
class TestPrepSampleReadOnly(BaseTestPrepSample):
56+
def test_add_setitem_queries_error(self):
57+
conn_handler = SQLConnectionHandler()
58+
queue = "test_queue"
59+
conn_handler.create_queue(queue)
60+
61+
with self.assertRaises(QiitaDBColumnError):
62+
self.tester.add_setitem_queries(
63+
'COL_DOES_NOT_EXIST', 'Foo', conn_handler, queue)
64+
65+
def test_add_setitem_queries_required(self):
66+
conn_handler = SQLConnectionHandler()
67+
queue = "test_queue"
68+
conn_handler.create_queue(queue)
69+
70+
self.tester.add_setitem_queries(
71+
'center_name', 'FOO', conn_handler, queue)
72+
73+
obs = conn_handler.queues[queue]
74+
sql = """UPDATE qiita.common_prep_info
75+
SET center_name=%s
76+
WHERE sample_id=%s"""
77+
exp = [(sql, ('FOO', '1.SKB8.640193'))]
78+
self.assertEqual(obs, exp)
79+
80+
def test_add_setitem_queries_dynamic(self):
81+
conn_handler = SQLConnectionHandler()
82+
queue = "test_queue"
83+
conn_handler.create_queue(queue)
84+
85+
self.tester.add_setitem_queries(
86+
'barcodesequence', 'AAAAAAAAAAAA', conn_handler, queue)
87+
88+
obs = conn_handler.queues[queue]
89+
sql = """UPDATE qiita.prep_1
90+
SET barcodesequence=%s
91+
WHERE sample_id=%s"""
92+
exp = [(sql, ('AAAAAAAAAAAA', '1.SKB8.640193'))]
93+
self.assertEqual(obs, exp)
94+
5695
def test_init_unknown_error(self):
5796
"""Init errors if the PrepSample id is not found in the template"""
5897
with self.assertRaises(QiitaDBUnknownIDError):

qiita_db/metadata_template/test/test_sample_template.py

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,45 @@ def setUp(self):
5353

5454

5555
class TestSampleReadOnly(BaseTestSample):
56+
def test_add_setitem_queries_error(self):
57+
conn_handler = SQLConnectionHandler()
58+
queue = "test_queue"
59+
conn_handler.create_queue(queue)
60+
61+
with self.assertRaises(QiitaDBColumnError):
62+
self.tester.add_setitem_queries(
63+
'COL_DOES_NOT_EXIST', 0.30, conn_handler, queue)
64+
65+
def test_add_setitem_queries_required(self):
66+
conn_handler = SQLConnectionHandler()
67+
queue = "test_queue"
68+
conn_handler.create_queue(queue)
69+
70+
self.tester.add_setitem_queries(
71+
'has_physical_specimen', True, conn_handler, queue)
72+
73+
obs = conn_handler.queues[queue]
74+
sql = """UPDATE qiita.required_sample_info
75+
SET has_physical_specimen=%s
76+
WHERE sample_id=%s"""
77+
exp = [(sql, (True, '1.SKB8.640193'))]
78+
self.assertEqual(obs, exp)
79+
80+
def test_add_setitem_queries_dynamic(self):
81+
conn_handler = SQLConnectionHandler()
82+
queue = "test_queue"
83+
conn_handler.create_queue(queue)
84+
85+
self.tester.add_setitem_queries(
86+
'tot_nitro', '1234.5', conn_handler, queue)
87+
88+
obs = conn_handler.queues[queue]
89+
sql = """UPDATE qiita.sample_1
90+
SET tot_nitro=%s
91+
WHERE sample_id=%s"""
92+
exp = [(sql, ('1234.5', '1.SKB8.640193'))]
93+
self.assertEqual(obs, exp)
94+
5695
def test_init_unknown_error(self):
5796
"""Init raises an error if the sample id is not found in the template
5897
"""
@@ -1214,7 +1253,6 @@ def test_exists_false(self):
12141253
self.assertFalse(SampleTemplate.exists(self.new_study.id))
12151254

12161255
def test_update_category(self):
1217-
"""setitem raises an error (currently not allowed)"""
12181256
with self.assertRaises(QiitaDBUnknownIDError):
12191257
self.tester.update_category('country', {"foo": "bar"})
12201258

@@ -1256,10 +1294,18 @@ def test_update_category(self):
12561294
# testing that if fails when trying to change an int column value
12571295
# to str
12581296
st = SampleTemplate.create(self.metadata, self.new_study)
1259-
mapping = {'2.Sample1': "no_value"}
1297+
1298+
sql = """SELECT * FROM qiita.sample_2 ORDER BY sample_id"""
1299+
before = self.conn_handler.execute_fetchall(sql)
1300+
mapping = {'2.Sample1': 1, '2.Sample2': "no_value"}
1301+
12601302
with self.assertRaises(ValueError):
12611303
st.update_category('int_column', mapping)
12621304

1305+
after = self.conn_handler.execute_fetchall(sql)
1306+
1307+
self.assertEqual(before, after)
1308+
12631309
def test_update(self):
12641310
"""Updates values in existing mapping file"""
12651311
# creating a new sample template

0 commit comments

Comments
 (0)