Skip to content

Commit 42b4191

Browse files
davidshinnjreback
authored andcommitted
ENH/TST: Raise error if invalid value passed to if_exists argument
The if_exists argument in io.sql.write_frame needed data validation because the logic of the function implicitly used 'append' if the argument value was any string that was not either 'fail' or 'replace'. I added a new unit test to support the requirement. BUG: Fix if_exists='replace' functionality in io.sql.write_frame This should resolve issues pandas-dev#2971 and pandas-dev#4110 CLN: Refactor in between test clean ups to be more DRY TST: Complete test coverage for if_exists uses in io.sql.write_frame CLN: Refactor to make interaction between exists and if_exists clearer This refactor results in the function logic being clearer, since if_exists is only relevant when exists is True, the program flow is better served to have if_exists control flow only when exists is True BUG: Fix regression introduced by c28f11a0041a9f3b25f33b0539e42fa802b1d8d4 sqlite3 convenience function executescript not available in other database flavors. TST: Adding if_exist test for mysql flavor
1 parent eb54f49 commit 42b4191

File tree

2 files changed

+133
-4
lines changed

2 files changed

+133
-4
lines changed

pandas/io/sql.py

+14-4
Original file line numberDiff line numberDiff line change
@@ -200,15 +200,25 @@ def write_frame(frame, name, con, flavor='sqlite', if_exists='fail', **kwargs):
200200
if_exists = 'append'
201201
else:
202202
if_exists = 'fail'
203+
204+
if if_exists not in ('fail', 'replace', 'append'):
205+
raise ValueError, "'%s' is not valid for if_exists" % if_exists
206+
203207
exists = table_exists(name, con, flavor)
204208
if if_exists == 'fail' and exists:
205209
raise ValueError("Table '%s' already exists." % name)
206210

207-
#create or drop-recreate if necessary
211+
# creation/replacement dependent on the table existing and if_exist criteria
208212
create = None
209-
if exists and if_exists == 'replace':
210-
create = "DROP TABLE %s" % name
211-
elif not exists:
213+
if exists:
214+
if if_exists == 'fail':
215+
raise ValueError, "Table '%s' already exists." % name
216+
elif if_exists == 'replace':
217+
cur = con.cursor()
218+
cur.execute("DROP TABLE %s;" % name)
219+
cur.close()
220+
create = get_schema(frame, name, flavor)
221+
else:
212222
create = get_schema(frame, name, flavor)
213223

214224
if create is not None:

pandas/io/tests/test_sql.py

+119
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,65 @@ def test_onecolumn_of_integer(self):
240240
result = sql.read_frame("select * from mono_df",con_x)
241241
tm.assert_frame_equal(result,mono_df)
242242

243+
def test_if_exists(self):
244+
df_if_exists_1 = DataFrame({'col1': [1, 2], 'col2': ['A', 'B']})
245+
df_if_exists_2 = DataFrame({'col1': [3, 4, 5], 'col2': ['C', 'D', 'E']})
246+
table_name = 'table_if_exists'
247+
sql_select = "SELECT * FROM %s" % table_name
248+
249+
def clean_up(test_table_to_drop):
250+
"""
251+
Drops tables created from individual tests
252+
so no dependencies arise from sequential tests
253+
"""
254+
if sql.table_exists(test_table_to_drop, self.db, flavor='sqlite'):
255+
cur = self.db.cursor()
256+
cur.execute("DROP TABLE %s" % test_table_to_drop)
257+
cur.close()
258+
259+
# test if invalid value for if_exists raises appropriate error
260+
self.assertRaises(ValueError,
261+
sql.write_frame,
262+
frame=df_if_exists_1,
263+
con=self.db,
264+
name=table_name,
265+
flavor='sqlite',
266+
if_exists='notvalidvalue')
267+
clean_up(table_name)
268+
269+
# test if_exists='fail'
270+
sql.write_frame(frame=df_if_exists_1, con=self.db, name=table_name,
271+
flavor='sqlite', if_exists='fail')
272+
self.assertRaises(ValueError,
273+
sql.write_frame,
274+
frame=df_if_exists_1,
275+
con=self.db,
276+
name=table_name,
277+
flavor='sqlite',
278+
if_exists='fail')
279+
280+
# test if_exists='replace'
281+
sql.write_frame(frame=df_if_exists_1, con=self.db, name=table_name,
282+
flavor='sqlite', if_exists='replace')
283+
self.assertEqual(sql.tquery(sql_select, con=self.db),
284+
[(1, 'A'), (2, 'B')])
285+
sql.write_frame(frame=df_if_exists_2, con=self.db, name=table_name,
286+
flavor='sqlite', if_exists='replace')
287+
self.assertEqual(sql.tquery(sql_select, con=self.db),
288+
[(3, 'C'), (4, 'D'), (5, 'E')])
289+
clean_up(table_name)
290+
291+
# test if_exists='append'
292+
sql.write_frame(frame=df_if_exists_1, con=self.db, name=table_name,
293+
flavor='sqlite', if_exists='fail')
294+
self.assertEqual(sql.tquery(sql_select, con=self.db),
295+
[(1, 'A'), (2, 'B')])
296+
sql.write_frame(frame=df_if_exists_2, con=self.db, name=table_name,
297+
flavor='sqlite', if_exists='append')
298+
self.assertEqual(sql.tquery(sql_select, con=self.db),
299+
[(1, 'A'), (2, 'B'), (3, 'C'), (4, 'D'), (5, 'E')])
300+
clean_up(table_name)
301+
243302

