Index: trunk/Misc/ACKS =================================================================== --- trunk/Misc/ACKS (revision 61630) +++ trunk/Misc/ACKS (working copy) @@ -361,6 +361,7 @@ Vivek Khera Mads Kiilerich Taek Joo Kim +Paul Kippes Steve Kirsch Ron Klatchko Bastian Kleineidam Index: trunk/Doc/library/sqlite3.rst =================================================================== --- trunk/Doc/library/sqlite3.rst (revision 61630) +++ trunk/Doc/library/sqlite3.rst (working copy) @@ -378,6 +378,25 @@ deleted since the database connection was opened. +.. attribute:: Connection.iterdump + + Returns an iterator to dump the database in an SQL text format. Useful when + saving an in-memory database for later restoration. This function provides + the same capabilities as the :kbd:`.dump` command in the :program:`sqlite3` + shell. + + Example:: + + # Convert file existing_db.db to SQL dump file dump.sql + import sqlite3, os + + con = sqlite3.connect('existing_db.db') + full_dump = os.linesep.join([line for line in con.iterdump()]) + f = open('dump.sql', 'w') + f.writelines(full_dump) + f.close() + + .. _sqlite3-cursor-objects: Cursor Objects Index: trunk/Lib/sqlite3/test/dump.py =================================================================== --- trunk/Lib/sqlite3/test/dump.py (revision 0) +++ trunk/Lib/sqlite3/test/dump.py (revision 0) @@ -0,0 +1,52 @@ +# Author: Paul Kippes + +import unittest +import sqlite3 as sqlite + +class DumpTests(unittest.TestCase): + def setUp(self): + self.cx = sqlite.connect(":memory:") + self.cu = self.cx.cursor() + + def tearDown(self): + self.cx.close() + + def CheckTableDump(self): + expected_sqls = [ + "CREATE TABLE t1(id integer primary key, s1 text, " \ + "t1_i1 integer not null, i2 integer, unique (s1), " \ + "constraint t1_idx1 unique (i2));" + , + "INSERT INTO \"t1\" VALUES(1,'foo',10,20);" + , + "INSERT INTO \"t1\" VALUES(2,'foo2',30,30);" + , + "CREATE TABLE t2(id integer, t2_i1 integer, " \ + "t2_i2 integer, primary key (id)," \ + "foreign key(t2_i1) references t1(t1_i1));" + , + "CREATE TRIGGER trigger_1 update of t1_i1 on t1 " \ + "begin " \ + "update t2 set t2_i1 = new.t1_i1 where t2_i1 = old.t1_i1; " \ + "end;" + , + "CREATE VIEW v1 as select * from t1 left join t2 " \ + "using (id);" + ] + [self.cu.execute(s) for s in expected_sqls] + i = self.cx.iterdump() + actual_sqls = [s for s in i] + expected_sqls = ['BEGIN TRANSACTION;'] + expected_sqls + \ + ['COMMIT;'] + [self.assertEqual(expected_sqls[i], actual_sqls[i]) + for i in xrange(len(expected_sqls))] + +def suite(): + return unittest.TestSuite(unittest.makeSuite(DumpTests, "Check")) + +def test(): + runner = unittest.TextTestRunner() + runner.run(suite()) + +if __name__ == "__main__": + test() Index: trunk/Lib/sqlite3/dump.py =================================================================== --- trunk/Lib/sqlite3/dump.py (revision 0) +++ trunk/Lib/sqlite3/dump.py (revision 0) @@ -0,0 +1,63 @@ +# Mimic the sqlite3 console shell's .dump command +# Author: Paul Kippes + +def _iterdump(connection): + """ + Returns an iterator to the dump of the database in an SQL text format. + + Used to produce an SQL dump of the database. Useful to save an in-memory + database for later restoration. This function should not be called + directly but instead called from the Connection method, iterdump(). + """ + + cu = connection.cursor() + yield('BEGIN TRANSACTION;') + + # sqlite_master table contains the SQL CREATE statements for the database. + q = """ + SELECT name, type, sql + FROM sqlite_master + WHERE sql NOT NULL AND + type == 'table' + """ + schema_res = cu.execute(q) + for table_name, type, sql in schema_res.fetchall(): + if table_name == 'sqlite_sequence': + yield('DELETE FROM sqlite_sequence;') + elif table_name == 'sqlite_stat1': + yield('ANALYZE sqlite_master;') + elif table_name.startswith('sqlite_'): + continue + # NOTE: Virtual table support not implemented + #elif sql.startswith('CREATE VIRTUAL TABLE'): + # qtable = table_name.replace("'", "''") + # yield("INSERT INTO sqlite_master(type,name,tbl_name,rootpage,sql)"\ + # "VALUES('table','%s','%s',0,'%s');" % + # qtable, + # qtable, + # sql.replace("''")) + else: + yield('%s;' % sql) + + # Build the insert statement for each row of the current table + res = cu.execute("PRAGMA table_info('%s')" % table_name) + column_names = [str(table_info[1]) for table_info in res.fetchall()] + q = "SELECT 'INSERT INTO \"%(tbl_name)s\" VALUES(" + q += ",".join(["'||quote(" + col + ")||'" for col in column_names]) + q += ")' FROM '%(tbl_name)s'" + query_res = cu.execute(q % {'tbl_name': table_name}) + for row in query_res: + yield("%s;" % row[0]) + + # Now when the type is 'index', 'trigger', or 'view' + q = """ + SELECT name, type, sql + FROM sqlite_master + WHERE sql NOT NULL AND + type IN ('index', 'trigger', 'view') + """ + schema_res = cu.execute(q) + for name, type, sql in schema_res.fetchall(): + yield('%s;' % sql) + + yield('COMMIT;') Index: trunk/Lib/test/test_sqlite.py =================================================================== --- trunk/Lib/test/test_sqlite.py (revision 61630) +++ trunk/Lib/test/test_sqlite.py (working copy) @@ -5,12 +5,13 @@ except ImportError: raise TestSkipped('no sqlite available') from sqlite3.test import (dbapi, types, userfunctions, py25tests, - factory, transactions, hooks, regression) + factory, transactions, hooks, regression, + dump) def test_main(): run_unittest(dbapi.suite(), types.suite(), userfunctions.suite(), py25tests.suite(), factory.suite(), transactions.suite(), - hooks.suite(), regression.suite()) + hooks.suite(), regression.suite(), dump.suite()) if __name__ == "__main__": test_main() Index: trunk/Modules/_sqlite/connection.c =================================================================== --- trunk/Modules/_sqlite/connection.c (revision 61630) +++ trunk/Modules/_sqlite/connection.c (working copy) @@ -1199,7 +1199,53 @@ return retval; } +/* Function author: Paul Kippes + * Class method of Connection to call the Python function _iterdump + * of the sqlite3 module. + */ static PyObject * +pysqlite_connection_iterdump(pysqlite_Connection* self, PyObject* args) +{ + PyObject* retval = NULL; + PyObject* module = NULL; + PyObject* module_dict; + PyObject* pyfn_iterdump; + + if (!pysqlite_check_connection(self)) { + goto finally; + } + + module = PyImport_ImportModule(MODULE_NAME ".dump"); + if (!module) { + goto finally; + } + + module_dict = PyModule_GetDict(module); + if (!module_dict) { + goto finally; + } + + pyfn_iterdump = PyDict_GetItemString(module_dict, "_iterdump"); + if (!pyfn_iterdump) { + PyErr_SetString(pysqlite_OperationalError, "Failed to obtain _iterdump() reference"); + goto finally; + } + + args = PyTuple_New(1); + if (!args) { + goto finally; + } + Py_INCREF(self); + PyTuple_SetItem(args, 0, (PyObject*)self); + retval = PyObject_CallObject(pyfn_iterdump, args); + +finally: + Py_XDECREF(args); + Py_XDECREF(module); + return retval; +} + +static PyObject * pysqlite_connection_create_collation(pysqlite_Connection* self, PyObject* args) { PyObject* callable; @@ -1344,6 +1390,8 @@ PyDoc_STR("Creates a collation function. Non-standard.")}, {"interrupt", (PyCFunction)pysqlite_connection_interrupt, METH_NOARGS, PyDoc_STR("Abort any pending database operation. Non-standard.")}, + {"iterdump", (PyCFunction)pysqlite_connection_iterdump, METH_NOARGS, + PyDoc_STR("Returns iterator to the dump of the database in an SQL text format.")}, {"__enter__", (PyCFunction)pysqlite_connection_enter, METH_NOARGS, PyDoc_STR("For context manager. Non-standard.")}, {"__exit__", (PyCFunction)pysqlite_connection_exit, METH_VARARGS,