backup.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. import sqlite3 as sqlite
  2. import unittest
  3. @unittest.skipIf(sqlite.sqlite_version_info < (3, 6, 11), "Backup API not supported")
  4. class BackupTests(unittest.TestCase):
  5. def setUp(self):
  6. cx = self.cx = sqlite.connect(":memory:")
  7. cx.execute('CREATE TABLE foo (key INTEGER)')
  8. cx.executemany('INSERT INTO foo (key) VALUES (?)', [(3,), (4,)])
  9. cx.commit()
  10. def tearDown(self):
  11. self.cx.close()
  12. def verify_backup(self, bckcx):
  13. result = bckcx.execute("SELECT key FROM foo ORDER BY key").fetchall()
  14. self.assertEqual(result[0][0], 3)
  15. self.assertEqual(result[1][0], 4)
  16. def test_bad_target_none(self):
  17. with self.assertRaises(TypeError):
  18. self.cx.backup(None)
  19. def test_bad_target_filename(self):
  20. with self.assertRaises(TypeError):
  21. self.cx.backup('some_file_name.db')
  22. def test_bad_target_same_connection(self):
  23. with self.assertRaises(ValueError):
  24. self.cx.backup(self.cx)
  25. def test_bad_target_closed_connection(self):
  26. bck = sqlite.connect(':memory:')
  27. bck.close()
  28. with self.assertRaises(sqlite.ProgrammingError):
  29. self.cx.backup(bck)
  30. def test_bad_source_closed_connection(self):
  31. bck = sqlite.connect(':memory:')
  32. source = sqlite.connect(":memory:")
  33. source.close()
  34. with self.assertRaises(sqlite.ProgrammingError):
  35. source.backup(bck)
  36. def test_bad_target_in_transaction(self):
  37. bck = sqlite.connect(':memory:')
  38. bck.execute('CREATE TABLE bar (key INTEGER)')
  39. bck.executemany('INSERT INTO bar (key) VALUES (?)', [(3,), (4,)])
  40. with self.assertRaises(sqlite.OperationalError) as cm:
  41. self.cx.backup(bck)
  42. if sqlite.sqlite_version_info < (3, 8, 8):
  43. self.assertEqual(str(cm.exception), 'target is in transaction')
  44. def test_keyword_only_args(self):
  45. with self.assertRaises(TypeError):
  46. with sqlite.connect(':memory:') as bck:
  47. self.cx.backup(bck, 1)
  48. def test_simple(self):
  49. with sqlite.connect(':memory:') as bck:
  50. self.cx.backup(bck)
  51. self.verify_backup(bck)
  52. def test_progress(self):
  53. journal = []
  54. def progress(status, remaining, total):
  55. journal.append(status)
  56. with sqlite.connect(':memory:') as bck:
  57. self.cx.backup(bck, pages=1, progress=progress)
  58. self.verify_backup(bck)
  59. self.assertEqual(len(journal), 2)
  60. self.assertEqual(journal[0], sqlite.SQLITE_OK)
  61. self.assertEqual(journal[1], sqlite.SQLITE_DONE)
  62. def test_progress_all_pages_at_once_1(self):
  63. journal = []
  64. def progress(status, remaining, total):
  65. journal.append(remaining)
  66. with sqlite.connect(':memory:') as bck:
  67. self.cx.backup(bck, progress=progress)
  68. self.verify_backup(bck)
  69. self.assertEqual(len(journal), 1)
  70. self.assertEqual(journal[0], 0)
  71. def test_progress_all_pages_at_once_2(self):
  72. journal = []
  73. def progress(status, remaining, total):
  74. journal.append(remaining)
  75. with sqlite.connect(':memory:') as bck:
  76. self.cx.backup(bck, pages=-1, progress=progress)
  77. self.verify_backup(bck)
  78. self.assertEqual(len(journal), 1)
  79. self.assertEqual(journal[0], 0)
  80. def test_non_callable_progress(self):
  81. with self.assertRaises(TypeError) as cm:
  82. with sqlite.connect(':memory:') as bck:
  83. self.cx.backup(bck, pages=1, progress='bar')
  84. self.assertEqual(str(cm.exception), 'progress argument must be a callable')
  85. def test_modifying_progress(self):
  86. journal = []
  87. def progress(status, remaining, total):
  88. if not journal:
  89. self.cx.execute('INSERT INTO foo (key) VALUES (?)', (remaining+1000,))
  90. self.cx.commit()
  91. journal.append(remaining)
  92. with sqlite.connect(':memory:') as bck:
  93. self.cx.backup(bck, pages=1, progress=progress)
  94. self.verify_backup(bck)
  95. result = bck.execute("SELECT key FROM foo"
  96. " WHERE key >= 1000"
  97. " ORDER BY key").fetchall()
  98. self.assertEqual(result[0][0], 1001)
  99. self.assertEqual(len(journal), 3)
  100. self.assertEqual(journal[0], 1)
  101. self.assertEqual(journal[1], 1)
  102. self.assertEqual(journal[2], 0)
  103. def test_failing_progress(self):
  104. def progress(status, remaining, total):
  105. raise SystemError('nearly out of space')
  106. with self.assertRaises(SystemError) as err:
  107. with sqlite.connect(':memory:') as bck:
  108. self.cx.backup(bck, progress=progress)
  109. self.assertEqual(str(err.exception), 'nearly out of space')
  110. def test_database_source_name(self):
  111. with sqlite.connect(':memory:') as bck:
  112. self.cx.backup(bck, name='main')
  113. with sqlite.connect(':memory:') as bck:
  114. self.cx.backup(bck, name='temp')
  115. with self.assertRaises(sqlite.OperationalError) as cm:
  116. with sqlite.connect(':memory:') as bck:
  117. self.cx.backup(bck, name='non-existing')
  118. self.assertIn(
  119. str(cm.exception),
  120. ['SQL logic error', 'SQL logic error or missing database',
  121. 'unknown database non-existing']
  122. )
  123. self.cx.execute("ATTACH DATABASE ':memory:' AS attached_db")
  124. self.cx.execute('CREATE TABLE attached_db.foo (key INTEGER)')
  125. self.cx.executemany('INSERT INTO attached_db.foo (key) VALUES (?)', [(3,), (4,)])
  126. self.cx.commit()
  127. with sqlite.connect(':memory:') as bck:
  128. self.cx.backup(bck, name='attached_db')
  129. self.verify_backup(bck)
  130. def suite():
  131. return unittest.makeSuite(BackupTests)
  132. if __name__ == "__main__":
  133. unittest.main()