244303
class TestMySQL(tm.TestCase):
245304

@@ -483,6 +542,66 @@ def test_keyword_as_column_names(self):
483542
sql.write_frame(df, con = self.db, name = 'testkeywords',
484543
if_exists='replace', flavor='mysql')
485544

545+
def test_if_exists(self):
546+
_skip_if_no_MySQLdb()
547+
df_if_exists_1 = DataFrame({'col1': [1, 2], 'col2': ['A', 'B']})
548+
df_if_exists_2 = DataFrame({'col1': [3, 4, 5], 'col2': ['C', 'D', 'E']})
549+
table_name = 'table_if_exists'
550+
sql_select = "SELECT * FROM %s" % table_name
551+
552+
def clean_up(test_table_to_drop):
553+
"""
554+
Drops tables created from individual tests
555+
so no dependencies arise from sequential tests
556+
"""
557+
if sql.table_exists(test_table_to_drop, self.db, flavor='mysql'):
558+
cur = self.db.cursor()
559+
cur.execute("DROP TABLE %s" % test_table_to_drop)
560+
cur.close()
561+
562+
# test if invalid value for if_exists raises appropriate error
563+
self.assertRaises(ValueError,
564+
sql.write_frame,
565+
frame=df_if_exists_1,
566+
con=self.db,
567+
name=table_name,
568+
flavor='mysql',
569+
if_exists='notvalidvalue')
570+
clean_up(table_name)
571+
572+
# test if_exists='fail'
573+
sql.write_frame(frame=df_if_exists_1, con=self.db, name=table_name,
574+
flavor='mysql', if_exists='fail')
575+
self.assertRaises(ValueError,
576+
sql.write_frame,
577+
frame=df_if_exists_1,
578+
con=self.db,
579+
name=table_name,
580+
flavor='mysql',
581+
if_exists='fail')
582+
583+
# test if_exists='replace'
584+
sql.write_frame(frame=df_if_exists_1, con=self.db, name=table_name,
585+
flavor='mysql', if_exists='replace')
586+
self.assertEqual(sql.tquery(sql_select, con=self.db),
587+
[(1, 'A'), (2, 'B')])
588+
sql.write_frame(frame=df_if_exists_2, con=self.db, name=table_name,
589+
flavor='mysql', if_exists='replace')
590+
self.assertEqual(sql.tquery(sql_select, con=self.db),
591+
[(3, 'C'), (4, 'D'), (5, 'E')])
592+
clean_up(table_name)
593+
594+
# test if_exists='append'
595+
sql.write_frame(frame=df_if_exists_1, con=self.db, name=table_name,
596+
flavor='mysql', if_exists='fail')
597+
self.assertEqual(sql.tquery(sql_select, con=self.db),
598+
[(1, 'A'), (2, 'B')])
599+
sql.write_frame(frame=df_if_exists_2, con=self.db, name=table_name,
600+
flavor='mysql', if_exists='append')
601+
self.assertEqual(sql.tquery(sql_select, con=self.db),
602+
[(1, 'A'), (2, 'B'), (3, 'C'), (4, 'D'), (5, 'E')])
603+
clean_up(table_name)
604+
486605

487606
if __name__ == '__main__':
488607
nose.runmodule(argv=[__file__, '-vvs', '-x', '--pdb', '--pdb-failure'],

0 commit comments

Comments
 (0)