def test_run_quick_check_fails(self): """Quick check fails for non SQLite dtabase files.""" with tempfile.NamedTemporaryFile() as db_file: db_file.write('this is a text file, not a database file') db_file.flush() with Database(db_file.name) as database: self.assertFalse(database.run_quick_check())
def paths(self): """Return paths to valid databases found under directory. :return: Paths to valid databases :rtype: list(str) """ db_paths = self._explore() logger.debug( '%d database paths found under %s:\n%s', len(db_paths), self.directory, '\n'.join(os.path.relpath(db_path, self.directory) for db_path in db_paths)) # Filter out files that don't pass sqlite's quick check # that just can't be opened valid_paths = [] for db_path in db_paths: try: with Database(db_path) as database: if database.run_quick_check(): valid_paths.append(db_path) except OperationalError: logger.warning('Unable to open: %s', db_path) continue logger.debug( '%d database paths passed the integrity check:\n%s', len(valid_paths), '\n'.join(os.path.relpath(valid_path, self.directory) for valid_path in valid_paths)) return valid_paths
def test_ignore_fts_tables(self): """FTS database tables are ignored.""" expected_table_names = ['messages', 'calls', 'events', 'pictures'] fts_table_names = [ '{}_search'.format(table_name) for table_name in expected_table_names ] with tempfile.NamedTemporaryFile() as db_file: self.create_tables( db_file.name, expected_table_names, fts_table_names, ) with Database(db_file.name) as database: db_reader = DBReader(database) table_names = [table_name for table_name in db_reader.tables()] # Check ignored tables are indeed in the database master_table = database['sqlite_master'] query = (select([master_table.c.name ]).where(master_table.c.type == 'table')) result = database.connection.execute(query) all_table_names = set(row[0] for row in result.fetchall()) for table_name in fts_table_names: self.assertIn(table_name, all_table_names) self.assertListEqual(sorted(table_names), sorted(expected_table_names))
def test_run_quick_check_passes(self): """Quick check passes for SQLite database.""" with tempfile.NamedTemporaryFile() as db_file: with closing(sqlite3.connect(db_file.name)) as connection: with closing(connection.cursor()) as cursor: cursor.execute( 'CREATE TABLE messages (id INTEGER, message TEXT)') with Database(db_file.name) as database: self.assertTrue(database.run_quick_check())
def test_tables(self): """Database tables are correctly retrieved.""" expected_table_names = sorted( ['messages', 'calls', 'events', 'pictures']) with tempfile.NamedTemporaryFile() as db_file: self.create_tables(db_file.name, expected_table_names) with Database(db_file.name) as database: db_reader = DBReader(database) table_names = sorted(db_reader.tables()) self.assertListEqual(table_names, expected_table_names)
def test_type_error_on_wrong_table_name(self): """TypeError raised when table name is not a string.""" with tempfile.NamedTemporaryFile() as db_file: with closing(sqlite3.connect(db_file.name)) as connection: with closing(connection.cursor()) as cursor: cursor.execute( 'CREATE TABLE messages (id INTEGER, message TEXT)') database = Database(db_file.name) with self.assertRaises(TypeError): database[0]
def test_get_unknown_table_metadata(self): """NoSuchTableError raised when table name is not found.""" with tempfile.NamedTemporaryFile() as db_file: with closing(sqlite3.connect(db_file.name)) as connection: with closing(connection.cursor()) as cursor: cursor.execute( 'CREATE TABLE messages (id INTEGER, message TEXT)') database = Database(db_file.name) with self.assertRaises(NoSuchTableError): database['unknown']
def test_context_manager(self): """Connection is opened/closed when used as a context manager.""" database = Database(':memory:') # Connection is None when database object is created self.assertIsNone(database.connection) with database: # Connection is not closed inside the context self.assertFalse(database.connection.closed) # Connection is closed outside the context self.assertTrue(database.connection.closed)
def test_get_table_metadata(self): """Table metadata can be retrieved using index notation.""" with tempfile.NamedTemporaryFile() as db_file: with closing(sqlite3.connect(db_file.name)) as connection: with closing(connection.cursor()) as cursor: cursor.execute( 'CREATE TABLE messages (id INTEGER, message TEXT)') database = Database(db_file.name) table = database['messages'] schema = { column.name: type(column.type) for column in table.columns } self.assertDictEqual(schema, {'id': INTEGER, 'message': TEXT})
def _index_directory(self, directory): """Index all databases under a given directory. :param directory: Path to the directory to explore :type directory: str :return: Documents indexed for this directory :rtype: int """ documents_indexed = 0 self._recreate_index(self.INDEX_NAME) tree_explorer = TreeExplorer(directory) for db_path in tree_explorer.paths(): with Database(db_path) as database: documents_indexed += self._index_database(database) return documents_indexed
def setUpClass(cls): """Create test database. Database is reused between test cases because they just read data without chaning the database in any way. """ with tempfile.NamedTemporaryFile(delete=False) as cls.db_file: with closing(sqlite3.connect(cls.db_file.name)) as connection: with closing(connection.cursor()) as cursor: cursor.execute( 'CREATE TABLE messages (id INTEGER, message TEXT);') cls.message_values = [(1, 'one message'), (2, 'another message'), (3, 'one more message')] cursor.executemany('INSERT INTO messages VALUES(?, ?);', cls.message_values) cursor.execute( 'CREATE TABLE calls (_id INTEGER, number TEXT);') cls.call_values = [(1, '123456789'), (2, '234567890'), (3, '345678901')] cursor.executemany('INSERT INTO calls VALUES(?, ?);', cls.call_values) cursor.execute( 'CREATE TABLE events (_id INTEGER, description TEXT);') cls.event_values = [(1, 'holiday'), (2, 'meeting'), (1, 'reminder')] cursor.executemany('INSERT INTO events VALUES(?, ?);', cls.event_values) cursor.execute('CREATE TABLE pictures ' '(id INTEGER, raw_data BLOB);') cls.picture_values = [(1, ''), (2, ''), (3, '')] cursor.executemany('INSERT INTO pictures VALUES(?, ?);', cls.picture_values) connection.commit() cls.database = Database(cls.db_file.name) cls.database.connect()