860 res = self._eid_type_source(cnx, eid, sql) |
860 res = self._eid_type_source(cnx, eid, sql) |
861 if not isinstance(res, list): |
861 if not isinstance(res, list): |
862 res = list(res) |
862 res = list(res) |
863 if res[-1] is not None: |
863 if res[-1] is not None: |
864 res[-1] = b64decode(res[-1]) |
864 res[-1] = b64decode(res[-1]) |
865 res.append(res[1]) |
865 res.append("system") |
866 return res |
866 return res |
867 |
867 |
868 def extid2eid(self, cnx, extid): |
868 def extid2eid(self, cnx, extid): |
869 """get eid from an external id. Return None if no record found.""" |
869 """get eid from an external id. Return None if no record found.""" |
870 assert isinstance(extid, str) |
870 assert isinstance(extid, str) |
1568 |
1568 |
1569 The backup and restore methods are used to dump / restore the |
1569 The backup and restore methods are used to dump / restore the |
1570 system database in a database independent format. The file is a |
1570 system database in a database independent format. The file is a |
1571 Zip archive containing the following files: |
1571 Zip archive containing the following files: |
1572 |
1572 |
1573 * format.txt: the format of the archive. Currently '1.0' |
1573 * format.txt: the format of the archive. Currently '1.1' |
1574 * tables.txt: list of filenames in the archive tables/ directory |
1574 * tables.txt: list of filenames in the archive tables/ directory |
1575 * sequences.txt: list of filenames in the archive sequences/ directory |
1575 * sequences.txt: list of filenames in the archive sequences/ directory |
|
1576 * numranges.txt: list of filenames in the archive numrange/ directory |
1576 * versions.txt: the list of cube versions from CWProperty |
1577 * versions.txt: the list of cube versions from CWProperty |
1577 * tables/<tablename>.<chunkno>: pickled data |
1578 * tables/<tablename>.<chunkno>: pickled data |
1578 * sequences/<sequencename>: pickled data |
1579 * sequences/<sequencename>: pickled data |
1579 |
1580 |
1580 The pickled data format for tables and sequences is a tuple of 3 elements: |
1581 The pickled data format for tables, numranges and sequences is a tuple of 3 elements: |
1581 * the table name |
1582 * the table name |
1582 * a tuple of column names |
1583 * a tuple of column names |
1583 * a list of rows (as tuples with one element per column) |
1584 * a list of rows (as tuples with one element per column) |
1584 |
1585 |
1585 Tables are saved in chunks in different files in order to prevent |
1586 Tables are saved in chunks in different files in order to prevent |
1613 self.logger.info('writing metadata') |
1614 self.logger.info('writing metadata') |
1614 self.write_metadata(archive) |
1615 self.write_metadata(archive) |
1615 for seq in self.get_sequences(): |
1616 for seq in self.get_sequences(): |
1616 self.logger.info('processing sequence %s', seq) |
1617 self.logger.info('processing sequence %s', seq) |
1617 self.write_sequence(archive, seq) |
1618 self.write_sequence(archive, seq) |
|
1619 for numrange in self.get_numranges(): |
|
1620 self.logger.info('processing numrange %s', numrange) |
|
1621 self.write_numrange(archive, numrange) |
1618 for table in self.get_tables(): |
1622 for table in self.get_tables(): |
1619 self.logger.info('processing table %s', table) |
1623 self.logger.info('processing table %s', table) |
1620 self.write_table(archive, table) |
1624 self.write_table(archive, table) |
1621 finally: |
1625 finally: |
1622 archive.close() |
1626 archive.close() |
1643 continue |
1647 continue |
1644 relation_tables.append('%s_relation' % rtype) |
1648 relation_tables.append('%s_relation' % rtype) |
1645 return non_entity_tables + etype_tables + relation_tables |
1649 return non_entity_tables + etype_tables + relation_tables |
1646 |
1650 |
1647 def get_sequences(self): |
1651 def get_sequences(self): |
|
1652 return [] |
|
1653 |
|
1654 def get_numranges(self): |
1648 return ['entities_id_seq'] |
1655 return ['entities_id_seq'] |
1649 |
1656 |
1650 def write_metadata(self, archive): |
1657 def write_metadata(self, archive): |
1651 archive.writestr('format.txt', '1.0') |
1658 archive.writestr('format.txt', '1.1') |
1652 archive.writestr('tables.txt', '\n'.join(self.get_tables())) |
1659 archive.writestr('tables.txt', '\n'.join(self.get_tables())) |
1653 archive.writestr('sequences.txt', '\n'.join(self.get_sequences())) |
1660 archive.writestr('sequences.txt', '\n'.join(self.get_sequences())) |
|
1661 archive.writestr('numranges.txt', '\n'.join(self.get_numranges())) |
1654 versions = self._get_versions() |
1662 versions = self._get_versions() |
1655 versions_str = '\n'.join('%s %s' % (k, v) |
1663 versions_str = '\n'.join('%s %s' % (k, v) |
1656 for k, v in versions) |
1664 for k, v in versions) |
1657 archive.writestr('versions.txt', versions_str) |
1665 archive.writestr('versions.txt', versions_str) |
1658 |
1666 |
1660 sql = self.dbhelper.sql_sequence_current_state(seq) |
1668 sql = self.dbhelper.sql_sequence_current_state(seq) |
1661 columns, rows_iterator = self._get_cols_and_rows(sql) |
1669 columns, rows_iterator = self._get_cols_and_rows(sql) |
1662 rows = list(rows_iterator) |
1670 rows = list(rows_iterator) |
1663 serialized = self._serialize(seq, columns, rows) |
1671 serialized = self._serialize(seq, columns, rows) |
1664 archive.writestr('sequences/%s' % seq, serialized) |
1672 archive.writestr('sequences/%s' % seq, serialized) |
|
1673 |
|
1674 def write_numrange(self, archive, numrange): |
|
1675 sql = self.dbhelper.sql_numrange_current_state(numrange) |
|
1676 columns, rows_iterator = self._get_cols_and_rows(sql) |
|
1677 rows = list(rows_iterator) |
|
1678 serialized = self._serialize(numrange, columns, rows) |
|
1679 archive.writestr('numrange/%s' % numrange, serialized) |
1665 |
1680 |
1666 def write_table(self, archive, table): |
1681 def write_table(self, archive, table): |
1667 nb_lines_sql = 'SELECT COUNT(*) FROM %s' % table |
1682 nb_lines_sql = 'SELECT COUNT(*) FROM %s' % table |
1668 self.cursor.execute(nb_lines_sql) |
1683 self.cursor.execute(nb_lines_sql) |
1669 rowcount = self.cursor.fetchone()[0] |
1684 rowcount = self.cursor.fetchone()[0] |
1697 |
1712 |
1698 def restore(self, backupfile): |
1713 def restore(self, backupfile): |
1699 archive = zipfile.ZipFile(backupfile, 'r', allowZip64=True) |
1714 archive = zipfile.ZipFile(backupfile, 'r', allowZip64=True) |
1700 self.cnx = self.get_connection() |
1715 self.cnx = self.get_connection() |
1701 self.cursor = self.cnx.cursor() |
1716 self.cursor = self.cnx.cursor() |
1702 sequences, tables, table_chunks = self.read_metadata(archive, backupfile) |
1717 sequences, numranges, tables, table_chunks = self.read_metadata(archive, backupfile) |
1703 for seq in sequences: |
1718 for seq in sequences: |
1704 self.logger.info('restoring sequence %s', seq) |
1719 self.logger.info('restoring sequence %s', seq) |
1705 self.read_sequence(archive, seq) |
1720 self.read_sequence(archive, seq) |
|
1721 for numrange in numranges: |
|
1722 self.logger.info('restoring numrange %s', seq) |
|
1723 self.read_numrange(archive, numrange) |
1706 for table in tables: |
1724 for table in tables: |
1707 self.logger.info('restoring table %s', table) |
1725 self.logger.info('restoring table %s', table) |
1708 self.read_table(archive, table, sorted(table_chunks[table])) |
1726 self.read_table(archive, table, sorted(table_chunks[table])) |
1709 self.cnx.close() |
1727 self.cnx.close() |
1710 archive.close() |
1728 archive.close() |
1711 self.logger.info('done') |
1729 self.logger.info('done') |
1712 |
1730 |
1713 def read_metadata(self, archive, backupfile): |
1731 def read_metadata(self, archive, backupfile): |
1714 formatinfo = archive.read('format.txt') |
1732 formatinfo = archive.read('format.txt') |
1715 self.logger.info('checking metadata') |
1733 self.logger.info('checking metadata') |
1716 if formatinfo.strip() != "1.0": |
1734 if formatinfo.strip() != "1.1": |
1717 self.logger.critical('Unsupported format in archive: %s', formatinfo) |
1735 self.logger.critical('Unsupported format in archive: %s', formatinfo) |
1718 raise ValueError('Unknown format in %s: %s' % (backupfile, formatinfo)) |
1736 raise ValueError('Unknown format in %s: %s' % (backupfile, formatinfo)) |
1719 tables = archive.read('tables.txt').splitlines() |
1737 tables = archive.read('tables.txt').splitlines() |
1720 sequences = archive.read('sequences.txt').splitlines() |
1738 sequences = archive.read('sequences.txt').splitlines() |
|
1739 numranges = archive.read('numranges.txt').splitlines() |
1721 file_versions = self._parse_versions(archive.read('versions.txt')) |
1740 file_versions = self._parse_versions(archive.read('versions.txt')) |
1722 versions = set(self._get_versions()) |
1741 versions = set(self._get_versions()) |
1723 if file_versions != versions: |
1742 if file_versions != versions: |
1724 self.logger.critical('Unable to restore : versions do not match') |
1743 self.logger.critical('Unable to restore : versions do not match') |
1725 self.logger.critical('Expected:\n%s', '\n'.join('%s : %s' % (cube, ver) |
1744 self.logger.critical('Expected:\n%s', '\n'.join('%s : %s' % (cube, ver) |
1732 if not name.startswith('tables/'): |
1751 if not name.startswith('tables/'): |
1733 continue |
1752 continue |
1734 filename = basename(name) |
1753 filename = basename(name) |
1735 tablename, _ext = filename.rsplit('.', 1) |
1754 tablename, _ext = filename.rsplit('.', 1) |
1736 table_chunks.setdefault(tablename, []).append(name) |
1755 table_chunks.setdefault(tablename, []).append(name) |
1737 return sequences, tables, table_chunks |
1756 return sequences, numranges, tables, table_chunks |
1738 |
1757 |
1739 def read_sequence(self, archive, seq): |
1758 def read_sequence(self, archive, seq): |
1740 seqname, columns, rows = loads(archive.read('sequences/%s' % seq)) |
1759 seqname, columns, rows = loads(archive.read('sequences/%s' % seq)) |
1741 assert seqname == seq |
1760 assert seqname == seq |
1742 assert len(rows) == 1 |
1761 assert len(rows) == 1 |
1743 assert len(rows[0]) == 1 |
1762 assert len(rows[0]) == 1 |
1744 value = rows[0][0] |
1763 value = rows[0][0] |
1745 sql = self.dbhelper.sql_restart_sequence(seq, value) |
1764 sql = self.dbhelper.sql_restart_sequence(seq, value) |
|
1765 self.cursor.execute(sql) |
|
1766 self.cnx.commit() |
|
1767 |
|
1768 def read_numrange(self, archive, numrange): |
|
1769 rangename, columns, rows = loads(archive.read('numrange/%s' % numrange)) |
|
1770 assert rangename == numrange |
|
1771 assert len(rows) == 1 |
|
1772 assert len(rows[0]) == 1 |
|
1773 value = rows[0][0] |
|
1774 sql = self.dbhelper.sql_restart_numrange(numrange, value) |
1746 self.cursor.execute(sql) |
1775 self.cursor.execute(sql) |
1747 self.cnx.commit() |
1776 self.cnx.commit() |
1748 |
1777 |
1749 def read_table(self, archive, table, filenames): |
1778 def read_table(self, archive, table, filenames): |
1750 merge_args = self._source.merge_args |
1779 merge_args = self._source.merge_args |