initial commit
authorKali Kaneko <kali@futeisha.org>
Mon, 4 Feb 2013 10:20:12 +0000 (19:20 +0900)
committerKali Kaneko <kali@futeisha.org>
Mon, 4 Feb 2013 10:20:12 +0000 (19:20 +0900)
83 files changed:
.gitignore [new file with mode: 0644]
LICENSE [new file with mode: 0644]
MANIFEST.in [new file with mode: 0644]
PKG-INFO [new file with mode: 0644]
README.rst [new file with mode: 0644]
cross_bdist_wininst.py [new file with mode: 0644]
cross_bdist_wininst.pyc [new file with mode: 0644]
doc/includes/sqlite3/adapter_datetime.py [new file with mode: 0644]
doc/includes/sqlite3/adapter_point_1.py [new file with mode: 0644]
doc/includes/sqlite3/adapter_point_2.py [new file with mode: 0644]
doc/includes/sqlite3/apsw_example.py [new file with mode: 0644]
doc/includes/sqlite3/authorizer.py [new file with mode: 0644]
doc/includes/sqlite3/collation_reverse.py [new file with mode: 0644]
doc/includes/sqlite3/complete_statement.py [new file with mode: 0644]
doc/includes/sqlite3/connect_db_1.py [new file with mode: 0644]
doc/includes/sqlite3/connect_db_2.py [new file with mode: 0644]
doc/includes/sqlite3/converter_point.py [new file with mode: 0644]
doc/includes/sqlite3/countcursors.py [new file with mode: 0644]
doc/includes/sqlite3/createdb.py [new file with mode: 0644]
doc/includes/sqlite3/ctx_manager.py [new file with mode: 0644]
doc/includes/sqlite3/execsql_fetchonerow.py [new file with mode: 0644]
doc/includes/sqlite3/execsql_printall_1.py [new file with mode: 0644]
doc/includes/sqlite3/execute_1.py [new file with mode: 0644]
doc/includes/sqlite3/execute_2.py [new file with mode: 0644]
doc/includes/sqlite3/execute_3.py [new file with mode: 0644]
doc/includes/sqlite3/executemany_1.py [new file with mode: 0644]
doc/includes/sqlite3/executemany_2.py [new file with mode: 0644]
doc/includes/sqlite3/executescript.py [new file with mode: 0644]
doc/includes/sqlite3/insert_more_people.py [new file with mode: 0644]
doc/includes/sqlite3/load_extension.py [new file with mode: 0644]
doc/includes/sqlite3/md5func.py [new file with mode: 0644]
doc/includes/sqlite3/mysumaggr.py [new file with mode: 0644]
doc/includes/sqlite3/parse_colnames.py [new file with mode: 0644]
doc/includes/sqlite3/progress.py [new file with mode: 0644]
doc/includes/sqlite3/pysqlite_datetime.py [new file with mode: 0644]
doc/includes/sqlite3/row_factory.py [new file with mode: 0644]
doc/includes/sqlite3/rowclass.py [new file with mode: 0644]
doc/includes/sqlite3/shared_cache.py [new file with mode: 0644]
doc/includes/sqlite3/shortcut_methods.py [new file with mode: 0644]
doc/includes/sqlite3/simple_tableprinter.py [new file with mode: 0644]
doc/includes/sqlite3/text_factory.py [new file with mode: 0644]
doc/install-source.txt [new file with mode: 0644]
doc/sphinx/.static/.keepme [new file with mode: 0644]
doc/sphinx/Makefile [new file with mode: 0644]
doc/sphinx/conf.py [new file with mode: 0644]
doc/sphinx/index.rst [new file with mode: 0644]
doc/sphinx/sqlite3.rst [new file with mode: 0644]
lib/__init__.py [new file with mode: 0644]
lib/dbapi2.py [new file with mode: 0644]
lib/dump.py [new file with mode: 0644]
lib/test/__init__.py [new file with mode: 0644]
lib/test/dbapi.py [new file with mode: 0644]
lib/test/dump.py [new file with mode: 0644]
lib/test/factory.py [new file with mode: 0644]
lib/test/hooks.py [new file with mode: 0644]
lib/test/py25/__init__.py [new file with mode: 0644]
lib/test/py25/py25tests.py [new file with mode: 0644]
lib/test/regression.py [new file with mode: 0644]
lib/test/transactions.py [new file with mode: 0644]
lib/test/types.py [new file with mode: 0644]
lib/test/userfunctions.py [new file with mode: 0644]
setup.cfg [new file with mode: 0644]
setup.py [new file with mode: 0644]
src/backup.h [new file with mode: 0644]
src/cache.c [new file with mode: 0644]
src/cache.h [new file with mode: 0644]
src/connection.c [new file with mode: 0644]
src/connection.h [new file with mode: 0644]
src/cursor.c [new file with mode: 0644]
src/cursor.h [new file with mode: 0644]
src/microprotocols.c [new file with mode: 0644]
src/microprotocols.h [new file with mode: 0644]
src/module.c [new file with mode: 0644]
src/module.h [new file with mode: 0644]
src/prepare_protocol.c [new file with mode: 0644]
src/prepare_protocol.h [new file with mode: 0644]
src/row.c [new file with mode: 0644]
src/row.h [new file with mode: 0644]
src/sqlitecompat.h [new file with mode: 0644]
src/statement.c [new file with mode: 0644]
src/statement.h [new file with mode: 0644]
src/util.c [new file with mode: 0644]
src/util.h [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..ba66091
--- /dev/null
@@ -0,0 +1,2 @@
+amalgamation
+build
diff --git a/LICENSE b/LICENSE
new file mode 100644 (file)
index 0000000..268c33b
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2004-2007 Gerhard Häring
+
+This software is provided 'as-is', without any express or implied warranty. In
+no event will the authors be held liable for any damages arising from the use
+of this software.
+
+Permission is granted to anyone to use this software for any purpose,
+including commercial applications, and to alter it and redistribute it freely,
+subject to the following restrictions:
+
+    1. The origin of this software must not be misrepresented; you must not
+       claim that you wrote the original software. If you use this software in
+       a product, an acknowledgment in the product documentation would be
+       appreciated but is not required.
+
+    2. Altered source versions must be plainly marked as such, and must not be
+       misrepresented as being the original software.
+
+    3. This notice may not be removed or altered from any source distribution.
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644 (file)
index 0000000..d909e8e
--- /dev/null
@@ -0,0 +1,8 @@
+include MANIFEST.in
+include LICENSE
+include cross_bdist_wininst.py
+include doc/*.txt
+include doc/includes/sqlite3/*.py
+include doc/sphinx/*
+include doc/sphinx/.static/.keepme
+include src/*.h
diff --git a/PKG-INFO b/PKG-INFO
new file mode 100644 (file)
index 0000000..e0fdf08
--- /dev/null
+++ b/PKG-INFO
@@ -0,0 +1,25 @@
+Metadata-Version: 1.0
+Name: pysqlite
+Version: 2.6.3
+Summary: DB-API 2.0 interface for SQLite 3.x
+Home-page: http://pysqlite.googlecode.com/
+Author: Gerhard Haering
+Author-email: gh@ghaering.de
+License: zlib/libpng license
+Download-URL: http://code.google.com/p/pysqlite/downloads/list
+Description: Python interface to SQLite 3
+        
+        pysqlite is an interface to the SQLite 3.x embedded relational database engine.
+        It is almost fully compliant with the Python database API version 2.0 also
+        exposes the unique features of SQLite.
+Platform: ALL
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: zlib/libpng License
+Classifier: Operating System :: MacOS :: MacOS X
+Classifier: Operating System :: Microsoft :: Windows
+Classifier: Operating System :: POSIX
+Classifier: Programming Language :: C
+Classifier: Programming Language :: Python
+Classifier: Topic :: Database :: Database Engines/Servers
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
diff --git a/README.rst b/README.rst
new file mode 100644 (file)
index 0000000..2b9590d
--- /dev/null
@@ -0,0 +1,11 @@
+pysqlcipher
+===========
+
+this library is an experimental fork of pysqlite,
+and is statically linked against sqlcipher.
+
+Original code (c) 2004-2007 Gerhard Häring
+SQLCipher (c) 2013 Kali Kaneko
+
+It uses a sqlcipher amalgamation, see https://www.sqlite.org/amalgamation.html
+
diff --git a/cross_bdist_wininst.py b/cross_bdist_wininst.py
new file mode 100644 (file)
index 0000000..0b40921
--- /dev/null
@@ -0,0 +1,340 @@
+# Gerhard Haering <gh@gharing.d> is responsible for the hacked version of this
+# module.
+#
+# This is a modified version of the bdist_wininst distutils command to make it
+# possible to build installers *with extension modules* on Unix.
+
+"""distutils.command.bdist_wininst
+
+Implements the Distutils 'bdist_wininst' command: create a windows installer
+exe-program."""
+
+# This module should be kept compatible with Python 2.1.
+
+__revision__ = "$Id: bdist_wininst.py 59620 2007-12-31 14:47:07Z christian.heimes $"
+
+import sys, os, string
+from distutils.core import Command
+from distutils.util import get_platform
+from distutils.dir_util import create_tree, remove_tree
+from distutils.errors import *
+from distutils.sysconfig import get_python_version
+from distutils import log
+
+class bdist_wininst (Command):
+
+    description = "create an executable installer for MS Windows"
+
+    user_options = [('bdist-dir=', None,
+                     "temporary directory for creating the distribution"),
+                    ('keep-temp', 'k',
+                     "keep the pseudo-installation tree around after " +
+                     "creating the distribution archive"),
+                    ('target-version=', None,
+                     "require a specific python version" +
+                     " on the target system"),
+                    ('no-target-compile', 'c',
+                     "do not compile .py to .pyc on the target system"),
+                    ('no-target-optimize', 'o',
+                     "do not compile .py to .pyo (optimized)"
+                     "on the target system"),
+                    ('dist-dir=', 'd',
+                     "directory to put final built distributions in"),
+                    ('bitmap=', 'b',
+                     "bitmap to use for the installer instead of python-powered logo"),
+                    ('title=', 't',
+                     "title to display on the installer background instead of default"),
+                    ('skip-build', None,
+                     "skip rebuilding everything (for testing/debugging)"),
+                    ('install-script=', None,
+                     "basename of installation script to be run after"
+                     "installation or before deinstallation"),
+                    ('pre-install-script=', None,
+                     "Fully qualified filename of a script to be run before "
+                     "any files are installed.  This script need not be in the "
+                     "distribution"),
+                   ]
+
+    boolean_options = ['keep-temp', 'no-target-compile', 'no-target-optimize',
+                       'skip-build']
+
+    def initialize_options (self):
+        self.bdist_dir = None
+        self.keep_temp = 0
+        self.no_target_compile = 0
+        self.no_target_optimize = 0
+        self.target_version = None
+        self.dist_dir = None
+        self.bitmap = None
+        self.title = None
+        self.skip_build = 0
+        self.install_script = None
+        self.pre_install_script = None
+
+    # initialize_options()
+
+
+    def finalize_options (self):
+        if self.bdist_dir is None:
+            bdist_base = self.get_finalized_command('bdist').bdist_base
+            self.bdist_dir = os.path.join(bdist_base, 'wininst')
+        if not self.target_version:
+            self.target_version = ""
+        if not self.skip_build and self.distribution.has_ext_modules():
+            short_version = get_python_version()
+            if self.target_version and self.target_version != short_version:
+                raise DistutilsOptionError, \
+                      "target version can only be %s, or the '--skip_build'" \
+                      " option must be specified" % (short_version,)
+            self.target_version = short_version
+
+        self.set_undefined_options('bdist', ('dist_dir', 'dist_dir'))
+
+        if self.install_script:
+            for script in self.distribution.scripts:
+                if self.install_script == os.path.basename(script):
+                    break
+            else:
+                raise DistutilsOptionError, \
+                      "install_script '%s' not found in scripts" % \
+                      self.install_script
+    # finalize_options()
+
+
+    def run (self):
+        # HACK I disabled this check.
+        if 0 and (sys.platform != "win32" and
+            (self.distribution.has_ext_modules() or
+             self.distribution.has_c_libraries())):
+            raise DistutilsPlatformError \
+                  ("distribution contains extensions and/or C libraries; "
+                   "must be compiled on a Windows 32 platform")
+
+        if not self.skip_build:
+            self.run_command('build')
+
+        install = self.reinitialize_command('install', reinit_subcommands=1)
+        install.root = self.bdist_dir
+        install.skip_build = self.skip_build
+        install.warn_dir = 0
+
+        install_lib = self.reinitialize_command('install_lib')
+        # we do not want to include pyc or pyo files
+        install_lib.compile = 0
+        install_lib.optimize = 0
+
+        if self.distribution.has_ext_modules():
+            # If we are building an installer for a Python version other
+            # than the one we are currently running, then we need to ensure
+            # our build_lib reflects the other Python version rather than ours.
+            # Note that for target_version!=sys.version, we must have skipped the
+            # build step, so there is no issue with enforcing the build of this
+            # version.
+            target_version = self.target_version
+            if not target_version:
+                assert self.skip_build, "Should have already checked this"
+                target_version = sys.version[0:3]
+            plat_specifier = ".%s-%s" % (get_platform(), target_version)
+            build = self.get_finalized_command('build')
+            build.build_lib = os.path.join(build.build_base,
+                                           'lib' + plat_specifier)
+
+        # Use a custom scheme for the zip-file, because we have to decide
+        # at installation time which scheme to use.
+        for key in ('purelib', 'platlib', 'headers', 'scripts', 'data'):
+            value = string.upper(key)
+            if key == 'headers':
+                value = value + '/Include/$dist_name'
+            setattr(install,
+                    'install_' + key,
+                    value)
+
+        log.info("installing to %s", self.bdist_dir)
+        install.ensure_finalized()
+
+        # avoid warning of 'install_lib' about installing
+        # into a directory not in sys.path
+        sys.path.insert(0, os.path.join(self.bdist_dir, 'PURELIB'))
+
+        install.run()
+
+        del sys.path[0]
+
+        # And make an archive relative to the root of the
+        # pseudo-installation tree.
+        from tempfile import mktemp
+        archive_basename = mktemp()
+        fullname = self.distribution.get_fullname()
+        arcname = self.make_archive(archive_basename, "zip",
+                                    root_dir=self.bdist_dir)
+        # create an exe containing the zip-file
+        self.create_exe(arcname, fullname, self.bitmap)
+        if self.distribution.has_ext_modules():
+            pyversion = get_python_version()
+        else:
+            pyversion = 'any'
+        self.distribution.dist_files.append(('bdist_wininst', pyversion,
+                                             self.get_installer_filename(fullname)))
+        # remove the zip-file again
+        log.debug("removing temporary file '%s'", arcname)
+        os.remove(arcname)
+
+        if not self.keep_temp:
+            remove_tree(self.bdist_dir, dry_run=self.dry_run)
+
+    # run()
+
+    def get_inidata (self):
+        # Return data describing the installation.
+
+        lines = []
+        metadata = self.distribution.metadata
+
+        # Write the [metadata] section.
+        lines.append("[metadata]")
+
+        # 'info' will be displayed in the installer's dialog box,
+        # describing the items to be installed.
+        info = (metadata.long_description or '') + '\n'
+
+        # Escape newline characters
+        def escape(s):
+            return string.replace(s, "\n", "\\n")
+
+        for name in ["author", "author_email", "description", "maintainer",
+                     "maintainer_email", "name", "url", "version"]:
+            data = getattr(metadata, name, "")
+            if data:
+                info = info + ("\n    %s: %s" % \
+                               (string.capitalize(name), escape(data)))
+                lines.append("%s=%s" % (name, escape(data)))
+
+        # The [setup] section contains entries controlling
+        # the installer runtime.
+        lines.append("\n[Setup]")
+        if self.install_script:
+            lines.append("install_script=%s" % self.install_script)
+        lines.append("info=%s" % escape(info))
+        lines.append("target_compile=%d" % (not self.no_target_compile))
+        lines.append("target_optimize=%d" % (not self.no_target_optimize))
+        if self.target_version:
+            lines.append("target_version=%s" % self.target_version)
+
+        title = self.title or self.distribution.get_fullname()
+        lines.append("title=%s" % escape(title))
+        import time
+        import distutils
+        build_info = "Built %s with distutils-%s" % \
+                     (time.ctime(time.time()), distutils.__version__)
+        lines.append("build_info=%s" % build_info)
+        return string.join(lines, "\n")
+
+    # get_inidata()
+
+    def create_exe (self, arcname, fullname, bitmap=None):
+        import struct
+
+        self.mkpath(self.dist_dir)
+
+        cfgdata = self.get_inidata()
+
+        installer_name = self.get_installer_filename(fullname)
+        self.announce("creating %s" % installer_name)
+
+        if bitmap:
+            bitmapdata = open(bitmap, "rb").read()
+            bitmaplen = len(bitmapdata)
+        else:
+            bitmaplen = 0
+
+        file = open(installer_name, "wb")
+        file.write(self.get_exe_bytes())
+        if bitmap:
+            file.write(bitmapdata)
+
+        # Convert cfgdata from unicode to ascii, mbcs encoded
+        try:
+            unicode
+        except NameError:
+            pass
+        else:
+            if isinstance(cfgdata, unicode):
+                cfgdata = cfgdata.encode("mbcs")
+
+        # Append the pre-install script
+        cfgdata = cfgdata + "\0"
+        if self.pre_install_script:
+            script_data = open(self.pre_install_script, "r").read()
+            cfgdata = cfgdata + script_data + "\n\0"
+        else:
+            # empty pre-install script
+            cfgdata = cfgdata + "\0"
+        file.write(cfgdata)
+
+        # The 'magic number' 0x1234567B is used to make sure that the
+        # binary layout of 'cfgdata' is what the wininst.exe binary
+        # expects.  If the layout changes, increment that number, make
+        # the corresponding changes to the wininst.exe sources, and
+        # recompile them.
+        header = struct.pack("<iii",
+                             0x1234567B,       # tag
+                             len(cfgdata),     # length
+                             bitmaplen,        # number of bytes in bitmap
+                             )
+        file.write(header)
+        file.write(open(arcname, "rb").read())
+
+    # create_exe()
+
+    def get_installer_filename(self, fullname):
+        # Factored out to allow overriding in subclasses
+        if self.target_version:
+            # if we create an installer for a specific python version,
+            # it's better to include this in the name
+            installer_name = os.path.join(self.dist_dir,
+                                          "%s.win32-py%s.exe" %
+                                           (fullname, self.target_version))
+        else:
+            installer_name = os.path.join(self.dist_dir,
+                                          "%s.win32.exe" % fullname)
+        return installer_name
+    # get_installer_filename()
+
+    def get_exe_bytes (self):
+        from distutils.msvccompiler import get_build_version
+        # If a target-version other than the current version has been
+        # specified, then using the MSVC version from *this* build is no good.
+        # Without actually finding and executing the target version and parsing
+        # its sys.version, we just hard-code our knowledge of old versions.
+        # NOTE: Possible alternative is to allow "--target-version" to
+        # specify a Python executable rather than a simple version string.
+        # We can then execute this program to obtain any info we need, such
+        # as the real sys.version string for the build.
+        cur_version = get_python_version()
+        if self.target_version and self.target_version != cur_version:
+            if self.target_version < "2.3":
+                raise NotImplementedError
+            elif self.target_version == "2.3":
+               bv = "6"
+            elif self.target_version in ("2.4", "2.5"):
+               bv = "7.1"
+            elif self.target_version in ("2.6", "2.7"):
+                bv = "9.0"
+            else:
+                raise NotImplementedError
+        else:
+            # for current version - use authoritative check.
+            bv = get_build_version()
+
+        # wininst-x.y.exe is in the same directory as this file
+        directory = os.path.dirname(__file__)
+        # we must use a wininst-x.y.exe built with the same C compiler
+        # used for python.  XXX What about mingw, borland, and so on?
+
+        # The uninstallers need to be available in $PYEXT_CROSS/uninst/*.exe
+        # Use http://oss.itsystementwicklung.de/hg/pyext_cross_linux_to_win32/
+        # and copy it alongside your pysqlite checkout.
+
+        filename = os.path.join(directory, os.path.join(os.environ["PYEXT_CROSS"], "uninst", "wininst-%s.exe" % bv))
+        return open(filename, "rb").read()
+# class bdist_wininst
diff --git a/cross_bdist_wininst.pyc b/cross_bdist_wininst.pyc
new file mode 100644 (file)
index 0000000..f9fb0af
Binary files /dev/null and b/cross_bdist_wininst.pyc differ
diff --git a/doc/includes/sqlite3/adapter_datetime.py b/doc/includes/sqlite3/adapter_datetime.py
new file mode 100644 (file)
index 0000000..5a43b02
--- /dev/null
@@ -0,0 +1,14 @@
+from pysqlite2 import dbapi2 as sqlite3
+import datetime, time
+
+def adapt_datetime(ts):
+    return time.mktime(ts.timetuple())
+
+sqlite3.register_adapter(datetime.datetime, adapt_datetime)
+
+con = sqlite3.connect(":memory:")
+cur = con.cursor()
+
+now = datetime.datetime.now()
+cur.execute("select ?", (now,))
+print cur.fetchone()[0]
diff --git a/doc/includes/sqlite3/adapter_point_1.py b/doc/includes/sqlite3/adapter_point_1.py
new file mode 100644 (file)
index 0000000..d9acb8d
--- /dev/null
@@ -0,0 +1,16 @@
+from pysqlite2 import dbapi2 as sqlite3
+
+class Point(object):
+    def __init__(self, x, y):
+        self.x, self.y = x, y
+
+    def __conform__(self, protocol):
+        if protocol is sqlite3.PrepareProtocol:
+            return "%f;%f" % (self.x, self.y)
+
+con = sqlite3.connect(":memory:")
+cur = con.cursor()
+
+p = Point(4.0, -3.2)
+cur.execute("select ?", (p,))
+print cur.fetchone()[0]
diff --git a/doc/includes/sqlite3/adapter_point_2.py b/doc/includes/sqlite3/adapter_point_2.py
new file mode 100644 (file)
index 0000000..6ec58a8
--- /dev/null
@@ -0,0 +1,17 @@
+from pysqlite2 import dbapi2 as sqlite3
+
+class Point(object):
+    def __init__(self, x, y):
+        self.x, self.y = x, y
+
+def adapt_point(point):
+    return "%f;%f" % (point.x, point.y)
+
+sqlite3.register_adapter(Point, adapt_point)
+
+con = sqlite3.connect(":memory:")
+cur = con.cursor()
+
+p = Point(4.0, -3.2)
+cur.execute("select ?", (p,))
+print cur.fetchone()[0]
diff --git a/doc/includes/sqlite3/apsw_example.py b/doc/includes/sqlite3/apsw_example.py
new file mode 100644 (file)
index 0000000..bdca0c9
--- /dev/null
@@ -0,0 +1,12 @@
+from pysqlite2 import dbapi2 as sqlite3
+import apsw
+
+apsw_con = apsw.Connection(":memory:")
+apsw_con.createscalarfunction("times_two", lambda x: 2*x, 1)
+
+# Create pysqlite connection from APSW connection
+con = sqlite3.connect(apsw_con)
+result = con.execute("select times_two(15)").fetchone()[0]
+assert result == 30
+con.close()
+
diff --git a/doc/includes/sqlite3/authorizer.py b/doc/includes/sqlite3/authorizer.py
new file mode 100644 (file)
index 0000000..0176c6c
--- /dev/null
@@ -0,0 +1,26 @@
+from pysqlite2 import dbapi2 as sqlite3
+
+def authorizer_callback(action, arg1, arg2, dbname, source):
+    if action != sqlite3.SQLITE_SELECT:
+        return sqlite3.SQLITE_DENY
+    if arg1 == "private_table":
+        return sqlite3.SQLITE_DENY
+    return sqlite3.SQLITE_OK
+
+con = sqlite3.connect(":memory:")
+con.executescript("""
+    create table public_table(c1, c2);
+    create table private_table(c1, c2);
+    """)
+con.set_authorizer(authorizer_callback)
+
+try:
+    con.execute("select * from private_table")
+except sqlite3.DatabaseError, e:
+    print "SELECT FROM private_table =>", e.args[0]     # access ... prohibited
+
+try:
+    con.execute("insert into public_table(c1, c2) values (1, 2)")
+except sqlite3.DatabaseError, e:
+    print "DML command =>", e.args[0]     # access ... prohibited
+
diff --git a/doc/includes/sqlite3/collation_reverse.py b/doc/includes/sqlite3/collation_reverse.py
new file mode 100644 (file)
index 0000000..100fac9
--- /dev/null
@@ -0,0 +1,15 @@
+from pysqlite2 import dbapi2 as sqlite3
+
+def collate_reverse(string1, string2):
+    return -cmp(string1, string2)
+
+con = sqlite3.connect(":memory:")
+con.create_collation("reverse", collate_reverse)
+
+cur = con.cursor()
+cur.execute("create table test(x)")
+cur.executemany("insert into test(x) values (?)", [("a",), ("b",)])
+cur.execute("select x from test order by x collate reverse")
+for row in cur:
+    print row
+con.close()
diff --git a/doc/includes/sqlite3/complete_statement.py b/doc/includes/sqlite3/complete_statement.py
new file mode 100644 (file)
index 0000000..2bb49d4
--- /dev/null
@@ -0,0 +1,30 @@
+# A minimal SQLite shell for experiments
+
+from pysqlite2 import dbapi2 as sqlite3
+
+con = sqlite3.connect(":memory:")
+con.isolation_level = None
+cur = con.cursor()
+
+buffer = ""
+
+print "Enter your SQL commands to execute in SQLite."
+print "Enter a blank line to exit."
+
+while True:
+    line = raw_input()
+    if line == "":
+        break
+    buffer += line
+    if sqlite3.complete_statement(buffer):
+        try:
+            buffer = buffer.strip()
+            cur.execute(buffer)
+
+            if buffer.lstrip().upper().startswith("SELECT"):
+                print cur.fetchall()
+        except sqlite3.Error, e:
+            print "An error occurred:", e.args[0]
+        buffer = ""
+
+con.close()
diff --git a/doc/includes/sqlite3/connect_db_1.py b/doc/includes/sqlite3/connect_db_1.py
new file mode 100644 (file)
index 0000000..360bf21
--- /dev/null
@@ -0,0 +1,3 @@
+from pysqlite2 import dbapi2 as sqlite3
+
+con = sqlite3.connect("mydb")
diff --git a/doc/includes/sqlite3/connect_db_2.py b/doc/includes/sqlite3/connect_db_2.py
new file mode 100644 (file)
index 0000000..6899843
--- /dev/null
@@ -0,0 +1,3 @@
+from pysqlite2 import dbapi2 as sqlite3
+
+con = sqlite3.connect(":memory:")
diff --git a/doc/includes/sqlite3/converter_point.py b/doc/includes/sqlite3/converter_point.py
new file mode 100644 (file)
index 0000000..4ba0df5
--- /dev/null
@@ -0,0 +1,47 @@
+from pysqlite2 import dbapi2 as sqlite3
+
+class Point(object):
+    def __init__(self, x, y):
+        self.x, self.y = x, y
+
+    def __repr__(self):
+        return "(%f;%f)" % (self.x, self.y)
+
+def adapt_point(point):
+    return "%f;%f" % (point.x, point.y)
+
+def convert_point(s):
+    x, y = map(float, s.split(";"))
+    return Point(x, y)
+
+# Register the adapter
+sqlite3.register_adapter(Point, adapt_point)
+
+# Register the converter
+sqlite3.register_converter("point", convert_point)
+
+p = Point(4.0, -3.2)
+
+#########################
+# 1) Using declared types
+con = sqlite3.connect(":memory:", detect_types=sqlite3.PARSE_DECLTYPES)
+cur = con.cursor()
+cur.execute("create table test(p point)")
+
+cur.execute("insert into test(p) values (?)", (p,))
+cur.execute("select p from test")
+print "with declared types:", cur.fetchone()[0]
+cur.close()
+con.close()
+
+#######################
+# 1) Using column names
+con = sqlite3.connect(":memory:", detect_types=sqlite3.PARSE_COLNAMES)
+cur = con.cursor()
+cur.execute("create table test(p)")
+
+cur.execute("insert into test(p) values (?)", (p,))
+cur.execute('select p as "p [point]" from test')
+print "with column names:", cur.fetchone()[0]
+cur.close()
+con.close()
diff --git a/doc/includes/sqlite3/countcursors.py b/doc/includes/sqlite3/countcursors.py
new file mode 100644 (file)
index 0000000..9ba7614
--- /dev/null
@@ -0,0 +1,15 @@
+from pysqlite2 import dbapi2 as sqlite3
+
+class CountCursorsConnection(sqlite3.Connection):
+    def __init__(self, *args, **kwargs):
+        sqlite3.Connection.__init__(self, *args, **kwargs)
+        self.numcursors = 0
+
+    def cursor(self, *args, **kwargs):
+        self.numcursors += 1
+        return sqlite3.Connection.cursor(self, *args, **kwargs)
+
+con = sqlite3.connect(":memory:", factory=CountCursorsConnection)
+cur1 = con.cursor()
+cur2 = con.cursor()
+print con.numcursors
diff --git a/doc/includes/sqlite3/createdb.py b/doc/includes/sqlite3/createdb.py
new file mode 100644 (file)
index 0000000..28e9514
--- /dev/null
@@ -0,0 +1,28 @@
+# Not referenced from the documentation, but builds the database file the other
+# code snippets expect.
+
+from pysqlite2 import dbapi2 as sqlite3
+import os
+
+DB_FILE = "mydb"
+
+if os.path.exists(DB_FILE):
+    os.remove(DB_FILE)
+
+con = sqlite3.connect(DB_FILE)
+cur = con.cursor()
+cur.execute("""
+        create table people
+        (
+          name_last      varchar(20),
+          age            integer
+        )
+        """)
+
+cur.execute("insert into people (name_last, age) values ('Yeltsin',   72)")
+cur.execute("insert into people (name_last, age) values ('Putin',     51)")
+
+con.commit()
+
+cur.close()
+con.close()
diff --git a/doc/includes/sqlite3/ctx_manager.py b/doc/includes/sqlite3/ctx_manager.py
new file mode 100644 (file)
index 0000000..2821e8f
--- /dev/null
@@ -0,0 +1,19 @@
+from __future__ import with_statement
+from pysqlite2 import dbapi2 as sqlite3
+
+con = sqlite3.connect(":memory:")
+con.execute("create table person (id integer primary key, firstname varchar unique)")
+
+# Successful, con.commit() is called automatically afterwards
+with con:
+    con.execute("insert into person(firstname) values (?)", ("Joe",))
+
+# con.rollback() is called after the with block finishes with an exception, the
+# exception is still raised and must be catched
+try:
+    with con:
+        con.execute("insert into person(firstname) values (?)", ("Joe",))
+except sqlite3.IntegrityError:
+    print "couldn't add Joe twice"
+
+
diff --git a/doc/includes/sqlite3/execsql_fetchonerow.py b/doc/includes/sqlite3/execsql_fetchonerow.py
new file mode 100644 (file)
index 0000000..e3aa578
--- /dev/null
@@ -0,0 +1,17 @@
+from pysqlite2 import dbapi2 as sqlite3
+
+con = sqlite3.connect("mydb")
+
+cur = con.cursor()
+SELECT = "select name_last, age from people order by age, name_last"
+
+# 1. Iterate over the rows available from the cursor, unpacking the
+# resulting sequences to yield their elements (name_last, age):
+cur.execute(SELECT)
+for (name_last, age) in cur:
+    print '%s is %d years old.' % (name_last, age)
+
+# 2. Equivalently:
+cur.execute(SELECT)
+for row in cur:
+    print '%s is %d years old.' % (row[0], row[1])
diff --git a/doc/includes/sqlite3/execsql_printall_1.py b/doc/includes/sqlite3/execsql_printall_1.py
new file mode 100644 (file)
index 0000000..62e48bd
--- /dev/null
@@ -0,0 +1,13 @@
+from pysqlite2 import dbapi2 as sqlite3
+
+# Create a connection to the database file "mydb":
+con = sqlite3.connect("mydb")
+
+# Get a Cursor object that operates in the context of Connection con:
+cur = con.cursor()
+
+# Execute the SELECT statement:
+cur.execute("select * from people order by age")
+
+# Retrieve all rows as a sequence and print that sequence:
+print cur.fetchall()
diff --git a/doc/includes/sqlite3/execute_1.py b/doc/includes/sqlite3/execute_1.py
new file mode 100644 (file)
index 0000000..70967ea
--- /dev/null
@@ -0,0 +1,11 @@
+from pysqlite2 import dbapi2 as sqlite3
+
+con = sqlite3.connect("mydb")
+
+cur = con.cursor()
+
+who = "Yeltsin"
+age = 72
+
+cur.execute("select name_last, age from people where name_last=? and age=?", (who, age))
+print cur.fetchone()
diff --git a/doc/includes/sqlite3/execute_2.py b/doc/includes/sqlite3/execute_2.py
new file mode 100644 (file)
index 0000000..416b116
--- /dev/null
@@ -0,0 +1,12 @@
+from pysqlite2 import dbapi2 as sqlite3
+
+con = sqlite3.connect("mydb")
+
+cur = con.cursor()
+
+who = "Yeltsin"
+age = 72
+
+cur.execute("select name_last, age from people where name_last=:who and age=:age",
+    {"who": who, "age": age})
+print cur.fetchone()
diff --git a/doc/includes/sqlite3/execute_3.py b/doc/includes/sqlite3/execute_3.py
new file mode 100644 (file)
index 0000000..868be99
--- /dev/null
@@ -0,0 +1,12 @@
+from pysqlite2 import dbapi2 as sqlite3
+
+con = sqlite3.connect("mydb")
+
+cur = con.cursor()
+
+who = "Yeltsin"
+age = 72
+
+cur.execute("select name_last, age from people where name_last=:who and age=:age",
+    locals())
+print cur.fetchone()
diff --git a/doc/includes/sqlite3/executemany_1.py b/doc/includes/sqlite3/executemany_1.py
new file mode 100644 (file)
index 0000000..b076389
--- /dev/null
@@ -0,0 +1,24 @@
+from pysqlite2 import dbapi2 as sqlite3
+
+class IterChars:
+    def __init__(self):
+        self.count = ord('a')
+
+    def __iter__(self):
+        return self
+
+    def next(self):
+        if self.count > ord('z'):
+            raise StopIteration
+        self.count += 1
+        return (chr(self.count - 1),) # this is a 1-tuple
+
+con = sqlite3.connect(":memory:")
+cur = con.cursor()
+cur.execute("create table characters(c)")
+
+theIter = IterChars()
+cur.executemany("insert into characters(c) values (?)", theIter)
+
+cur.execute("select c from characters")
+print cur.fetchall()
diff --git a/doc/includes/sqlite3/executemany_2.py b/doc/includes/sqlite3/executemany_2.py
new file mode 100644 (file)
index 0000000..9913909
--- /dev/null
@@ -0,0 +1,15 @@
+from pysqlite2 import dbapi2 as sqlite3
+
+def char_generator():
+    import string
+    for c in string.letters[:26]:
+        yield (c,)
+
+con = sqlite3.connect(":memory:")
+cur = con.cursor()
+cur.execute("create table characters(c)")
+
+cur.executemany("insert into characters(c) values (?)", char_generator())
+
+cur.execute("select c from characters")
+print cur.fetchall()
diff --git a/doc/includes/sqlite3/executescript.py b/doc/includes/sqlite3/executescript.py
new file mode 100644 (file)
index 0000000..57c2613
--- /dev/null
@@ -0,0 +1,24 @@
+from pysqlite2 import dbapi2 as sqlite3
+
+con = sqlite3.connect(":memory:")
+cur = con.cursor()
+cur.executescript("""
+    create table person(
+        firstname,
+        lastname,
+        age
+    );
+
+    create table book(
+        title,
+        author,
+        published
+    );
+
+    insert into book(title, author, published)
+    values (
+        'Dirk Gently''s Holistic Detective Agency',
+        'Douglas Adams',
+        1987
+    );
+    """)
diff --git a/doc/includes/sqlite3/insert_more_people.py b/doc/includes/sqlite3/insert_more_people.py
new file mode 100644 (file)
index 0000000..40600dc
--- /dev/null
@@ -0,0 +1,16 @@
+from pysqlite2 import dbapi2 as sqlite3
+
+con = sqlite3.connect("mydb")
+
+cur = con.cursor()
+
+newPeople = (
+    ('Lebed'       , 53),
+    ('Zhirinovsky' , 57),
+  )
+
+for person in newPeople:
+    cur.execute("insert into people (name_last, age) values (?, ?)", person)
+
+# The changes will not be saved unless the transaction is committed explicitly:
+con.commit()
diff --git a/doc/includes/sqlite3/load_extension.py b/doc/includes/sqlite3/load_extension.py
new file mode 100644 (file)
index 0000000..d8df90f
--- /dev/null
@@ -0,0 +1,28 @@
+from pysqlite2 import dbapi2 as sqlite3
+
+con = sqlite3.connect(":memory:")
+
+# enable extension loading
+con.enable_load_extension(True)
+
+# Load the fulltext search extension
+con.execute("select load_extension('./fts3.so')")
+
+# alternatively you can load the extension using an API call:
+# con.load_extension("./fts3.so")
+
+# disable extension laoding again
+con.enable_load_extension(False)
+
+# example from SQLite wiki
+con.execute("create virtual table recipe using fts3(name, ingredients)")
+con.executescript("""
+    insert into recipe (name, ingredients) values ('broccoli stew', 'broccoli peppers cheese tomatoes');
+    insert into recipe (name, ingredients) values ('pumpkin stew', 'pumpkin onions garlic celery');
+    insert into recipe (name, ingredients) values ('broccoli pie', 'broccoli cheese onions flour');
+    insert into recipe (name, ingredients) values ('pumpkin pie', 'pumpkin sugar flour butter');
+    """)
+for row in con.execute("select rowid, name, ingredients from recipe where name match 'pie'"):
+    print row
+
+
diff --git a/doc/includes/sqlite3/md5func.py b/doc/includes/sqlite3/md5func.py
new file mode 100644 (file)
index 0000000..5b8b983
--- /dev/null
@@ -0,0 +1,11 @@
+from pysqlite2 import dbapi2 as sqlite3
+import md5
+
+def md5sum(t):
+    return md5.md5(t).hexdigest()
+
+con = sqlite3.connect(":memory:")
+con.create_function("md5", 1, md5sum)
+cur = con.cursor()
+cur.execute("select md5(?)", ("foo",))
+print cur.fetchone()[0]
diff --git a/doc/includes/sqlite3/mysumaggr.py b/doc/includes/sqlite3/mysumaggr.py
new file mode 100644 (file)
index 0000000..4fbcad5
--- /dev/null
@@ -0,0 +1,20 @@
+from pysqlite2 import dbapi2 as sqlite3
+
+class MySum:
+    def __init__(self):
+        self.count = 0
+
+    def step(self, value):
+        self.count += value
+
+    def finalize(self):
+        return self.count
+
+con = sqlite3.connect(":memory:")
+con.create_aggregate("mysum", 1, MySum)
+cur = con.cursor()
+cur.execute("create table test(i)")
+cur.execute("insert into test(i) values (1)")
+cur.execute("insert into test(i) values (2)")
+cur.execute("select mysum(i) from test")
+print cur.fetchone()[0]
diff --git a/doc/includes/sqlite3/parse_colnames.py b/doc/includes/sqlite3/parse_colnames.py
new file mode 100644 (file)
index 0000000..702fa8d
--- /dev/null
@@ -0,0 +1,8 @@
+from pysqlite2 import dbapi2 as sqlite3
+import datetime
+
+con = sqlite3.connect(":memory:", detect_types=sqlite3.PARSE_COLNAMES)
+cur = con.cursor()
+cur.execute('select ? as "x [timestamp]"', (datetime.datetime.now(),))
+dt = cur.fetchone()[0]
+print dt, type(dt)
diff --git a/doc/includes/sqlite3/progress.py b/doc/includes/sqlite3/progress.py
new file mode 100644 (file)
index 0000000..b30941d
--- /dev/null
@@ -0,0 +1,29 @@
+from pysqlite2 import dbapi2 as sqlite3
+
+def progress():
+    print "Query still executing. Please wait ..."
+
+con = sqlite3.connect(":memory:")
+con.execute("create table test(x)")
+
+# Let's create some data
+con.executemany("insert into test(x) values (?)", [(x,) for x in xrange(300)])
+
+# A progress handler, executed every 10 million opcodes
+con.set_progress_handler(progress, 10000000)
+
+# A particularly long-running query
+killer_stament = """
+    select count(*) from (
+        select t1.x from test t1, test t2, test t3
+    )
+    """
+
+con.execute(killer_stament)
+print "-" * 50
+
+# Clear the progress handler
+con.set_progress_handler(None, 0)
+
+con.execute(killer_stament)
+
diff --git a/doc/includes/sqlite3/pysqlite_datetime.py b/doc/includes/sqlite3/pysqlite_datetime.py
new file mode 100644 (file)
index 0000000..9075b46
--- /dev/null
@@ -0,0 +1,20 @@
+from pysqlite2 import dbapi2 as sqlite3
+import datetime
+
+con = sqlite3.connect(":memory:", detect_types=sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES)
+cur = con.cursor()
+cur.execute("create table test(d date, ts timestamp)")
+
+today = datetime.date.today()
+now = datetime.datetime.now()
+
+cur.execute("insert into test(d, ts) values (?, ?)", (today, now))
+cur.execute("select d, ts from test")
+row = cur.fetchone()
+print today, "=>", row[0], type(row[0])
+print now, "=>", row[1], type(row[1])
+
+cur.execute('select current_date as "d [date]", current_timestamp as "ts [timestamp]"')
+row = cur.fetchone()
+print "current_date", row[0], type(row[0])
+print "current_timestamp", row[1], type(row[1])
diff --git a/doc/includes/sqlite3/row_factory.py b/doc/includes/sqlite3/row_factory.py
new file mode 100644 (file)
index 0000000..bfbc64d
--- /dev/null
@@ -0,0 +1,13 @@
+from pysqlite2 import dbapi2 as sqlite3
+
+def dict_factory(cursor, row):
+    d = {}
+    for idx, col in enumerate(cursor.description):
+        d[col[0]] = row[idx]
+    return d
+
+con = sqlite3.connect(":memory:")
+con.row_factory = dict_factory
+cur = con.cursor()
+cur.execute("select 1 as a")
+print cur.fetchone()["a"]
diff --git a/doc/includes/sqlite3/rowclass.py b/doc/includes/sqlite3/rowclass.py
new file mode 100644 (file)
index 0000000..e210ef2
--- /dev/null
@@ -0,0 +1,12 @@
+from pysqlite2 import dbapi2 as sqlite3
+
+con = sqlite3.connect("mydb")
+con.row_factory = sqlite3.Row
+
+cur = con.cursor()
+cur.execute("select name_last, age from people")
+for row in cur:
+    assert row[0] == row["name_last"]
+    assert row["name_last"] == row["nAmE_lAsT"]
+    assert row[1] == row["age"]
+    assert row[1] == row["AgE"]
diff --git a/doc/includes/sqlite3/shared_cache.py b/doc/includes/sqlite3/shared_cache.py
new file mode 100644 (file)
index 0000000..98adf78
--- /dev/null
@@ -0,0 +1,6 @@
+from pysqlite2 import dbapi2 as sqlite3
+
+# The shared cache is only available in SQLite versions 3.3.3 or later
+# See the SQLite documentaton for details.
+
+sqlite3.enable_shared_cache(True)
diff --git a/doc/includes/sqlite3/shortcut_methods.py b/doc/includes/sqlite3/shortcut_methods.py
new file mode 100644 (file)
index 0000000..fcfc631
--- /dev/null
@@ -0,0 +1,21 @@
+from pysqlite2 import dbapi2 as sqlite3
+
+persons = [
+    ("Hugo", "Boss"),
+    ("Calvin", "Klein")
+    ]
+
+con = sqlite3.connect(":memory:")
+
+# Create the table
+con.execute("create table person(firstname, lastname)")
+
+# Fill the table
+con.executemany("insert into person(firstname, lastname) values (?, ?)", persons)
+
+# Print the table contents
+for row in con.execute("select firstname, lastname from person"):
+    print row
+
+# Using a dummy WHERE clause to not let SQLite take the shortcut table deletes.
+print "I just deleted", con.execute("delete from person where 1=1").rowcount, "rows"
diff --git a/doc/includes/sqlite3/simple_tableprinter.py b/doc/includes/sqlite3/simple_tableprinter.py
new file mode 100644 (file)
index 0000000..2237dc5
--- /dev/null
@@ -0,0 +1,26 @@
+from pysqlite2 import dbapi2 as sqlite3
+
+FIELD_MAX_WIDTH = 20
+TABLE_NAME = 'people'
+SELECT = 'select * from %s order by age, name_last' % TABLE_NAME
+
+con = sqlite3.connect("mydb")
+
+cur = con.cursor()
+cur.execute(SELECT)
+
+# Print a header.
+for fieldDesc in cur.description:
+    print fieldDesc[0].ljust(FIELD_MAX_WIDTH) ,
+print # Finish the header with a newline.
+print '-' * 78
+
+# For each row, print the value of each field left-justified within
+# the maximum possible width of that field.
+fieldIndices = range(len(cur.description))
+for row in cur:
+    for fieldIndex in fieldIndices:
+        fieldValue = str(row[fieldIndex])
+        print fieldValue.ljust(FIELD_MAX_WIDTH) ,
+
+    print # Finish the row with a newline.
diff --git a/doc/includes/sqlite3/text_factory.py b/doc/includes/sqlite3/text_factory.py
new file mode 100644 (file)
index 0000000..cb38d52
--- /dev/null
@@ -0,0 +1,42 @@
+from pysqlite2 import dbapi2 as sqlite3
+
+con = sqlite3.connect(":memory:")
+cur = con.cursor()
+
+# Create the table
+con.execute("create table person(lastname, firstname)")
+
+AUSTRIA = u"\xd6sterreich"
+
+# by default, rows are returned as Unicode
+cur.execute("select ?", (AUSTRIA,))
+row = cur.fetchone()
+assert row[0] == AUSTRIA
+
+# but we can make pysqlite always return bytestrings ...
+con.text_factory = str
+cur.execute("select ?", (AUSTRIA,))
+row = cur.fetchone()
+assert type(row[0]) == str
+# the bytestrings will be encoded in UTF-8, unless you stored garbage in the
+# database ...
+assert row[0] == AUSTRIA.encode("utf-8")
+
+# we can also implement a custom text_factory ...
+# here we implement one that will ignore Unicode characters that cannot be
+# decoded from UTF-8
+con.text_factory = lambda x: unicode(x, "utf-8", "ignore")
+cur.execute("select ?", ("this is latin1 and would normally create errors" + u"\xe4\xf6\xfc".encode("latin1"),))
+row = cur.fetchone()
+assert type(row[0]) == unicode
+
+# pysqlite offers a builtin optimized text_factory that will return bytestring
+# objects, if the data is in ASCII only, and otherwise return unicode objects
+con.text_factory = sqlite3.OptimizedUnicode
+cur.execute("select ?", (AUSTRIA,))
+row = cur.fetchone()
+assert type(row[0]) == unicode
+
+cur.execute("select ?", ("Germany",))
+row = cur.fetchone()
+assert type(row[0]) == str
diff --git a/doc/install-source.txt b/doc/install-source.txt
new file mode 100644 (file)
index 0000000..90a3ce7
--- /dev/null
@@ -0,0 +1,147 @@
+-------------------------------------------------
+pysqlite installation guide - source distribution
+-------------------------------------------------
+
+\(c\) 2005 Gerhard Häring
+
+Note: For Windows users, it is recommended that you use the win32 binary
+  distribution of pysqlite!
+
+Steps:
+
+  - `Step 1: Satisfy the dependencies`_
+  - `Step 2: Compile pysqlite`_
+  - `Step 3: Install pysqlite`_
+  - `Step 4: Test your pysqlite installation`_
+
+Step 1: Satisfy The Dependencies
+================================
+
+pysqlite requires a valid combination of the dependencies in the list below.
+
+Detailed instructions on how to install each dependency are beyond the scope of
+this document; consult the dependency distributor for installation
+instructions.
+
+Dependencies:
+
+ 1. Operating System and C Compiler - one of:
+
+    * Linux/FreeBSD/NetBSD/OpenBSD and GCC
+
+    * Other POSIX-compliant operating system and a C compiler
+
+ 2. SQLite:
+
+    * SQLite version 3.0.8 or later (as of pysqlite 2.2.0). This means we need
+      the SQLite library installed - either statically or dynamically linked -
+      and the SQLite header files. On Linux, the chances are very high that
+      your distribution offers packages for SQLite 3. Be sure to verify the
+      package is recent enough (version 3.0.8 or higher) and that you're
+      installing the development package as well, which will be need for
+      building pysqlite. On Debian and derivatives, the package to look for is
+      called libsqlite3-dev.
+
+ 3. Python:
+
+    * Python 2.3 or later
+
+Step 2: Compile pysqlite
+========================
+
+Once you have successfully installed the dependencies, you may proceed with the
+installation of pysqlite itself.
+
+pysqlite has full support for the distutils_, the standard facility for Python
+package distribution and installation. Full instructions for using the
+distutils are available in `this section of the Python documentation`_, but you
+can skip them unless you have an otherwise insoluble problem.
+
+Open a command prompt, change to the directory where you decompressed the
+pysqlite source distribution, and type::
+
+  python setup.py build
+
+The installation script, setup.py, will attempt to automatically detect the
+information needed by the C compiler; then it will invoke the distutils to
+perform the actual compilation. If you installed automatic distributions of the
+dependencies that place themselves in standard locations (on UNIX-style
+operating systems), the compilation should proceed without incident.
+
+Otherwise you will have to customize the build process. That's what the file
+*setup.cfg* is meant for. It's contains a few lines that you can customize so
+your C compiler will find the library and header files and you can also do a
+few other tweaks, like build pysqlite in debug mode.
+
+After you've customized *setup.cfg* appropriately, try invoking ``python
+setup.py build`` again.
+
+If setup.py raises no errors and its output concludes with something like
+"Creating library...", then you are ready to proceed to the next step.
+
+If you receive an error message from the compiler, then try to look at the
+first error it reports. Other errors will most likely be aftereffects from the
+first error (like not finding the sqlite3.h header file).
+
+
+Step 3: Install pysqlite
+========================
+
+During this step, the setup script moves the *pysqlite2* package (including the
+newly compiled C extension) to the standard package directory of your Python
+installation so that Python will be able to import pysqlite2.dbapi2 and
+pysqlite2.test.
+
+In addition to the Python code and shared library files actually used by the
+Python interpreter, the setup script typically installs some supporting files,
+such as documentation. Depending on your system configuration, these supporting
+files may be placed in the same directory or a different directory from the
+files used by the Python interpreter.
+
+Run the following command::
+
+  python setup.py install
+
+The setup script will install pysqlite, listing each file it installs.
+
+Errors during this step are rare because compilation (the finicky part of this
+process) has already taken place; installation is really just a matter of
+copying files. However, there will be file system permission errors if the
+Python installation directory is not writable by the user running the setup
+script. If you encounter such an error, try one of the following:
+
+- Log in as a user who has the required file system permissions and repeat the
+  installation step.
+- Manually copy the directory build/lib.platform-pyver/pysqlite2 (which
+  contains the Python modules and compiled library files created during the
+  compilation step) to a directory in your PYTHONPATH. This approach will not
+  install the supporting files, but they are for the benefit of the programmer
+  rather than the Python interpreter anyway. 
+
+Step 4: Test Your pysqlite Installation
+=======================================
+
+Switch to a directory other than the temporary directory into which you
+decompressed the source distribution (to avoid conflict between the copy of
+pysqlite in that directory and the copy placed under the standard Python
+site-packages directory), then run the pysqlite test suite by starting a Python
+interpreter for the Python version you installed pysqlite for and entering the
+following::
+
+    >>> from pysqlite2 import test
+    >>> test.test()
+    .....................................................................................................
+    ----------------------------------------------------------------------
+    Ran 101 tests in 0.182s
+
+If the test suite runs without any errors, you are finished.
+
+You should not encounter any errors at this stage since you have already
+completed the compilation and installation steps successfully. If you do,
+please report them to the `pysqlite bug tracker`_ or the `pysqlite mailing
+list`_.
+
+.. _distutils: http://www.python.org/sigs/distutils-sig/
+.. _this section of the Python documentation: http://www.python.org/doc/current/inst/inst.html
+.. _pysqlite bug tracker: http://pysqlite.googlecode.com/
+.. _pysqlite mailing list: http://itsystementwicklung.de/cgi-bin/mailman/listinfo/list-pysqlite
diff --git a/doc/sphinx/.static/.keepme b/doc/sphinx/.static/.keepme
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/doc/sphinx/Makefile b/doc/sphinx/Makefile
new file mode 100644 (file)
index 0000000..bccfdaf
--- /dev/null
@@ -0,0 +1,66 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS   =
+SPHINXBUILD  = sphinx-build
+PAPER        =
+
+ALLSPHINXOPTS = -d .build/doctrees -D latex_paper_size=$(PAPER) \
+                $(SPHINXOPTS) .
+
+.PHONY: help clean html web htmlhelp latex changes linkcheck
+
+help:
+       @echo "Please use \`make <target>' where <target> is one of"
+       @echo "  html      to make standalone HTML files"
+       @echo "  web       to make files usable by Sphinx.web"
+       @echo "  htmlhelp  to make HTML files and a HTML help project"
+       @echo "  latex     to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+       @echo "  changes   to make an overview over all changed/added/deprecated items"
+       @echo "  linkcheck to check all external links for integrity"
+
+clean:
+       -rm -rf .build/*
+
+html:
+       mkdir -p .build/html .build/doctrees
+       $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) .build/html
+       @echo
+       @echo "Build finished. The HTML pages are in .build/html."
+
+web:
+       mkdir -p .build/web .build/doctrees
+       $(SPHINXBUILD) -b web $(ALLSPHINXOPTS) .build/web
+       @echo
+       @echo "Build finished; now you can run"
+       @echo "  python -m sphinx.web .build/web"
+       @echo "to start the server."
+
+htmlhelp:
+       mkdir -p .build/htmlhelp .build/doctrees
+       $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) .build/htmlhelp
+       @echo
+       @echo "Build finished; now you can run HTML Help Workshop with the" \
+             ".hhp project file in .build/htmlhelp."
+
+latex:
+       mkdir -p .build/latex .build/doctrees
+       $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) .build/latex
+       @echo
+       @echo "Build finished; the LaTeX files are in .build/latex."
+       @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \
+             "run these through (pdf)latex."
+
+changes:
+       mkdir -p .build/changes .build/doctrees
+       $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) .build/changes
+       @echo
+       @echo "The overview file is in .build/changes."
+
+linkcheck:
+       mkdir -p .build/linkcheck .build/doctrees
+       $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) .build/linkcheck
+       @echo
+       @echo "Link check complete; look for any errors in the above output " \
+             "or in .build/linkcheck/output.txt."
diff --git a/doc/sphinx/conf.py b/doc/sphinx/conf.py
new file mode 100644 (file)
index 0000000..6528513
--- /dev/null
@@ -0,0 +1,132 @@
+# -*- coding: utf-8 -*-
+#
+# pysqlite documentation build configuration file, created by
+# sphinx-quickstart.py on Sat Mar 22 02:47:54 2008.
+#
+# This file is execfile()d with the current directory set to its containing dir.
+#
+# The contents of this file are pickled, so don't put values in the namespace
+# that aren't pickleable (module imports are okay, they're removed automatically).
+#
+# All configuration values have a default value; values that are commented out
+# serve to show the default value.
+
+import sys
+
+# If your extensions are in another directory, add it here.
+#sys.path.append('some/directory')
+
+# General configuration
+# ---------------------
+
+# Add any Sphinx extension module names here, as strings. They can be extensions
+# coming with Sphinx (named 'sphinx.addons.*') or your custom ones.
+#extensions = []
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['.templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General substitutions.
+project = 'pysqlite'
+copyright = u'2008-2009, Gerhard Häring'
+
+# The default replacements for |version| and |release|, also used in various
+# other places throughout the built documents.
+#
+# The short X.Y version.
+version = '2.6'
+# The full version, including alpha/beta/rc tags.
+release = '2.6.0'
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+#today = ''
+# Else, today_fmt is used as the format for a strftime call.
+today_fmt = '%B %d, %Y'
+
+# List of documents that shouldn't be included in the build.
+#unused_docs = []
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+#add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+#add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+#show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+
+# Options for HTML output
+# -----------------------
+
+# The style sheet to use for HTML and HTML Help pages. A file of that name
+# must exist either in Sphinx' static/ path, or in one of the custom paths
+# given in html_static_path.
+html_style = 'default.css'
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['.static']
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+#html_use_smartypants = True
+
+# Content template for the index page.
+#html_index = ''
+
+# Custom sidebar templates, maps document names to template names.
+#html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+#html_additional_pages = {}
+
+# If false, no module index is generated.
+#html_use_modindex = True
+
+# If true, the reST sources are included in the HTML build as _sources/<name>.
+#html_copy_source = True
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'pysqlitedoc'
+
+
+# Options for LaTeX output
+# ------------------------
+
+# The paper size ('letter' or 'a4').
+#latex_paper_size = 'letter'
+
+# The font size ('10pt', '11pt' or '12pt').
+#latex_font_size = '10pt'
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title, author, document class [howto/manual]).
+#latex_documents = []
+
+# Additional stuff for the LaTeX preamble.
+#latex_preamble = ''
+
+# Documents to append as an appendix to all manuals.
+#latex_appendices = []
+
+# If false, no module index is generated.
+#latex_use_modindex = True
diff --git a/doc/sphinx/index.rst b/doc/sphinx/index.rst
new file mode 100644 (file)
index 0000000..522f986
--- /dev/null
@@ -0,0 +1,19 @@
+.. pysqlite documentation master file, created by sphinx-quickstart.py on Sat Mar 22 02:47:54 2008.
+   You can adapt this file completely to your liking, but it should at least
+   contain the root `toctree` directive.
+
+Welcome to pysqlite's documentation!
+====================================
+
+Contents:
+
+.. toctree::
+   :maxdepth: 2
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
+
diff --git a/doc/sphinx/sqlite3.rst b/doc/sphinx/sqlite3.rst
new file mode 100644 (file)
index 0000000..a0d5de8
--- /dev/null
@@ -0,0 +1,886 @@
+:mod:`sqlite3` --- DB-API 2.0 interface for SQLite databases
+============================================================
+
+.. module:: sqlite3
+   :synopsis: A DB-API 2.0 implementation using SQLite 3.x.
+.. sectionauthor:: Gerhard Häring <gh@ghaering.de>
+
+
+SQLite is a C library that provides a lightweight disk-based database that
+doesn't require a separate server process and allows accessing the database
+using a nonstandard variant of the SQL query language. Some applications can use
+SQLite for internal data storage.  It's also possible to prototype an
+application using SQLite and then port the code to a larger database such as
+PostgreSQL or Oracle.
+
+pysqlite was written by Gerhard Häring and provides a SQL interface compliant
+with the DB-API 2.0 specification described by :pep:`249`.
+
+To use the module, you must first create a :class:`Connection` object that
+represents the database.  Here the data will be stored in the
+:file:`/tmp/example` file::
+
+   conn = sqlite3.connect('/tmp/example')
+
+You can also supply the special name ``:memory:`` to create a database in RAM.
+
+Once you have a :class:`Connection`, you can create a :class:`Cursor`  object
+and call its :meth:`~Cursor.execute` method to perform SQL commands::
+
+   c = conn.cursor()
+
+   # Create table
+   c.execute('''create table stocks
+   (date text, trans text, symbol text,
+    qty real, price real)''')
+
+   # Insert a row of data
+   c.execute("""insert into stocks
+             values ('2006-01-05','BUY','RHAT',100,35.14)""")
+
+   # Save (commit) the changes
+   conn.commit()
+
+   # We can also close the cursor if we are done with it
+   c.close()
+
+Usually your SQL operations will need to use values from Python variables.  You
+shouldn't assemble your query using Python's string operations because doing so
+is insecure; it makes your program vulnerable to an SQL injection attack.
+
+Instead, use the DB-API's parameter substitution.  Put ``?`` as a placeholder
+wherever you want to use a value, and then provide a tuple of values as the
+second argument to the cursor's :meth:`~Cursor.execute` method.  (Other database
+modules may use a different placeholder, such as ``%s`` or ``:1``.) For
+example::
+
+   # Never do this -- insecure!
+   symbol = 'IBM'
+   c.execute("... where symbol = '%s'" % symbol)
+
+   # Do this instead
+   t = (symbol,)
+   c.execute('select * from stocks where symbol=?', t)
+
+   # Larger example
+   for t in [('2006-03-28', 'BUY', 'IBM', 1000, 45.00),
+             ('2006-04-05', 'BUY', 'MSOFT', 1000, 72.00),
+             ('2006-04-06', 'SELL', 'IBM', 500, 53.00),
+            ]:
+       c.execute('insert into stocks values (?,?,?,?,?)', t)
+
+To retrieve data after executing a SELECT statement, you can either treat the
+cursor as an :term:`iterator`, call the cursor's :meth:`~Cursor.fetchone` method to
+retrieve a single matching row, or call :meth:`~Cursor.fetchall` to get a list of the
+matching rows.
+
+This example uses the iterator form::
+
+   >>> c = conn.cursor()
+   >>> c.execute('select * from stocks order by price')
+   >>> for row in c:
+   ...    print row
+   ...
+   (u'2006-01-05', u'BUY', u'RHAT', 100, 35.14)
+   (u'2006-03-28', u'BUY', u'IBM', 1000, 45.0)
+   (u'2006-04-06', u'SELL', u'IBM', 500, 53.0)
+   (u'2006-04-05', u'BUY', u'MSOFT', 1000, 72.0)
+   >>>
+
+
+.. seealso::
+
+   http://code.google.com/p/pysqlite/
+      The pysqlite web page -- sqlite3 is developed externally under the name
+      "pysqlite".
+
+   http://www.sqlite.org
+      The SQLite web page; the documentation describes the syntax and the
+      available data types for the supported SQL dialect.
+
+   :pep:`249` - Database API Specification 2.0
+      PEP written by Marc-André Lemburg.
+
+
+.. _sqlite3-module-contents:
+
+Module functions and constants
+------------------------------
+
+
+.. data:: PARSE_DECLTYPES
+
+   This constant is meant to be used with the *detect_types* parameter of the
+   :func:`connect` function.
+
+   Setting it makes the :mod:`sqlite3` module parse the declared type for each
+   column it returns.  It will parse out the first word of the declared type,
+   i. e.  for "integer primary key", it will parse out "integer", or for
+   "number(10)" it will parse out "number". Then for that column, it will look
+   into the converters dictionary and use the converter function registered for
+   that type there.
+
+
+.. data:: PARSE_COLNAMES
+
+   This constant is meant to be used with the *detect_types* parameter of the
+   :func:`connect` function.
+
+   Setting this makes the SQLite interface parse the column name for each column it
+   returns.  It will look for a string formed [mytype] in there, and then decide
+   that 'mytype' is the type of the column. It will try to find an entry of
+   'mytype' in the converters dictionary and then use the converter function found
+   there to return the value. The column name found in :attr:`Cursor.description`
+   is only the first word of the column name, i.  e. if you use something like
+   ``'as "x [datetime]"'`` in your SQL, then we will parse out everything until the
+   first blank for the column name: the column name would simply be "x".
+
+
+.. function:: connect(database[, timeout, isolation_level, detect_types, factory])
+
+   Opens a connection to the SQLite database file *database*. You can use
+   ``":memory:"`` to open a database connection to a database that resides in RAM
+   instead of on disk.
+
+   When a database is accessed by multiple connections, and one of the processes
+   modifies the database, the SQLite database is locked until that transaction is
+   committed. The *timeout* parameter specifies how long the connection should wait
+   for the lock to go away until raising an exception. The default for the timeout
+   parameter is 5.0 (five seconds).
+
+   For the *isolation_level* parameter, please see the
+   :attr:`Connection.isolation_level` property of :class:`Connection` objects.
+
+   SQLite natively supports only the types TEXT, INTEGER, FLOAT, BLOB and NULL. If
+   you want to use other types you must add support for them yourself. The
+   *detect_types* parameter and the using custom **converters** registered with the
+   module-level :func:`register_converter` function allow you to easily do that.
+
+   *detect_types* defaults to 0 (i. e. off, no type detection), you can set it to
+   any combination of :const:`PARSE_DECLTYPES` and :const:`PARSE_COLNAMES` to turn
+   type detection on.
+
+   By default, the :mod:`sqlite3` module uses its :class:`Connection` class for the
+   connect call.  You can, however, subclass the :class:`Connection` class and make
+   :func:`connect` use your class instead by providing your class for the *factory*
+   parameter.
+
+   Consult the section :ref:`sqlite3-types` of this manual for details.
+
+   The :mod:`sqlite3` module internally uses a statement cache to avoid SQL parsing
+   overhead. If you want to explicitly set the number of statements that are cached
+   for the connection, you can set the *cached_statements* parameter. The currently
+   implemented default is to cache 100 statements.
+
+
+.. function:: register_converter(typename, callable)
+
+   Registers a callable to convert a bytestring from the database into a custom
+   Python type. The callable will be invoked for all database values that are of
+   the type *typename*. Confer the parameter *detect_types* of the :func:`connect`
+   function for how the type detection works. Note that the case of *typename* and
+   the name of the type in your query must match!
+
+
+.. function:: register_adapter(type, callable)
+
+   Registers a callable to convert the custom Python type *type* into one of
+   SQLite's supported types. The callable *callable* accepts as single parameter
+   the Python value, and must return a value of the following types: int, long,
+   float, str (UTF-8 encoded), unicode or buffer.
+
+
+.. function:: complete_statement(sql)
+
+   Returns :const:`True` if the string *sql* contains one or more complete SQL
+   statements terminated by semicolons. It does not verify that the SQL is
+   syntactically correct, only that there are no unclosed string literals and the
+   statement is terminated by a semicolon.
+
+   This can be used to build a shell for SQLite, as in the following example:
+
+
+   .. literalinclude:: ../includes/sqlite3/complete_statement.py
+
+
+.. function:: enable_callback_tracebacks(flag)
+
+   By default you will not get any tracebacks in user-defined functions,
+   aggregates, converters, authorizer callbacks etc. If you want to debug them, you
+   can call this function with *flag* as True. Afterwards, you will get tracebacks
+   from callbacks on ``sys.stderr``. Use :const:`False` to disable the feature
+   again.
+
+
+.. _sqlite3-connection-objects:
+
+Connection Objects
+------------------
+
+.. class:: Connection
+
+   A SQLite database connection has the following attributes and methods:
+
+.. attribute:: Connection.isolation_level
+
+   Get or set the current isolation level. :const:`None` for autocommit mode or
+   one of "DEFERRED", "IMMEDIATE" or "EXCLUSIVE". See section
+   :ref:`sqlite3-controlling-transactions` for a more detailed explanation.
+
+
+.. method:: Connection.cursor([cursorClass])
+
+   The cursor method accepts a single optional parameter *cursorClass*. If
+   supplied, this must be a custom cursor class that extends
+   :class:`sqlite3.Cursor`.
+
+
+.. method:: Connection.commit()
+
+   This method commits the current transaction. If you don't call this method,
+   anything you did since the last call to ``commit()`` is not visible from from
+   other database connections. If you wonder why you don't see the data you've
+   written to the database, please check you didn't forget to call this method.
+
+.. method:: Connection.rollback()
+
+   This method rolls back any changes to the database since the last call to
+   :meth:`commit`.
+
+.. method:: Connection.close()
+
+   This closes the database connection. Note that this does not automatically
+   call :meth:`commit`. If you just close your database connection without
+   calling :meth:`commit` first, your changes will be lost!
+
+.. method:: Connection.execute(sql, [parameters])
+
+   This is a nonstandard shortcut that creates an intermediate cursor object by
+   calling the cursor method, then calls the cursor's
+   :meth:`execute<Cursor.execute>` method with the parameters given.
+
+
+.. method:: Connection.executemany(sql, [parameters])
+
+   This is a nonstandard shortcut that creates an intermediate cursor object by
+   calling the cursor method, then calls the cursor's
+   :meth:`executemany<Cursor.executemany>` method with the parameters given.
+
+.. method:: Connection.executescript(sql_script)
+
+   This is a nonstandard shortcut that creates an intermediate cursor object by
+   calling the cursor method, then calls the cursor's
+   :meth:`executescript<Cursor.executescript>` method with the parameters
+   given.
+
+
+.. method:: Connection.create_function(name, num_params, func)
+
+   Creates a user-defined function that you can later use from within SQL
+   statements under the function name *name*. *num_params* is the number of
+   parameters the function accepts, and *func* is a Python callable that is called
+   as the SQL function.
+
+   The function can return any of the types supported by SQLite: unicode, str, int,
+   long, float, buffer and None.
+
+   Example:
+
+   .. literalinclude:: ../includes/sqlite3/md5func.py
+
+
+.. method:: Connection.create_aggregate(name, num_params, aggregate_class)
+
+   Creates a user-defined aggregate function.
+
+   The aggregate class must implement a ``step`` method, which accepts the number
+   of parameters *num_params*, and a ``finalize`` method which will return the
+   final result of the aggregate.
+
+   The ``finalize`` method can return any of the types supported by SQLite:
+   unicode, str, int, long, float, buffer and None.
+
+   Example:
+
+   .. literalinclude:: ../includes/sqlite3/mysumaggr.py
+
+
+.. method:: Connection.create_collation(name, callable)
+
+   Creates a collation with the specified *name* and *callable*. The callable will
+   be passed two string arguments. It should return -1 if the first is ordered
+   lower than the second, 0 if they are ordered equal and 1 if the first is ordered
+   higher than the second.  Note that this controls sorting (ORDER BY in SQL) so
+   your comparisons don't affect other SQL operations.
+
+   Note that the callable will get its parameters as Python bytestrings, which will
+   normally be encoded in UTF-8.
+
+   The following example shows a custom collation that sorts "the wrong way":
+
+   .. literalinclude:: ../includes/sqlite3/collation_reverse.py
+
+   To remove a collation, call ``create_collation`` with None as callable::
+
+      con.create_collation("reverse", None)
+
+
+.. method:: Connection.interrupt()
+
+   You can call this method from a different thread to abort any queries that might
+   be executing on the connection. The query will then abort and the caller will
+   get an exception.
+
+
+.. method:: Connection.set_authorizer(authorizer_callback)
+
+   This routine registers a callback. The callback is invoked for each attempt to
+   access a column of a table in the database. The callback should return
+   :const:`SQLITE_OK` if access is allowed, :const:`SQLITE_DENY` if the entire SQL
+   statement should be aborted with an error and :const:`SQLITE_IGNORE` if the
+   column should be treated as a NULL value. These constants are available in the
+   :mod:`sqlite3` module.
+
+   The first argument to the callback signifies what kind of operation is to be
+   authorized. The second and third argument will be arguments or :const:`None`
+   depending on the first argument. The 4th argument is the name of the database
+   ("main", "temp", etc.) if applicable. The 5th argument is the name of the
+   inner-most trigger or view that is responsible for the access attempt or
+   :const:`None` if this access attempt is directly from input SQL code.
+
+   Please consult the SQLite documentation about the possible values for the first
+   argument and the meaning of the second and third argument depending on the first
+   one. All necessary constants are available in the :mod:`sqlite3` module.
+
+
+.. method:: Connection.set_progress_handler(handler, n)
+
+   This routine registers a callback. The callback is invoked for every *n*
+   instructions of the SQLite virtual machine. This is useful if you want to
+   get called from SQLite during long-running operations, for example to update
+   a GUI.
+
+   If you want to clear any previously installed progress handler, call the
+   method with :const:`None` for *handler*.
+
+
+.. method:: Connection.enable_load_extension(enabled)
+
+   This routine allows/disallows the SQLite engine to load SQLite extensions
+   from shared libraries.  SQLite extensions can define new functions,
+   aggregates or whole new virtual table implementations. One well-known
+   extension is the fulltext-search extension distributed with SQLite.
+
+   .. literalinclude:: ../includes/sqlite3/load_extension.py
+
+.. method:: Connection.load_extension(path)
+
+   This routine loads a SQLite extension from a shared library. You have to
+   enable extension loading with ``enable_load_extension`` before you can use
+   this routine.
+
+.. attribute:: Connection.row_factory
+
+   You can change this attribute to a callable that accepts the cursor and the
+   original row as a tuple and will return the real result row.  This way, you can
+   implement more advanced ways of returning results, such  as returning an object
+   that can also access columns by name.
+
+   Example:
+
+   .. literalinclude:: ../includes/sqlite3/row_factory.py
+
+   If returning a tuple doesn't suffice and you want name-based access to
+   columns, you should consider setting :attr:`row_factory` to the
+   highly-optimized :class:`sqlite3.Row` type. :class:`Row` provides both
+   index-based and case-insensitive name-based access to columns with almost no
+   memory overhead. It will probably be better than your own custom
+   dictionary-based approach or even a db_row based solution.
+
+   .. XXX what's a db_row-based solution?
+
+
+.. attribute:: Connection.text_factory
+
+   Using this attribute you can control what objects are returned for the ``TEXT``
+   data type. By default, this attribute is set to :class:`unicode` and the
+   :mod:`sqlite3` module will return Unicode objects for ``TEXT``. If you want to
+   return bytestrings instead, you can set it to :class:`str`.
+
+   For efficiency reasons, there's also a way to return Unicode objects only for
+   non-ASCII data, and bytestrings otherwise. To activate it, set this attribute to
+   :const:`sqlite3.OptimizedUnicode`.
+
+   You can also set it to any other callable that accepts a single bytestring
+   parameter and returns the resulting object.
+
+   See the following example code for illustration:
+
+   .. literalinclude:: ../includes/sqlite3/text_factory.py
+
+
+.. attribute:: Connection.total_changes
+
+   Returns the total number of database rows that have been modified, inserted, or
+   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
+--------------
+
+A :class:`Cursor` instance has the following attributes and methods:
+
+   A SQLite database cursor has the following attributes and methods:
+
+.. method:: Cursor.execute(sql, [parameters])
+
+   Executes an SQL statement. The SQL statement may be parametrized (i. e.
+   placeholders instead of SQL literals). The :mod:`sqlite3` module supports two
+   kinds of placeholders: question marks (qmark style) and named placeholders
+   (named style).
+
+   This example shows how to use parameters with qmark style:
+
+   .. literalinclude:: ../includes/sqlite3/execute_1.py
+
+   This example shows how to use the named style:
+
+   .. literalinclude:: ../includes/sqlite3/execute_2.py
+
+   :meth:`execute` will only execute a single SQL statement. If you try to execute
+   more than one statement with it, it will raise a Warning. Use
+   :meth:`executescript` if you want to execute multiple SQL statements with one
+   call.
+
+
+.. method:: Cursor.executemany(sql, seq_of_parameters)
+
+   Executes an SQL command against all parameter sequences or mappings found in
+   the sequence *sql*.  The :mod:`sqlite3` module also allows using an
+   :term:`iterator` yielding parameters instead of a sequence.
+
+   .. literalinclude:: ../includes/sqlite3/executemany_1.py
+
+   Here's a shorter example using a :term:`generator`:
+
+   .. literalinclude:: ../includes/sqlite3/executemany_2.py
+
+
+.. method:: Cursor.executescript(sql_script)
+
+   This is a nonstandard convenience method for executing multiple SQL statements
+   at once. It issues a ``COMMIT`` statement first, then executes the SQL script it
+   gets as a parameter.
+
+   *sql_script* can be a bytestring or a Unicode string.
+
+   Example:
+
+   .. literalinclude:: ../includes/sqlite3/executescript.py
+
+
+.. method:: Cursor.fetchone()
+
+   Fetches the next row of a query result set, returning a single sequence,
+   or :const:`None` when no more data is available.
+
+
+.. method:: Cursor.fetchmany([size=cursor.arraysize])
+
+   Fetches the next set of rows of a query result, returning a list.  An empty
+   list is returned when no more rows are available.
+
+   The number of rows to fetch per call is specified by the *size* parameter.
+   If it is not given, the cursor's arraysize determines the number of rows
+   to be fetched. The method should try to fetch as many rows as indicated by
+   the size parameter. If this is not possible due to the specified number of
+   rows not being available, fewer rows may be returned.
+
+   Note there are performance considerations involved with the *size* parameter.
+   For optimal performance, it is usually best to use the arraysize attribute.
+   If the *size* parameter is used, then it is best for it to retain the same
+   value from one :meth:`fetchmany` call to the next.
+
+.. method:: Cursor.fetchall()
+
+   Fetches all (remaining) rows of a query result, returning a list.  Note that
+   the cursor's arraysize attribute can affect the performance of this operation.
+   An empty list is returned when no rows are available.
+
+
+.. attribute:: Cursor.rowcount
+
+   Although the :class:`Cursor` class of the :mod:`sqlite3` module implements this
+   attribute, the database engine's own support for the determination of "rows
+   affected"/"rows selected" is quirky.
+
+   For ``DELETE`` statements, SQLite reports :attr:`rowcount` as 0 if you make a
+   ``DELETE FROM table`` without any condition.
+
+   For :meth:`executemany` statements, the number of modifications are summed up
+   into :attr:`rowcount`.
+
+   As required by the Python DB API Spec, the :attr:`rowcount` attribute "is -1 in
+   case no ``executeXX()`` has been performed on the cursor or the rowcount of the
+   last operation is not determinable by the interface".
+
+   This includes ``SELECT`` statements because we cannot determine the number of
+   rows a query produced until all rows were fetched.
+
+.. attribute:: Cursor.lastrowid
+
+   This read-only attribute provides the rowid of the last modified row. It is
+   only set if you issued a ``INSERT`` statement using the :meth:`execute`
+   method. For operations other than ``INSERT`` or when :meth:`executemany` is
+   called, :attr:`lastrowid` is set to :const:`None`.
+
+.. attribute:: Cursor.description
+
+   This read-only attribute provides the column names of the last query. To
+   remain compatible with the Python DB API, it returns a 7-tuple for each
+   column where the last six items of each tuple are :const:`None`.
+
+   It is set for ``SELECT`` statements without any matching rows as well.
+
+.. _sqlite3-row-objects:
+
+Row Objects
+-----------
+
+.. class:: Row
+
+   A :class:`Row` instance serves as a highly optimized
+   :attr:`~Connection.row_factory` for :class:`Connection` objects.
+   It tries to mimic a tuple in most of its features.
+
+   It supports mapping access by column name and index, iteration,
+   representation, equality testing and :func:`len`.
+
+   If two :class:`Row` objects have exactly the same columns and their
+   members are equal, they compare equal.
+
+   .. versionchanged:: 2.6
+      Added iteration and equality (hashability).
+
+   .. method:: keys
+
+      This method returns a tuple of column names. Immediately after a query,
+      it is the first member of each tuple in :attr:`Cursor.description`.
+
+      .. versionadded:: 2.6
+
+Let's assume we initialize a table as in the example given above::
+
+    conn = sqlite3.connect(":memory:")
+    c = conn.cursor()
+    c.execute('''create table stocks
+    (date text, trans text, symbol text,
+     qty real, price real)''')
+    c.execute("""insert into stocks
+              values ('2006-01-05','BUY','RHAT',100,35.14)""")
+    conn.commit()
+    c.close()
+
+Now we plug :class:`Row` in::
+
+    >>> conn.row_factory = sqlite3.Row
+    >>> c = conn.cursor()
+    >>> c.execute('select * from stocks')
+    <sqlite3.Cursor object at 0x7f4e7dd8fa80>
+    >>> r = c.fetchone()
+    >>> type(r)
+    <type 'sqlite3.Row'>
+    >>> r
+    (u'2006-01-05', u'BUY', u'RHAT', 100.0, 35.14)
+    >>> len(r)
+    5
+    >>> r[2]
+    u'RHAT'
+    >>> r.keys()
+    ['date', 'trans', 'symbol', 'qty', 'price']
+    >>> r['qty']
+    100.0
+    >>> for member in r: print member
+    ...
+    2006-01-05
+    BUY
+    RHAT
+    100.0
+    35.14
+
+
+.. _sqlite3-types:
+
+SQLite and Python types
+-----------------------
+
+
+Introduction
+^^^^^^^^^^^^
+
+SQLite natively supports the following types: ``NULL``, ``INTEGER``,
+``REAL``, ``TEXT``, ``BLOB``.
+
+The following Python types can thus be sent to SQLite without any problem:
+
++-----------------------------+-------------+
+| Python type                 | SQLite type |
++=============================+=============+
+| :const:`None`               | ``NULL``    |
++-----------------------------+-------------+
+| :class:`int`                | ``INTEGER`` |
++-----------------------------+-------------+
+| :class:`long`               | ``INTEGER`` |
++-----------------------------+-------------+
+| :class:`float`              | ``REAL``    |
++-----------------------------+-------------+
+| :class:`str` (UTF8-encoded) | ``TEXT``    |
++-----------------------------+-------------+
+| :class:`unicode`            | ``TEXT``    |
++-----------------------------+-------------+
+| :class:`buffer`             | ``BLOB``    |
++-----------------------------+-------------+
+
+This is how SQLite types are converted to Python types by default:
+
++-------------+----------------------------------------------+
+| SQLite type | Python type                                  |
++=============+==============================================+
+| ``NULL``    | :const:`None`                                |
++-------------+----------------------------------------------+
+| ``INTEGER`` | :class:`int` or :class:`long`,               |
+|             | depending on size                            |
++-------------+----------------------------------------------+
+| ``REAL``    | :class:`float`                               |
++-------------+----------------------------------------------+
+| ``TEXT``    | depends on :attr:`~Connection.text_factory`, |
+|             | :class:`unicode` by default                  |
++-------------+----------------------------------------------+
+| ``BLOB``    | :class:`buffer`                              |
++-------------+----------------------------------------------+
+
+The type system of the :mod:`sqlite3` module is extensible in two ways: you can
+store additional Python types in a SQLite database via object adaptation, and
+you can let the :mod:`sqlite3` module convert SQLite types to different Python
+types via converters.
+
+
+Using adapters to store additional Python types in SQLite databases
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+As described before, SQLite supports only a limited set of types natively. To
+use other Python types with SQLite, you must **adapt** them to one of the
+sqlite3 module's supported types for SQLite: one of NoneType, int, long, float,
+str, unicode, buffer.
+
+The :mod:`sqlite3` module uses Python object adaptation, as described in
+:pep:`246` for this.  The protocol to use is :class:`PrepareProtocol`.
+
+There are two ways to enable the :mod:`sqlite3` module to adapt a custom Python
+type to one of the supported ones.
+
+
+Letting your object adapt itself
+""""""""""""""""""""""""""""""""
+
+This is a good approach if you write the class yourself. Let's suppose you have
+a class like this::
+
+   class Point(object):
+       def __init__(self, x, y):
+           self.x, self.y = x, y
+
+Now you want to store the point in a single SQLite column.  First you'll have to
+choose one of the supported types first to be used for representing the point.
+Let's just use str and separate the coordinates using a semicolon. Then you need
+to give your class a method ``__conform__(self, protocol)`` which must return
+the converted value. The parameter *protocol* will be :class:`PrepareProtocol`.
+
+.. literalinclude:: ../includes/sqlite3/adapter_point_1.py
+
+
+Registering an adapter callable
+"""""""""""""""""""""""""""""""
+
+The other possibility is to create a function that converts the type to the
+string representation and register the function with :meth:`register_adapter`.
+
+.. note::
+
+   The type/class to adapt must be a :term:`new-style class`, i. e. it must have
+   :class:`object` as one of its bases.
+
+.. literalinclude:: ../includes/sqlite3/adapter_point_2.py
+
+The :mod:`sqlite3` module has two default adapters for Python's built-in
+:class:`datetime.date` and :class:`datetime.datetime` types.  Now let's suppose
+we want to store :class:`datetime.datetime` objects not in ISO representation,
+but as a Unix timestamp.
+
+.. literalinclude:: ../includes/sqlite3/adapter_datetime.py
+
+
+Converting SQLite values to custom Python types
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Writing an adapter lets you send custom Python types to SQLite. But to make it
+really useful we need to make the Python to SQLite to Python roundtrip work.
+
+Enter converters.
+
+Let's go back to the :class:`Point` class. We stored the x and y coordinates
+separated via semicolons as strings in SQLite.
+
+First, we'll define a converter function that accepts the string as a parameter
+and constructs a :class:`Point` object from it.
+
+.. note::
+
+   Converter functions **always** get called with a string, no matter under which
+   data type you sent the value to SQLite.
+
+::
+
+   def convert_point(s):
+       x, y = map(float, s.split(";"))
+       return Point(x, y)
+
+Now you need to make the :mod:`sqlite3` module know that what you select from
+the database is actually a point. There are two ways of doing this:
+
+* Implicitly via the declared type
+
+* Explicitly via the column name
+
+Both ways are described in section :ref:`sqlite3-module-contents`, in the entries
+for the constants :const:`PARSE_DECLTYPES` and :const:`PARSE_COLNAMES`.
+
+The following example illustrates both approaches.
+
+.. literalinclude:: ../includes/sqlite3/converter_point.py
+
+
+Default adapters and converters
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+There are default adapters for the date and datetime types in the datetime
+module. They will be sent as ISO dates/ISO timestamps to SQLite.
+
+The default converters are registered under the name "date" for
+:class:`datetime.date` and under the name "timestamp" for
+:class:`datetime.datetime`.
+
+This way, you can use date/timestamps from Python without any additional
+fiddling in most cases. The format of the adapters is also compatible with the
+experimental SQLite date/time functions.
+
+The following example demonstrates this.
+
+.. literalinclude:: ../includes/sqlite3/pysqlite_datetime.py
+
+
+.. _sqlite3-controlling-transactions:
+
+Controlling Transactions
+------------------------
+
+By default, the :mod:`sqlite3` module opens transactions implicitly before a
+Data Modification Language (DML)  statement (i.e.
+``INSERT``/``UPDATE``/``DELETE``/``REPLACE``), and commits transactions
+implicitly before a non-DML, non-query statement (i. e.
+anything other than ``SELECT`` or the aforementioned).
+
+So if you are within a transaction and issue a command like ``CREATE TABLE
+...``, ``VACUUM``, ``PRAGMA``, the :mod:`sqlite3` module will commit implicitly
+before executing that command. There are two reasons for doing that. The first
+is that some of these commands don't work within transactions. The other reason
+is that pysqlite needs to keep track of the transaction state (if a transaction
+is active or not).
+
+You can control which kind of ``BEGIN`` statements sqlite3 implicitly executes
+(or none at all) via the *isolation_level* parameter to the :func:`connect`
+call, or via the :attr:`isolation_level` property of connections.
+
+If you want **autocommit mode**, then set :attr:`isolation_level` to None.
+
+Otherwise leave it at its default, which will result in a plain "BEGIN"
+statement, or set it to one of SQLite's supported isolation levels: "DEFERRED",
+"IMMEDIATE" or "EXCLUSIVE".
+
+
+
+Using :mod:`sqlite3` efficiently
+--------------------------------
+
+
+Using shortcut methods
+^^^^^^^^^^^^^^^^^^^^^^
+
+Using the nonstandard :meth:`execute`, :meth:`executemany` and
+:meth:`executescript` methods of the :class:`Connection` object, your code can
+be written more concisely because you don't have to create the (often
+superfluous) :class:`Cursor` objects explicitly. Instead, the :class:`Cursor`
+objects are created implicitly and these shortcut methods return the cursor
+objects. This way, you can execute a ``SELECT`` statement and iterate over it
+directly using only a single call on the :class:`Connection` object.
+
+.. literalinclude:: ../includes/sqlite3/shortcut_methods.py
+
+
+Accessing columns by name instead of by index
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+One useful feature of the :mod:`sqlite3` module is the built-in
+:class:`sqlite3.Row` class designed to be used as a row factory.
+
+Rows wrapped with this class can be accessed both by index (like tuples) and
+case-insensitively by name:
+
+.. literalinclude:: ../includes/sqlite3/rowclass.py
+
+
+Using the connection as a context manager
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+With Python 2.5 or higher, connection objects can be used as context managers
+that automatically commit or rollback transactions.  In the event of an
+exception, the transaction is rolled back; otherwise, the transaction is
+committed:
+
+.. literalinclude:: ../includes/sqlite3/ctx_manager.py
+
+
+Common issues
+-------------
+
+Multithreading
+^^^^^^^^^^^^^^
+
+Older SQLite versions had issues with sharing connections between threads.
+That's why the Python module disallows sharing connections and cursors between
+threads. If you still try to do so, you will get an exception at runtime.
+
+The only exception is calling the :meth:`~Connection.interrupt` method, which
+only makes sense to call from a different thread.
+
diff --git a/lib/__init__.py b/lib/__init__.py
new file mode 100644 (file)
index 0000000..3920a2b
--- /dev/null
@@ -0,0 +1,22 @@
+#-*- coding: ISO-8859-1 -*-
+# pysqlcipher/__init__.py: the pysqlite2 package.
+#
+# Copyright (C) 2005-2007 Gerhard Häring <gh@ghaering.de>
+#
+# This file is part of pysqlite.
+#
+# This software is provided 'as-is', without any express or implied
+# warranty.  In no event will the authors be held liable for any damages
+# arising from the use of this software.
+#
+# Permission is granted to anyone to use this software for any purpose,
+# including commercial applications, and to alter it and redistribute it
+# freely, subject to the following restrictions:
+#
+# 1. The origin of this software must not be misrepresented; you must not
+#    claim that you wrote the original software. If you use this software
+#    in a product, an acknowledgment in the product documentation would be
+#    appreciated but is not required.
+# 2. Altered source versions must be plainly marked as such, and must not be
+#    misrepresented as being the original software.
+# 3. This notice may not be removed or altered from any source distribution.
diff --git a/lib/dbapi2.py b/lib/dbapi2.py
new file mode 100644 (file)
index 0000000..78f340e
--- /dev/null
@@ -0,0 +1,92 @@
+#-*- coding: ISO-8859-1 -*-
+# pysqlcipher/dbapi2.py: the DB-API 2.0 interface with SQLCipher support
+#
+# Copyright (C) Kali Kaneko <kali@futeisha.org>
+# Copyright (C) 2010 Gerhard Häring <gh@ghaering.de>
+#
+# This file is part of pysqlcipher.
+#
+# This software is provided 'as-is', without any express or implied
+# warranty.  In no event will the authors be held liable for any damages
+# arising from the use of this software.
+#
+# Permission is granted to anyone to use this software for any purpose,
+# including commercial applications, and to alter it and redistribute it
+# freely, subject to the following restrictions:
+#
+# 1. The origin of this software must not be misrepresented; you must not
+#    claim that you wrote the original software. If you use this software
+#    in a product, an acknowledgment in the product documentation would be
+#    appreciated but is not required.
+# 2. Altered source versions must be plainly marked as such, and must not be
+#    misrepresented as being the original software.
+# 3. This notice may not be removed or altered from any source distribution.
+
+import datetime
+import time
+
+try:
+    from pysqlcipher.libsqlite import *
+except ImportError:
+    from pysqlcipher._sqlite import *
+
+paramstyle = "qmark"
+
+threadsafety = 1
+
+apilevel = "2.0"
+
+Date = datetime.date
+
+Time = datetime.time
+
+Timestamp = datetime.datetime
+
+def DateFromTicks(ticks):
+    return Date(*time.localtime(ticks)[:3])
+
+def TimeFromTicks(ticks):
+    return Time(*time.localtime(ticks)[3:6])
+
+def TimestampFromTicks(ticks):
+    return Timestamp(*time.localtime(ticks)[:6])
+
+version_info = tuple([int(x) for x in version.split(".")])
+sqlite_version_info = tuple([int(x) for x in sqlite_version.split(".")])
+
+Binary = buffer
+
+def register_adapters_and_converters():
+    def adapt_date(val):
+        return val.isoformat()
+
+    def adapt_datetime(val):
+        return val.isoformat(" ")
+
+    def convert_date(val):
+        return datetime.date(*map(int, val.split("-")))
+
+    def convert_timestamp(val):
+        datepart, timepart = val.split(" ")
+        year, month, day = map(int, datepart.split("-"))
+        timepart_full = timepart.split(".")
+        hours, minutes, seconds = map(int, timepart_full[0].split(":"))
+        if len(timepart_full) == 2:
+            microseconds = int(timepart_full[1])
+        else:
+            microseconds = 0
+
+        val = datetime.datetime(year, month, day, hours, minutes, seconds, microseconds)
+        return val
+
+
+    register_adapter(datetime.date, adapt_date)
+    register_adapter(datetime.datetime, adapt_datetime)
+    register_converter("date", convert_date)
+    register_converter("timestamp", convert_timestamp)
+
+register_adapters_and_converters()
+
+# Clean up namespace
+
+del(register_adapters_and_converters)
diff --git a/lib/dump.py b/lib/dump.py
new file mode 100644 (file)
index 0000000..409a405
--- /dev/null
@@ -0,0 +1,63 @@
+# Mimic the sqlite3 console shell's .dump command
+# Author: Paul Kippes <kippesp@gmail.com>
+
+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;')
diff --git a/lib/test/__init__.py b/lib/test/__init__.py
new file mode 100644 (file)
index 0000000..1c760bb
--- /dev/null
@@ -0,0 +1,50 @@
+#-*- coding: ISO-8859-1 -*-
+# pysqlite2/test/__init__.py: the package containing the test suite
+#
+# Copyright (C) 2004-2007 Gerhard Häring <gh@ghaering.de>
+#
+# This file is part of pysqlite.
+#
+# This software is provided 'as-is', without any express or implied
+# warranty.  In no event will the authors be held liable for any damages
+# arising from the use of this software.
+#
+# Permission is granted to anyone to use this software for any purpose,
+# including commercial applications, and to alter it and redistribute it
+# freely, subject to the following restrictions:
+#
+# 1. The origin of this software must not be misrepresented; you must not
+#    claim that you wrote the original software. If you use this software
+#    in a product, an acknowledgment in the product documentation would be
+#    appreciated but is not required.
+# 2. Altered source versions must be plainly marked as such, and must not be
+#    misrepresented as being the original software.
+# 3. This notice may not be removed or altered from any source distribution.
+
+import os, sys
+import unittest
+
+if os.path.exists("extended_setup.py"):
+    print "-" * 75
+    print "You should not run the test suite from the pysqlite build directory."
+    print "This does not work well because the extension module cannot be found."
+    print "Just run the test suite from somewhere else, please!"
+    print "-" * 75
+    sys.exit(1)
+
+from pysqlite2.test import dbapi, types, userfunctions, factory, transactions,\
+    hooks, regression, dump
+from pysqlcipher import dbapi2 as sqlite
+
+def suite():
+    tests = [dbapi.suite(), types.suite(), userfunctions.suite(),
+      factory.suite(), transactions.suite(), hooks.suite(), regression.suite(), dump.suite()]
+    if sys.version_info >= (2, 5, 0):
+        from pysqlite2.test.py25 import py25tests
+        tests.append(py25tests.suite())
+
+    return unittest.TestSuite(tuple(tests))
+
+def test():
+    runner = unittest.TextTestRunner()
+    runner.run(suite())
diff --git a/lib/test/dbapi.py b/lib/test/dbapi.py
new file mode 100644 (file)
index 0000000..05dfd4b
--- /dev/null
@@ -0,0 +1,878 @@
+#-*- coding: ISO-8859-1 -*-
+# pysqlite2/test/dbapi.py: tests for DB-API compliance
+#
+# Copyright (C) 2004-2009 Gerhard Häring <gh@ghaering.de>
+#
+# This file is part of pysqlite.
+#
+# This software is provided 'as-is', without any express or implied
+# warranty.  In no event will the authors be held liable for any damages
+# arising from the use of this software.
+#
+# Permission is granted to anyone to use this software for any purpose,
+# including commercial applications, and to alter it and redistribute it
+# freely, subject to the following restrictions:
+#
+# 1. The origin of this software must not be misrepresented; you must not
+#    claim that you wrote the original software. If you use this software
+#    in a product, an acknowledgment in the product documentation would be
+#    appreciated but is not required.
+# 2. Altered source versions must be plainly marked as such, and must not be
+#    misrepresented as being the original software.
+# 3. This notice may not be removed or altered from any source distribution.
+
+import unittest
+import sys
+import threading
+import pysqlcipher.dbapi2 as sqlite
+
+class ModuleTests(unittest.TestCase):
+    def CheckAPILevel(self):
+        self.assertEqual(sqlite.apilevel, "2.0",
+                         "apilevel is %s, should be 2.0" % sqlite.apilevel)
+
+    def CheckThreadSafety(self):
+        self.assertEqual(sqlite.threadsafety, 1,
+                         "threadsafety is %d, should be 1" % sqlite.threadsafety)
+
+    def CheckParamStyle(self):
+        self.assertEqual(sqlite.paramstyle, "qmark",
+                         "paramstyle is '%s', should be 'qmark'" %
+                         sqlite.paramstyle)
+
+    def CheckWarning(self):
+        self.assert_(issubclass(sqlite.Warning, StandardError),
+                     "Warning is not a subclass of StandardError")
+
+    def CheckError(self):
+        self.assertTrue(issubclass(sqlite.Error, StandardError),
+                        "Error is not a subclass of StandardError")
+
+    def CheckInterfaceError(self):
+        self.assertTrue(issubclass(sqlite.InterfaceError, sqlite.Error),
+                        "InterfaceError is not a subclass of Error")
+
+    def CheckDatabaseError(self):
+        self.assertTrue(issubclass(sqlite.DatabaseError, sqlite.Error),
+                        "DatabaseError is not a subclass of Error")
+
+    def CheckDataError(self):
+        self.assertTrue(issubclass(sqlite.DataError, sqlite.DatabaseError),
+                        "DataError is not a subclass of DatabaseError")
+
+    def CheckOperationalError(self):
+        self.assertTrue(issubclass(sqlite.OperationalError, sqlite.DatabaseError),
+                        "OperationalError is not a subclass of DatabaseError")
+
+    def CheckIntegrityError(self):
+        self.assertTrue(issubclass(sqlite.IntegrityError, sqlite.DatabaseError),
+                        "IntegrityError is not a subclass of DatabaseError")
+
+    def CheckInternalError(self):
+        self.assertTrue(issubclass(sqlite.InternalError, sqlite.DatabaseError),
+                        "InternalError is not a subclass of DatabaseError")
+
+    def CheckProgrammingError(self):
+        self.assertTrue(issubclass(sqlite.ProgrammingError, sqlite.DatabaseError),
+                        "ProgrammingError is not a subclass of DatabaseError")
+
+    def CheckNotSupportedError(self):
+        self.assertTrue(issubclass(sqlite.NotSupportedError,
+                                   sqlite.DatabaseError),
+                        "NotSupportedError is not a subclass of DatabaseError")
+
+class ConnectionTests(unittest.TestCase):
+    def setUp(self):
+        self.cx = sqlite.connect(":memory:")
+        cu = self.cx.cursor()
+        cu.execute("create table test(id integer primary key, name text)")
+        cu.execute("insert into test(name) values (?)", ("foo",))
+
+    def tearDown(self):
+        self.cx.close()
+
+    def CheckCommit(self):
+        self.cx.commit()
+
+    def CheckCommitAfterNoChanges(self):
+        """
+        A commit should also work when no changes were made to the database.
+        """
+        self.cx.commit()
+        self.cx.commit()
+
+    def CheckRollback(self):
+        self.cx.rollback()
+
+    def CheckRollbackAfterNoChanges(self):
+        """
+        A rollback should also work when no changes were made to the database.
+        """
+        self.cx.rollback()
+        self.cx.rollback()
+
+    def CheckCursor(self):
+        cu = self.cx.cursor()
+
+    def CheckFailedOpen(self):
+        YOU_CANNOT_OPEN_THIS = "/foo/bar/bla/23534/mydb.db"
+        try:
+            con = sqlite.connect(YOU_CANNOT_OPEN_THIS)
+        except sqlite.OperationalError:
+            return
+        self.fail("should have raised an OperationalError")
+
+    def CheckClose(self):
+        self.cx.close()
+
+    def CheckExceptions(self):
+        # Optional DB-API extension.
+        self.assertEqual(self.cx.Warning, sqlite.Warning)
+        self.assertEqual(self.cx.Error, sqlite.Error)
+        self.assertEqual(self.cx.InterfaceError, sqlite.InterfaceError)
+        self.assertEqual(self.cx.DatabaseError, sqlite.DatabaseError)
+        self.assertEqual(self.cx.DataError, sqlite.DataError)
+        self.assertEqual(self.cx.OperationalError, sqlite.OperationalError)
+        self.assertEqual(self.cx.IntegrityError, sqlite.IntegrityError)
+        self.assertEqual(self.cx.InternalError, sqlite.InternalError)
+        self.assertEqual(self.cx.ProgrammingError, sqlite.ProgrammingError)
+        self.assertEqual(self.cx.NotSupportedError, sqlite.NotSupportedError)
+
+class CursorTests(unittest.TestCase):
+    def setUp(self):
+        self.cx = sqlite.connect(":memory:")
+        self.cu = self.cx.cursor()
+        self.cu.execute("create table test(id integer primary key, name text, income number)")
+        self.cu.execute("insert into test(name) values (?)", ("foo",))
+
+    def tearDown(self):
+        self.cu.close()
+        self.cx.close()
+
+    def CheckExecuteNoArgs(self):
+        self.cu.execute("delete from test")
+
+    def CheckExecuteIllegalSql(self):
+        try:
+            self.cu.execute("select asdf")
+            self.fail("should have raised an OperationalError")
+        except sqlite.OperationalError:
+            return
+        except:
+            self.fail("raised wrong exception")
+
+    def CheckExecuteTooMuchSql(self):
+        try:
+            self.cu.execute("select 5+4; select 4+5")
+            self.fail("should have raised a ProgrammingError")
+        except sqlite.ProgrammingError:
+            return
+        except:
+            self.fail("raised wrong exception")
+
+    def CheckExecuteTooMuchSql2(self):
+        self.cu.execute("select 5+4; -- foo bar")
+
+    def CheckExecuteTooMuchSql3(self):
+        self.cu.execute("""
+            select 5+4;
+
+            /*
+            foo
+            */
+            """)
+
+    def CheckExecuteWrongSqlArg(self):
+        try:
+            self.cu.execute(42)
+            self.fail("should have raised a ValueError")
+        except ValueError:
+            return
+        except:
+            self.fail("raised wrong exception.")
+
+    def CheckExecuteArgInt(self):
+        self.cu.execute("insert into test(id) values (?)", (42,))
+
+    def CheckExecuteArgFloat(self):
+        self.cu.execute("insert into test(income) values (?)", (2500.32,))
+
+    def CheckExecuteArgString(self):
+        self.cu.execute("insert into test(name) values (?)", ("Hugo",))
+
+    def CheckExecuteWrongNoOfArgs1(self):
+        # too many parameters
+        try:
+            self.cu.execute("insert into test(id) values (?)", (17, "Egon"))
+            self.fail("should have raised ProgrammingError")
+        except sqlite.ProgrammingError:
+            pass
+
+    def CheckExecuteWrongNoOfArgs2(self):
+        # too little parameters
+        try:
+            self.cu.execute("insert into test(id) values (?)")
+            self.fail("should have raised ProgrammingError")
+        except sqlite.ProgrammingError:
+            pass
+
+    def CheckExecuteWrongNoOfArgs3(self):
+        # no parameters, parameters are needed
+        try:
+            self.cu.execute("insert into test(id) values (?)")
+            self.fail("should have raised ProgrammingError")
+        except sqlite.ProgrammingError:
+            pass
+
+    def CheckExecuteParamList(self):
+        self.cu.execute("insert into test(name) values ('foo')")
+        self.cu.execute("select name from test where name=?", ["foo"])
+        row = self.cu.fetchone()
+        self.assertEqual(row[0], "foo")
+
+    def CheckExecuteParamSequence(self):
+        class L(object):
+            def __len__(self):
+                return 1
+            def __getitem__(self, x):
+                assert x == 0
+                return "foo"
+
+        self.cu.execute("insert into test(name) values ('foo')")
+        self.cu.execute("select name from test where name=?", L())
+        row = self.cu.fetchone()
+        self.assertEqual(row[0], "foo")
+
+    def CheckExecuteDictMapping(self):
+        self.cu.execute("insert into test(name) values ('foo')")
+        self.cu.execute("select name from test where name=:name", {"name": "foo"})
+        row = self.cu.fetchone()
+        self.assertEqual(row[0], "foo")
+
+    def CheckExecuteDictMapping_Mapping(self):
+        # Test only works with Python 2.5 or later
+        if sys.version_info < (2, 5, 0):
+            return
+
+        class D(dict):
+            def __missing__(self, key):
+                return "foo"
+
+        self.cu.execute("insert into test(name) values ('foo')")
+        self.cu.execute("select name from test where name=:name", D())
+        row = self.cu.fetchone()
+        self.assertEqual(row[0], "foo")
+
+    def CheckExecuteDictMappingTooLittleArgs(self):
+        self.cu.execute("insert into test(name) values ('foo')")
+        try:
+            self.cu.execute("select name from test where name=:name and id=:id", {"name": "foo"})
+            self.fail("should have raised ProgrammingError")
+        except sqlite.ProgrammingError:
+            pass
+
+    def CheckExecuteDictMappingNoArgs(self):
+        self.cu.execute("insert into test(name) values ('foo')")
+        try:
+            self.cu.execute("select name from test where name=:name")
+            self.fail("should have raised ProgrammingError")
+        except sqlite.ProgrammingError:
+            pass
+
+    def CheckExecuteDictMappingUnnamed(self):
+        self.cu.execute("insert into test(name) values ('foo')")
+        try:
+            self.cu.execute("select name from test where name=?", {"name": "foo"})
+            self.fail("should have raised ProgrammingError")
+        except sqlite.ProgrammingError:
+            pass
+
+    def CheckClose(self):
+        self.cu.close()
+
+    def CheckRowcountExecute(self):
+        self.cu.execute("delete from test")
+        self.cu.execute("insert into test(name) values ('foo')")
+        self.cu.execute("insert into test(name) values ('foo')")
+        self.cu.execute("update test set name='bar'")
+        self.assertEqual(self.cu.rowcount, 2)
+
+    def CheckRowcountSelect(self):
+        """
+        pysqlite does not know the rowcount of SELECT statements, because we
+        don't fetch all rows after executing the select statement. The rowcount
+        has thus to be -1.
+        """
+        self.cu.execute("select 5 union select 6")
+        self.assertEqual(self.cu.rowcount, -1)
+
+    def CheckRowcountExecutemany(self):
+        self.cu.execute("delete from test")
+        self.cu.executemany("insert into test(name) values (?)", [(1,), (2,), (3,)])
+        self.assertEqual(self.cu.rowcount, 3)
+
+    def CheckTotalChanges(self):
+        self.cu.execute("insert into test(name) values ('foo')")
+        self.cu.execute("insert into test(name) values ('foo')")
+        if self.cx.total_changes < 2:
+            self.fail("total changes reported wrong value")
+
+    # Checks for executemany:
+    # Sequences are required by the DB-API, iterators
+    # enhancements in pysqlite.
+
+    def CheckExecuteManySequence(self):
+        self.cu.executemany("insert into test(income) values (?)", [(x,) for x in range(100, 110)])
+
+    def CheckExecuteManyIterator(self):
+        class MyIter:
+            def __init__(self):
+                self.value = 5
+
+            def next(self):
+                if self.value == 10:
+                    raise StopIteration
+                else:
+                    self.value += 1
+                    return (self.value,)
+
+        self.cu.executemany("insert into test(income) values (?)", MyIter())
+
+    def CheckExecuteManyGenerator(self):
+        def mygen():
+            for i in range(5):
+                yield (i,)
+
+        self.cu.executemany("insert into test(income) values (?)", mygen())
+
+    def CheckExecuteManyWrongSqlArg(self):
+        try:
+            self.cu.executemany(42, [(3,)])
+            self.fail("should have raised a ValueError")
+        except ValueError:
+            return
+        except:
+            self.fail("raised wrong exception.")
+
+    def CheckExecuteManySelect(self):
+        try:
+            self.cu.executemany("select ?", [(3,)])
+            self.fail("should have raised a ProgrammingError")
+        except sqlite.ProgrammingError:
+            return
+        except:
+            self.fail("raised wrong exception.")
+
+    def CheckExecuteManyNotIterable(self):
+        try:
+            self.cu.executemany("insert into test(income) values (?)", 42)
+            self.fail("should have raised a TypeError")
+        except TypeError:
+            return
+        except Exception, e:
+            print "raised", e.__class__
+            self.fail("raised wrong exception.")
+
+    def CheckFetchIter(self):
+        # Optional DB-API extension.
+        self.cu.execute("delete from test")
+        self.cu.execute("insert into test(id) values (?)", (5,))
+        self.cu.execute("insert into test(id) values (?)", (6,))
+        self.cu.execute("select id from test order by id")
+        lst = []
+        for row in self.cu:
+            lst.append(row[0])
+        self.assertEqual(lst[0], 5)
+        self.assertEqual(lst[1], 6)
+
+    def CheckFetchone(self):
+        self.cu.execute("select name from test")
+        row = self.cu.fetchone()
+        self.assertEqual(row[0], "foo")
+        row = self.cu.fetchone()
+        self.assertEqual(row, None)
+
+    def CheckFetchoneNoStatement(self):
+        cur = self.cx.cursor()
+        row = cur.fetchone()
+        self.assertEqual(row, None)
+
+    def CheckArraySize(self):
+        # must default ot 1
+        self.assertEqual(self.cu.arraysize, 1)
+
+        # now set to 2
+        self.cu.arraysize = 2
+
+        # now make the query return 3 rows
+        self.cu.execute("delete from test")
+        self.cu.execute("insert into test(name) values ('A')")
+        self.cu.execute("insert into test(name) values ('B')")
+        self.cu.execute("insert into test(name) values ('C')")
+        self.cu.execute("select name from test")
+        res = self.cu.fetchmany()
+
+        self.assertEqual(len(res), 2)
+
+    def CheckFetchmany(self):
+        self.cu.execute("select name from test")
+        res = self.cu.fetchmany(100)
+        self.assertEqual(len(res), 1)
+        res = self.cu.fetchmany(100)
+        self.assertEqual(res, [])
+
+    def CheckFetchmanyKwArg(self):
+        """Checks if fetchmany works with keyword arguments"""
+        self.cu.execute("select name from test")
+        res = self.cu.fetchmany(size=100)
+        self.assertEqual(len(res), 1)
+
+    def CheckFetchall(self):
+        self.cu.execute("select name from test")
+        res = self.cu.fetchall()
+        self.assertEqual(len(res), 1)
+        res = self.cu.fetchall()
+        self.assertEqual(res, [])
+
+    def CheckSetinputsizes(self):
+        self.cu.setinputsizes([3, 4, 5])
+
+    def CheckSetoutputsize(self):
+        self.cu.setoutputsize(5, 0)
+
+    def CheckSetoutputsizeNoColumn(self):
+        self.cu.setoutputsize(42)
+
+    def CheckCursorConnection(self):
+        # Optional DB-API extension.
+        self.assertEqual(self.cu.connection, self.cx)
+
+    def CheckWrongCursorCallable(self):
+        try:
+            def f(): pass
+            cur = self.cx.cursor(f)
+            self.fail("should have raised a TypeError")
+        except TypeError:
+            return
+        self.fail("should have raised a ValueError")
+
+    def CheckCursorWrongClass(self):
+        class Foo: pass
+        foo = Foo()
+        try:
+            cur = sqlite.Cursor(foo)
+            self.fail("should have raised a ValueError")
+        except TypeError:
+            pass
+
+class ThreadTests(unittest.TestCase):
+    def setUp(self):
+        self.con = sqlite.connect(":memory:")
+        self.cur = self.con.cursor()
+        self.cur.execute("create table test(id integer primary key, name text, bin binary, ratio number, ts timestamp)")
+
+    def tearDown(self):
+        self.cur.close()
+        self.con.close()
+
+    def CheckConCursor(self):
+        def run(con, errors):
+            try:
+                cur = con.cursor()
+                errors.append("did not raise ProgrammingError")
+                return
+            except sqlite.ProgrammingError:
+                return
+            except:
+                errors.append("raised wrong exception")
+
+        errors = []
+        t = threading.Thread(target=run, kwargs={"con": self.con, "errors": errors})
+        t.start()
+        t.join()
+        if len(errors) > 0:
+            self.fail("\n".join(errors))
+
+    def CheckConCommit(self):
+        def run(con, errors):
+            try:
+                con.commit()
+                errors.append("did not raise ProgrammingError")
+                return
+            except sqlite.ProgrammingError:
+                return
+            except:
+                errors.append("raised wrong exception")
+
+        errors = []
+        t = threading.Thread(target=run, kwargs={"con": self.con, "errors": errors})
+        t.start()
+        t.join()
+        if len(errors) > 0:
+            self.fail("\n".join(errors))
+
+    def CheckConRollback(self):
+        def run(con, errors):
+            try:
+                con.rollback()
+                errors.append("did not raise ProgrammingError")
+                return
+            except sqlite.ProgrammingError:
+                return
+            except:
+                errors.append("raised wrong exception")
+
+        errors = []
+        t = threading.Thread(target=run, kwargs={"con": self.con, "errors": errors})
+        t.start()
+        t.join()
+        if len(errors) > 0:
+            self.fail("\n".join(errors))
+
+    def CheckConClose(self):
+        def run(con, errors):
+            try:
+                con.close()
+                errors.append("did not raise ProgrammingError")
+                return
+            except sqlite.ProgrammingError:
+                return
+            except:
+                errors.append("raised wrong exception")
+
+        errors = []
+        t = threading.Thread(target=run, kwargs={"con": self.con, "errors": errors})
+        t.start()
+        t.join()
+        if len(errors) > 0:
+            self.fail("\n".join(errors))
+
+    def CheckCurImplicitBegin(self):
+        def run(cur, errors):
+            try:
+                cur.execute("insert into test(name) values ('a')")
+                errors.append("did not raise ProgrammingError")
+                return
+            except sqlite.ProgrammingError:
+                return
+            except:
+                errors.append("raised wrong exception")
+
+        errors = []
+        t = threading.Thread(target=run, kwargs={"cur": self.cur, "errors": errors})
+        t.start()
+        t.join()
+        if len(errors) > 0:
+            self.fail("\n".join(errors))
+
+    def CheckCurClose(self):
+        def run(cur, errors):
+            try:
+                cur.close()
+                errors.append("did not raise ProgrammingError")
+                return
+            except sqlite.ProgrammingError:
+                return
+            except:
+                errors.append("raised wrong exception")
+
+        errors = []
+        t = threading.Thread(target=run, kwargs={"cur": self.cur, "errors": errors})
+        t.start()
+        t.join()
+        if len(errors) > 0:
+            self.fail("\n".join(errors))
+
+    def CheckCurExecute(self):
+        def run(cur, errors):
+            try:
+                cur.execute("select name from test")
+                errors.append("did not raise ProgrammingError")
+                return
+            except sqlite.ProgrammingError:
+                return
+            except:
+                errors.append("raised wrong exception")
+
+        errors = []
+        self.cur.execute("insert into test(name) values ('a')")
+        t = threading.Thread(target=run, kwargs={"cur": self.cur, "errors": errors})
+        t.start()
+        t.join()
+        if len(errors) > 0:
+            self.fail("\n".join(errors))
+
+    def CheckCurIterNext(self):
+        def run(cur, errors):
+            try:
+                row = cur.fetchone()
+                errors.append("did not raise ProgrammingError")
+                return
+            except sqlite.ProgrammingError:
+                return
+            except:
+                errors.append("raised wrong exception")
+
+        errors = []
+        self.cur.execute("insert into test(name) values ('a')")
+        self.cur.execute("select name from test")
+        t = threading.Thread(target=run, kwargs={"cur": self.cur, "errors": errors})
+        t.start()
+        t.join()
+        if len(errors) > 0:
+            self.fail("\n".join(errors))
+
+class ConstructorTests(unittest.TestCase):
+    def CheckDate(self):
+        d = sqlite.Date(2004, 10, 28)
+
+    def CheckTime(self):
+        t = sqlite.Time(12, 39, 35)
+
+    def CheckTimestamp(self):
+        ts = sqlite.Timestamp(2004, 10, 28, 12, 39, 35)
+
+    def CheckDateFromTicks(self):
+        d = sqlite.DateFromTicks(42)
+
+    def CheckTimeFromTicks(self):
+        t = sqlite.TimeFromTicks(42)
+
+    def CheckTimestampFromTicks(self):
+        ts = sqlite.TimestampFromTicks(42)
+
+    def CheckBinary(self):
+        b = sqlite.Binary(chr(0) + "'")
+
+class ExtensionTests(unittest.TestCase):
+    def CheckScriptStringSql(self):
+        con = sqlite.connect(":memory:")
+        cur = con.cursor()
+        cur.executescript("""
+            -- bla bla
+            /* a stupid comment */
+            create table a(i);
+            insert into a(i) values (5);
+            """)
+        cur.execute("select i from a")
+        res = cur.fetchone()[0]
+        self.assertEqual(res, 5)
+
+    def CheckScriptStringUnicode(self):
+        con = sqlite.connect(":memory:")
+        cur = con.cursor()
+        cur.executescript(u"""
+            create table a(i);
+            insert into a(i) values (5);
+            select i from a;
+            delete from a;
+            insert into a(i) values (6);
+            """)
+        cur.execute("select i from a")
+        res = cur.fetchone()[0]
+        self.assertEqual(res, 6)
+
+    def CheckScriptSyntaxError(self):
+        con = sqlite.connect(":memory:")
+        cur = con.cursor()
+        raised = False
+        try:
+            cur.executescript("create table test(x); asdf; create table test2(x)")
+        except sqlite.OperationalError:
+            raised = True
+        self.assertEqual(raised, True, "should have raised an exception")
+
+    def CheckScriptErrorNormal(self):
+        con = sqlite.connect(":memory:")
+        cur = con.cursor()
+        raised = False
+        try:
+            cur.executescript("create table test(sadfsadfdsa); select foo from hurz;")
+        except sqlite.OperationalError:
+            raised = True
+        self.assertEqual(raised, True, "should have raised an exception")
+
+    def CheckConnectionExecute(self):
+        con = sqlite.connect(":memory:")
+        result = con.execute("select 5").fetchone()[0]
+        self.assertEqual(result, 5, "Basic test of Connection.execute")
+
+    def CheckConnectionExecutemany(self):
+        con = sqlite.connect(":memory:")
+        con.execute("create table test(foo)")
+        con.executemany("insert into test(foo) values (?)", [(3,), (4,)])
+        result = con.execute("select foo from test order by foo").fetchall()
+        self.assertEqual(result[0][0], 3, "Basic test of Connection.executemany")
+        self.assertEqual(result[1][0], 4, "Basic test of Connection.executemany")
+
+    def CheckConnectionExecutescript(self):
+        con = sqlite.connect(":memory:")
+        con.executescript("create table test(foo); insert into test(foo) values (5);")
+        result = con.execute("select foo from test").fetchone()[0]
+        self.assertEqual(result, 5, "Basic test of Connection.executescript")
+
+class ClosedConTests(unittest.TestCase):
+    def setUp(self):
+        pass
+
+    def tearDown(self):
+        pass
+
+    def CheckClosedConCursor(self):
+        con = sqlite.connect(":memory:")
+        con.close()
+        try:
+            cur = con.cursor()
+            self.fail("Should have raised a ProgrammingError")
+        except sqlite.ProgrammingError:
+            pass
+        except:
+            self.fail("Should have raised a ProgrammingError")
+
+    def CheckClosedConCommit(self):
+        con = sqlite.connect(":memory:")
+        con.close()
+        try:
+            con.commit()
+            self.fail("Should have raised a ProgrammingError")
+        except sqlite.ProgrammingError:
+            pass
+        except:
+            self.fail("Should have raised a ProgrammingError")
+
+    def CheckClosedConRollback(self):
+        con = sqlite.connect(":memory:")
+        con.close()
+        try:
+            con.rollback()
+            self.fail("Should have raised a ProgrammingError")
+        except sqlite.ProgrammingError:
+            pass
+        except:
+            self.fail("Should have raised a ProgrammingError")
+
+    def CheckClosedCurExecute(self):
+        con = sqlite.connect(":memory:")
+        cur = con.cursor()
+        con.close()
+        try:
+            cur.execute("select 4")
+            self.fail("Should have raised a ProgrammingError")
+        except sqlite.ProgrammingError:
+            pass
+        except:
+            self.fail("Should have raised a ProgrammingError")
+
+    def CheckClosedCreateFunction(self):
+        con = sqlite.connect(":memory:")
+        con.close()
+        def f(x): return 17
+        try:
+            con.create_function("foo", 1, f)
+            self.fail("Should have raised a ProgrammingError")
+        except sqlite.ProgrammingError:
+            pass
+        except:
+            self.fail("Should have raised a ProgrammingError")
+
+    def CheckClosedCreateAggregate(self):
+        con = sqlite.connect(":memory:")
+        con.close()
+        class Agg:
+            def __init__(self):
+                pass
+            def step(self, x):
+                pass
+            def finalize(self):
+                return 17
+        try:
+            con.create_aggregate("foo", 1, Agg)
+            self.fail("Should have raised a ProgrammingError")
+        except sqlite.ProgrammingError:
+            pass
+        except:
+            self.fail("Should have raised a ProgrammingError")
+
+    def CheckClosedSetAuthorizer(self):
+        con = sqlite.connect(":memory:")
+        con.close()
+        def authorizer(*args):
+            return sqlite.DENY
+        try:
+            con.set_authorizer(authorizer)
+            self.fail("Should have raised a ProgrammingError")
+        except sqlite.ProgrammingError:
+            pass
+        except:
+            self.fail("Should have raised a ProgrammingError")
+
+    def CheckClosedSetProgressCallback(self):
+        con = sqlite.connect(":memory:")
+        con.close()
+        def progress(): pass
+        try:
+            con.set_progress_handler(progress, 100)
+            self.fail("Should have raised a ProgrammingError")
+        except sqlite.ProgrammingError:
+            pass
+        except:
+            self.fail("Should have raised a ProgrammingError")
+
+    def CheckClosedCall(self):
+        con = sqlite.connect(":memory:")
+        con.close()
+        try:
+            con()
+            self.fail("Should have raised a ProgrammingError")
+        except sqlite.ProgrammingError:
+            pass
+        except:
+            self.fail("Should have raised a ProgrammingError")
+
+class ClosedCurTests(unittest.TestCase):
+    def setUp(self):
+        pass
+
+    def tearDown(self):
+        pass
+
+    def CheckClosed(self):
+        con = sqlite.connect(":memory:")
+        cur = con.cursor()
+        cur.close()
+
+        for method_name in ("execute", "executemany", "executescript", "fetchall", "fetchmany", "fetchone"):
+            if method_name in ("execute", "executescript"):
+                params = ("select 4 union select 5",)
+            elif method_name == "executemany":
+                params = ("insert into foo(bar) values (?)", [(3,), (4,)])
+            else:
+                params = []
+
+            try:
+                method = getattr(cur, method_name)
+
+                method(*params)
+                self.fail("Should have raised a ProgrammingError: method " + method_name)
+            except sqlite.ProgrammingError:
+                pass
+            except:
+                self.fail("Should have raised a ProgrammingError: " + method_name)
+
+def suite():
+    module_suite = unittest.makeSuite(ModuleTests, "Check")
+    connection_suite = unittest.makeSuite(ConnectionTests, "Check")
+    cursor_suite = unittest.makeSuite(CursorTests, "Check")
+    thread_suite = unittest.makeSuite(ThreadTests, "Check")
+    constructor_suite = unittest.makeSuite(ConstructorTests, "Check")
+    ext_suite = unittest.makeSuite(ExtensionTests, "Check")
+    closed_con_suite = unittest.makeSuite(ClosedConTests, "Check")
+    closed_cur_suite = unittest.makeSuite(ClosedCurTests, "Check")
+    return unittest.TestSuite((module_suite, connection_suite, cursor_suite, thread_suite, constructor_suite, ext_suite, closed_con_suite, closed_cur_suite))
+
+def test():
+    runner = unittest.TextTestRunner()
+    runner.run(suite())
+
+if __name__ == "__main__":
+    test()
diff --git a/lib/test/dump.py b/lib/test/dump.py
new file mode 100644 (file)
index 0000000..b2f2d26
--- /dev/null
@@ -0,0 +1,52 @@
+# Author: Paul Kippes <kippesp@gmail.com>
+
+import unittest
+from pysqlcipher import dbapi2 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()
diff --git a/lib/test/factory.py b/lib/test/factory.py
new file mode 100644 (file)
index 0000000..a314d99
--- /dev/null
@@ -0,0 +1,205 @@
+#-*- coding: ISO-8859-1 -*-
+# pysqlite2/test/factory.py: tests for the various factories in pysqlite
+#
+# Copyright (C) 2005-2007 Gerhard Häring <gh@ghaering.de>
+#
+# This file is part of pysqlite.
+#
+# This software is provided 'as-is', without any express or implied
+# warranty.  In no event will the authors be held liable for any damages
+# arising from the use of this software.
+#
+# Permission is granted to anyone to use this software for any purpose,
+# including commercial applications, and to alter it and redistribute it
+# freely, subject to the following restrictions:
+#
+# 1. The origin of this software must not be misrepresented; you must not
+#    claim that you wrote the original software. If you use this software
+#    in a product, an acknowledgment in the product documentation would be
+#    appreciated but is not required.
+# 2. Altered source versions must be plainly marked as such, and must not be
+#    misrepresented as being the original software.
+# 3. This notice may not be removed or altered from any source distribution.
+
+import unittest
+import pysqlcipher.dbapi2 as sqlite
+
+class MyConnection(sqlite.Connection):
+    def __init__(self, *args, **kwargs):
+        sqlite.Connection.__init__(self, *args, **kwargs)
+
+def dict_factory(cursor, row):
+    d = {}
+    for idx, col in enumerate(cursor.description):
+        d[col[0]] = row[idx]
+    return d
+
+class MyCursor(sqlite.Cursor):
+    def __init__(self, *args, **kwargs):
+        sqlite.Cursor.__init__(self, *args, **kwargs)
+        self.row_factory = dict_factory
+
+class ConnectionFactoryTests(unittest.TestCase):
+    def setUp(self):
+        self.con = sqlite.connect(":memory:", factory=MyConnection)
+
+    def tearDown(self):
+        self.con.close()
+
+    def CheckIsInstance(self):
+        self.assertTrue(isinstance(self.con,
+                                   MyConnection),
+                        "connection is not instance of MyConnection")
+
+class CursorFactoryTests(unittest.TestCase):
+    def setUp(self):
+        self.con = sqlite.connect(":memory:")
+
+    def tearDown(self):
+        self.con.close()
+
+    def CheckIsInstance(self):
+        cur = self.con.cursor(factory=MyCursor)
+        self.assertTrue(isinstance(cur,
+                                   MyCursor),
+                        "cursor is not instance of MyCursor")
+
+class RowFactoryTestsBackwardsCompat(unittest.TestCase):
+    def setUp(self):
+        self.con = sqlite.connect(":memory:")
+
+    def CheckIsProducedByFactory(self):
+        cur = self.con.cursor(factory=MyCursor)
+        cur.execute("select 4+5 as foo")
+        row = cur.fetchone()
+        self.assertTrue(isinstance(row,
+                                   dict),
+                        "row is not instance of dict")
+        cur.close()
+
+    def tearDown(self):
+        self.con.close()
+
+class RowFactoryTests(unittest.TestCase):
+    def setUp(self):
+        self.con = sqlite.connect(":memory:")
+
+    def CheckCustomFactory(self):
+        self.con.row_factory = lambda cur, row: list(row)
+        row = self.con.execute("select 1, 2").fetchone()
+        self.assertTrue(isinstance(row,
+                                   list),
+                        "row is not instance of list")
+
+    def CheckSqliteRowIndex(self):
+        self.con.row_factory = sqlite.Row
+        row = self.con.execute("select 1 as a, 2 as b").fetchone()
+        self.assertTrue(isinstance(row,
+                                   sqlite.Row),
+                        "row is not instance of sqlite.Row")
+
+        col1, col2 = row["a"], row["b"]
+        self.assertTrue(col1 == 1, "by name: wrong result for column 'a'")
+        self.assertTrue(col2 == 2, "by name: wrong result for column 'a'")
+
+        col1, col2 = row["A"], row["B"]
+        self.assertTrue(col1 == 1, "by name: wrong result for column 'A'")
+        self.assertTrue(col2 == 2, "by name: wrong result for column 'B'")
+
+        col1, col2 = row[0], row[1]
+        self.assertTrue(col1 == 1, "by index: wrong result for column 0")
+        self.assertTrue(col2 == 2, "by index: wrong result for column 1")
+
+    def CheckSqliteRowIter(self):
+        """Checks if the row object is iterable"""
+        self.con.row_factory = sqlite.Row
+        row = self.con.execute("select 1 as a, 2 as b").fetchone()
+        for col in row:
+            pass
+
+    def CheckSqliteRowAsTuple(self):
+        """Checks if the row object can be converted to a tuple"""
+        self.con.row_factory = sqlite.Row
+        row = self.con.execute("select 1 as a, 2 as b").fetchone()
+        t = tuple(row)
+
+    def CheckSqliteRowAsDict(self):
+        """Checks if the row object can be correctly converted to a dictionary"""
+        self.con.row_factory = sqlite.Row
+        row = self.con.execute("select 1 as a, 2 as b").fetchone()
+        d = dict(row)
+        self.assertEqual(d["a"], row["a"])
+        self.assertEqual(d["b"], row["b"])
+
+    def CheckSqliteRowHashCmp(self):
+        """Checks if the row object compares and hashes correctly"""
+        self.con.row_factory = sqlite.Row
+        row_1 = self.con.execute("select 1 as a, 2 as b").fetchone()
+        row_2 = self.con.execute("select 1 as a, 2 as b").fetchone()
+        row_3 = self.con.execute("select 1 as a, 3 as b").fetchone()
+
+        self.assertTrue(row_1 == row_1)
+        self.assertTrue(row_1 == row_2)
+        self.assertTrue(row_2 != row_3)
+
+        self.assertFalse(row_1 != row_1)
+        self.assertFalse(row_1 != row_2)
+        self.assertFalse(row_2 == row_3)
+
+        self.assertEqual(row_1, row_2)
+        self.assertEqual(hash(row_1), hash(row_2))
+        self.assertNotEqual(row_1, row_3)
+        self.assertNotEqual(hash(row_1), hash(row_3))
+
+    def tearDown(self):
+        self.con.close()
+
+class TextFactoryTests(unittest.TestCase):
+    def setUp(self):
+        self.con = sqlite.connect(":memory:")
+
+    def CheckUnicode(self):
+        austria = unicode("Österreich", "latin1")
+        row = self.con.execute("select ?", (austria,)).fetchone()
+        self.assertTrue(type(row[0]) == unicode, "type of row[0] must be unicode")
+
+    def CheckString(self):
+        self.con.text_factory = str
+        austria = unicode("Österreich", "latin1")
+        row = self.con.execute("select ?", (austria,)).fetchone()
+        self.assertTrue(type(row[0]) == str, "type of row[0] must be str")
+        self.assertTrue(row[0] == austria.encode("utf-8"), "column must equal original data in UTF-8")
+
+    def CheckCustom(self):
+        self.con.text_factory = lambda x: unicode(x, "utf-8", "ignore")
+        austria = unicode("Österreich", "latin1")
+        row = self.con.execute("select ?", (austria.encode("latin1"),)).fetchone()
+        self.assertTrue(type(row[0]) == unicode, "type of row[0] must be unicode")
+        self.assertTrue(row[0].endswith(u"reich"), "column must contain original data")
+
+    def CheckOptimizedUnicode(self):
+        self.con.text_factory = sqlite.OptimizedUnicode
+        austria = unicode("Österreich", "latin1")
+        germany = unicode("Deutchland")
+        a_row = self.con.execute("select ?", (austria,)).fetchone()
+        d_row = self.con.execute("select ?", (germany,)).fetchone()
+        self.assertTrue(type(a_row[0]) == unicode, "type of non-ASCII row must be unicode")
+        self.assertTrue(type(d_row[0]) == str, "type of ASCII-only row must be str")
+
+    def tearDown(self):
+        self.con.close()
+
+def suite():
+    connection_suite = unittest.makeSuite(ConnectionFactoryTests, "Check")
+    cursor_suite = unittest.makeSuite(CursorFactoryTests, "Check")
+    row_suite_compat = unittest.makeSuite(RowFactoryTestsBackwardsCompat, "Check")
+    row_suite = unittest.makeSuite(RowFactoryTests, "Check")
+    text_suite = unittest.makeSuite(TextFactoryTests, "Check")
+    return unittest.TestSuite((connection_suite, cursor_suite, row_suite_compat, row_suite, text_suite))
+
+def test():
+    runner = unittest.TextTestRunner()
+    runner.run(suite())
+
+if __name__ == "__main__":
+    test()
diff --git a/lib/test/hooks.py b/lib/test/hooks.py
new file mode 100644 (file)
index 0000000..5a38060
--- /dev/null
@@ -0,0 +1,188 @@
+#-*- coding: ISO-8859-1 -*-
+# pysqlite2/test/hooks.py: tests for various SQLite-specific hooks
+#
+# Copyright (C) 2006-2007 Gerhard Häring <gh@ghaering.de>
+#
+# This file is part of pysqlite.
+#
+# This software is provided 'as-is', without any express or implied
+# warranty.  In no event will the authors be held liable for any damages
+# arising from the use of this software.
+#
+# Permission is granted to anyone to use this software for any purpose,
+# including commercial applications, and to alter it and redistribute it
+# freely, subject to the following restrictions:
+#
+# 1. The origin of this software must not be misrepresented; you must not
+#    claim that you wrote the original software. If you use this software
+#    in a product, an acknowledgment in the product documentation would be
+#    appreciated but is not required.
+# 2. Altered source versions must be plainly marked as such, and must not be
+#    misrepresented as being the original software.
+# 3. This notice may not be removed or altered from any source distribution.
+
+import os, unittest
+import pysqlcipher.dbapi2 as sqlite
+
+class CollationTests(unittest.TestCase):
+    def setUp(self):
+        pass
+
+    def tearDown(self):
+        pass
+
+    def CheckCreateCollationNotCallable(self):
+        con = sqlite.connect(":memory:")
+        try:
+            con.create_collation("X", 42)
+            self.fail("should have raised a TypeError")
+        except TypeError, e:
+            self.assertEqual(e.args[0], "parameter must be callable")
+
+    def CheckCreateCollationNotAscii(self):
+        con = sqlite.connect(":memory:")
+        try:
+            con.create_collation("collä", cmp)
+            self.fail("should have raised a ProgrammingError")
+        except sqlite.ProgrammingError, e:
+            pass
+
+    def CheckCollationIsUsed(self):
+        if sqlite.version_info < (3, 2, 1):  # old SQLite versions crash on this test
+            return
+        def mycoll(x, y):
+            # reverse order
+            return -cmp(x, y)
+
+        con = sqlite.connect(":memory:")
+        con.create_collation("mycoll", mycoll)
+        sql = """
+            select x from (
+            select 'a' as x
+            union
+            select 'b' as x
+            union
+            select 'c' as x
+            ) order by x collate mycoll
+            """
+        result = con.execute(sql).fetchall()
+        if result[0][0] != "c" or result[1][0] != "b" or result[2][0] != "a":
+            self.fail("the expected order was not returned")
+
+        con.create_collation("mycoll", None)
+        try:
+            result = con.execute(sql).fetchall()
+            self.fail("should have raised an OperationalError")
+        except sqlite.OperationalError, e:
+            self.assertEqual(e.args[0].lower(), "no such collation sequence: mycoll")
+
+    def CheckCollationRegisterTwice(self):
+        """
+        Register two different collation functions under the same name.
+        Verify that the last one is actually used.
+        """
+        con = sqlite.connect(":memory:")
+        con.create_collation("mycoll", cmp)
+        con.create_collation("mycoll", lambda x, y: -cmp(x, y))
+        result = con.execute("""
+            select x from (select 'a' as x union select 'b' as x) order by x collate mycoll
+            """).fetchall()
+        if result[0][0] != 'b' or result[1][0] != 'a':
+            self.fail("wrong collation function is used")
+
+    def CheckDeregisterCollation(self):
+        """
+        Register a collation, then deregister it. Make sure an error is raised if we try
+        to use it.
+        """
+        con = sqlite.connect(":memory:")
+        con.create_collation("mycoll", cmp)
+        con.create_collation("mycoll", None)
+        try:
+            con.execute("select 'a' as x union select 'b' as x order by x collate mycoll")
+            self.fail("should have raised an OperationalError")
+        except sqlite.OperationalError, e:
+            if not e.args[0].startswith("no such collation sequence"):
+                self.fail("wrong OperationalError raised")
+
+class ProgressTests(unittest.TestCase):
+    def CheckProgressHandlerUsed(self):
+        """
+        Test that the progress handler is invoked once it is set.
+        """
+        con = sqlite.connect(":memory:")
+        progress_calls = []
+        def progress():
+            progress_calls.append(None)
+            return 0
+        con.set_progress_handler(progress, 1)
+        con.execute("""
+            create table foo(a, b)
+            """)
+        self.assertTrue(progress_calls)
+
+
+    def CheckOpcodeCount(self):
+        """
+        Test that the opcode argument is respected.
+        """
+        con = sqlite.connect(":memory:")
+        progress_calls = []
+        def progress():
+            progress_calls.append(None)
+            return 0
+        con.set_progress_handler(progress, 1)
+        curs = con.cursor()
+        curs.execute("""
+            create table foo (a, b)
+            """)
+        first_count = len(progress_calls)
+        progress_calls = []
+        con.set_progress_handler(progress, 2)
+        curs.execute("""
+            create table bar (a, b)
+            """)
+        second_count = len(progress_calls)
+        self.assertTrue(first_count > second_count)
+
+    def CheckCancelOperation(self):
+        """
+        Test that returning a non-zero value stops the operation in progress.
+        """
+        con = sqlite.connect(":memory:")
+        progress_calls = []
+        def progress():
+            progress_calls.append(None)
+            return 1
+        con.set_progress_handler(progress, 1)
+        curs = con.cursor()
+        self.assertRaises(
+            sqlite.OperationalError,
+            curs.execute,
+            "create table bar (a, b)")
+
+    def CheckClearHandler(self):
+        """
+        Test that setting the progress handler to None clears the previously set handler.
+        """
+        con = sqlite.connect(":memory:")
+        action = 0
+        def progress():
+            action = 1
+            return 0
+        con.set_progress_handler(progress, 1)
+        con.set_progress_handler(None, 1)
+        con.execute("select 1 union select 2 union select 3").fetchall()
+        self.assertEqual(action, 0, "progress handler was not cleared")
+
+def suite():
+    collation_suite = unittest.makeSuite(CollationTests, "Check")
+    progress_suite = unittest.makeSuite(ProgressTests, "Check")
+    return unittest.TestSuite((collation_suite, progress_suite))
+
+def test():
+    runner = unittest.TextTestRunner()
+    runner.run(suite())
+
+if __name__ == "__main__":
+    test()
diff --git a/lib/test/py25/__init__.py b/lib/test/py25/__init__.py
new file mode 100644 (file)
index 0000000..8b13789
--- /dev/null
@@ -0,0 +1 @@
+
diff --git a/lib/test/py25/py25tests.py b/lib/test/py25/py25tests.py
new file mode 100644 (file)
index 0000000..ede1279
--- /dev/null
@@ -0,0 +1,80 @@
+#-*- coding: ISO-8859-1 -*-
+# pysqlite2/test/regression.py: pysqlite regression tests
+#
+# Copyright (C) 2007 Gerhard Häring <gh@ghaering.de>
+#
+# This file is part of pysqlite.
+#
+# This software is provided 'as-is', without any express or implied
+# warranty.  In no event will the authors be held liable for any damages
+# arising from the use of this software.
+#
+# Permission is granted to anyone to use this software for any purpose,
+# including commercial applications, and to alter it and redistribute it
+# freely, subject to the following restrictions:
+#
+# 1. The origin of this software must not be misrepresented; you must not
+#    claim that you wrote the original software. If you use this software
+#    in a product, an acknowledgment in the product documentation would be
+#    appreciated but is not required.
+# 2. Altered source versions must be plainly marked as such, and must not be
+#    misrepresented as being the original software.
+# 3. This notice may not be removed or altered from any source distribution.
+
+from __future__ import with_statement
+import unittest
+import pysqlcipher.dbapi2 as sqlite
+
+did_rollback = False
+
+class MyConnection(sqlite.Connection):
+    def rollback(self):
+        global did_rollback
+        did_rollback = True
+        sqlite.Connection.rollback(self)
+
+class ContextTests(unittest.TestCase):
+    def setUp(self):
+        global did_rollback
+        self.con = sqlite.connect(":memory:", factory=MyConnection)
+        self.con.execute("create table test(c unique)")
+        did_rollback = False
+
+    def tearDown(self):
+        self.con.close()
+
+    def CheckContextManager(self):
+        """Can the connection be used as a context manager at all?"""
+        with self.con:
+            pass
+
+    def CheckContextManagerCommit(self):
+        """Is a commit called in the context manager?"""
+        with self.con:
+            self.con.execute("insert into test(c) values ('foo')")
+        self.con.rollback()
+        count = self.con.execute("select count(*) from test").fetchone()[0]
+        self.assertEqual(count, 1)
+
+    def CheckContextManagerRollback(self):
+        """Is a rollback called in the context manager?"""
+        global did_rollback
+        self.assertEqual(did_rollback, False)
+        try:
+            with self.con:
+                self.con.execute("insert into test(c) values (4)")
+                self.con.execute("insert into test(c) values (4)")
+        except sqlite.IntegrityError:
+            pass
+        self.assertEqual(did_rollback, True)
+
+def suite():
+    ctx_suite = unittest.makeSuite(ContextTests, "Check")
+    return unittest.TestSuite((ctx_suite,))
+
+def test():
+    runner = unittest.TextTestRunner()
+    runner.run(suite())
+
+if __name__ == "__main__":
+    test()
diff --git a/lib/test/regression.py b/lib/test/regression.py
new file mode 100644 (file)
index 0000000..4003742
--- /dev/null
@@ -0,0 +1,271 @@
+#-*- coding: ISO-8859-1 -*-
+# pysqlite2/test/regression.py: pysqlite regression tests
+#
+# Copyright (C) 2006-2009 Gerhard Häring <gh@ghaering.de>
+#
+# This file is part of pysqlite.
+#
+# This software is provided 'as-is', without any express or implied
+# warranty.  In no event will the authors be held liable for any damages
+# arising from the use of this software.
+#
+# Permission is granted to anyone to use this software for any purpose,
+# including commercial applications, and to alter it and redistribute it
+# freely, subject to the following restrictions:
+#
+# 1. The origin of this software must not be misrepresented; you must not
+#    claim that you wrote the original software. If you use this software
+#    in a product, an acknowledgment in the product documentation would be
+#    appreciated but is not required.
+# 2. Altered source versions must be plainly marked as such, and must not be
+#    misrepresented as being the original software.
+# 3. This notice may not be removed or altered from any source distribution.
+
+import datetime
+import unittest
+import pysqlcipher.dbapi2 as sqlite
+
+class RegressionTests(unittest.TestCase):
+    def setUp(self):
+        self.con = sqlite.connect(":memory:")
+
+    def tearDown(self):
+        self.con.close()
+
+    def CheckPragmaUserVersion(self):
+        # This used to crash pysqlite because this pragma command returns NULL for the column name
+        cur = self.con.cursor()
+        cur.execute("pragma user_version")
+
+    def CheckPragmaSchemaVersion(self):
+        # This still crashed pysqlite <= 2.2.1
+        con = sqlite.connect(":memory:", detect_types=sqlite.PARSE_COLNAMES)
+        try:
+            cur = self.con.cursor()
+            cur.execute("pragma schema_version")
+        finally:
+            cur.close()
+            con.close()
+
+    def CheckStatementReset(self):
+        # pysqlite 2.1.0 to 2.2.0 have the problem that not all statements are
+        # reset before a rollback, but only those that are still in the
+        # statement cache. The others are not accessible from the connection object.
+        con = sqlite.connect(":memory:", cached_statements=5)
+        cursors = [con.cursor() for x in xrange(5)]
+        cursors[0].execute("create table test(x)")
+        for i in range(10):
+            cursors[0].executemany("insert into test(x) values (?)", [(x,) for x in xrange(10)])
+
+        for i in range(5):
+            cursors[i].execute(" " * i + "select x from test")
+
+        con.rollback()
+
+    def CheckColumnNameWithSpaces(self):
+        cur = self.con.cursor()
+        cur.execute('select 1 as "foo bar [datetime]"')
+        self.assertEqual(cur.description[0][0], "foo bar")
+
+        cur.execute('select 1 as "foo baz"')
+        self.assertEqual(cur.description[0][0], "foo baz")
+
+    def CheckStatementFinalizationOnCloseDb(self):
+        # pysqlite versions <= 2.3.3 only finalized statements in the statement
+        # cache when closing the database. statements that were still
+        # referenced in cursors weren't closed an could provoke "
+        # "OperationalError: Unable to close due to unfinalised statements".
+        con = sqlite.connect(":memory:")
+        cursors = []
+        # default statement cache size is 100
+        for i in range(105):
+            cur = con.cursor()
+            cursors.append(cur)
+            cur.execute("select 1 x union select " + str(i))
+        con.close()
+
+    def CheckOnConflictRollback(self):
+        if sqlite.sqlite_version_info < (3, 2, 2):
+            return
+        con = sqlite.connect(":memory:")
+        con.execute("create table foo(x, unique(x) on conflict rollback)")
+        con.execute("insert into foo(x) values (1)")
+        try:
+            con.execute("insert into foo(x) values (1)")
+        except sqlite.DatabaseError:
+            pass
+        con.execute("insert into foo(x) values (2)")
+        try:
+            con.commit()
+        except sqlite.OperationalError:
+            self.fail("pysqlite knew nothing about the implicit ROLLBACK")
+
+    def CheckWorkaroundForBuggySqliteTransferBindings(self):
+        """
+        pysqlite would crash with older SQLite versions unless
+        a workaround is implemented.
+        """
+        self.con.execute("create table foo(bar)")
+        self.con.execute("drop table foo")
+        self.con.execute("create table foo(bar)")
+
+    def CheckEmptyStatement(self):
+        """
+        pysqlite used to segfault with SQLite versions 3.5.x. These return NULL
+        for "no-operation" statements
+        """
+        self.con.execute("")
+
+    def CheckUnicodeConnect(self):
+        """
+        With pysqlite 2.4.0 you needed to use a string or a APSW connection
+        object for opening database connections.
+
+        Formerly, both bytestrings and unicode strings used to work.
+
+        Let's make sure unicode strings work in the future.
+        """
+        con = sqlite.connect(u":memory:")
+        con.close()
+
+    def CheckTypeMapUsage(self):
+        """
+        pysqlite until 2.4.1 did not rebuild the row_cast_map when recompiling
+        a statement. This test exhibits the problem.
+        """
+        SELECT = "select * from foo"
+        con = sqlite.connect(":memory:",detect_types=sqlite.PARSE_DECLTYPES)
+        con.execute("create table foo(bar timestamp)")
+        con.execute("insert into foo(bar) values (?)", (datetime.datetime.now(),))
+        con.execute(SELECT)
+        con.execute("drop table foo")
+        con.execute("create table foo(bar integer)")
+        con.execute("insert into foo(bar) values (5)")
+        con.execute(SELECT)
+
+    def CheckRegisterAdapter(self):
+        """
+        See issue 3312.
+        """
+        self.assertRaises(TypeError, sqlite.register_adapter, {}, None)
+
+    def CheckSetIsolationLevel(self):
+        """
+        See issue 3312.
+        """
+        con = sqlite.connect(":memory:")
+        self.assertRaises(UnicodeEncodeError, setattr, con,
+                          "isolation_level", u"\xe9")
+
+    def CheckCursorConstructorCallCheck(self):
+        """
+        Verifies that cursor methods check wether base class __init__ was called.
+        """
+        class Cursor(sqlite.Cursor):
+            def __init__(self, con):
+                pass
+
+        con = sqlite.connect(":memory:")
+        cur = Cursor(con)
+        try:
+            cur.execute("select 4+5").fetchall()
+            self.fail("should have raised ProgrammingError")
+        except sqlite.ProgrammingError:
+            pass
+        except:
+            self.fail("should have raised ProgrammingError")
+
+    def CheckConnectionConstructorCallCheck(self):
+        """
+        Verifies that connection methods check wether base class __init__ was called.
+        """
+        class Connection(sqlite.Connection):
+            def __init__(self, name):
+                pass
+
+        con = Connection(":memory:")
+        try:
+            cur = con.cursor()
+            self.fail("should have raised ProgrammingError")
+        except sqlite.ProgrammingError:
+            pass
+        except:
+            self.fail("should have raised ProgrammingError")
+
+    def CheckCursorRegistration(self):
+        """
+        Verifies that subclassed cursor classes are correctly registered with
+        the connection object, too.  (fetch-across-rollback problem)
+        """
+        class Connection(sqlite.Connection):
+            def cursor(self):
+                return Cursor(self)
+
+        class Cursor(sqlite.Cursor):
+            def __init__(self, con):
+                sqlite.Cursor.__init__(self, con)
+
+        con = Connection(":memory:")
+        cur = con.cursor()
+        cur.execute("create table foo(x)")
+        cur.executemany("insert into foo(x) values (?)", [(3,), (4,), (5,)])
+        cur.execute("select x from foo")
+        con.rollback()
+        try:
+            cur.fetchall()
+            self.fail("should have raised InterfaceError")
+        except sqlite.InterfaceError:
+            pass
+        except:
+            self.fail("should have raised InterfaceError")
+
+    def CheckAutoCommit(self):
+        """
+        Verifies that creating a connection in autocommit mode works.
+        2.5.3 introduced a regression so that these could no longer
+        be created.
+        """
+        con = sqlite.connect(":memory:", isolation_level=None)
+
+    def CheckPragmaAutocommit(self):
+        """
+        Verifies that running a PRAGMA statement that does an autocommit does
+        work. This did not work in 2.5.3/2.5.4.
+        """
+        con = sqlite.connect(":memory:")
+        cur = con.cursor()
+        cur.execute("create table foo(bar)")
+        cur.execute("insert into foo(bar) values (5)")
+
+        cur.execute("pragma page_size")
+        row = cur.fetchone()
+
+    def CheckSetDict(self):
+        """
+        See http://bugs.python.org/issue7478
+
+        It was possible to successfully register callbacks that could not be
+        hashed. Return codes of PyDict_SetItem were not checked properly.
+        """
+        class NotHashable:
+            def __call__(self, *args, **kw):
+                pass
+            def __hash__(self):
+                raise TypeError()
+        var = NotHashable()
+        con = sqlite.connect(":memory:")
+        self.assertRaises(TypeError, con.create_function, var)
+        self.assertRaises(TypeError, con.create_aggregate, var)
+        self.assertRaises(TypeError, con.set_authorizer, var)
+        self.assertRaises(TypeError, con.set_progress_handler, var)
+
+def suite():
+    regression_suite = unittest.makeSuite(RegressionTests, "Check")
+    return unittest.TestSuite((regression_suite,))
+
+def test():
+    runner = unittest.TextTestRunner()
+    runner.run(suite())
+
+if __name__ == "__main__":
+    test()
diff --git a/lib/test/transactions.py b/lib/test/transactions.py
new file mode 100644 (file)
index 0000000..4e3aa08
--- /dev/null
@@ -0,0 +1,205 @@
+#-*- coding: ISO-8859-1 -*-
+# pysqlite2/test/transactions.py: tests transactions
+#
+# Copyright (C) 2005-2009 Gerhard Häring <gh@ghaering.de>
+#
+# This file is part of pysqlite.
+#
+# This software is provided 'as-is', without any express or implied
+# warranty.  In no event will the authors be held liable for any damages
+# arising from the use of this software.
+#
+# Permission is granted to anyone to use this software for any purpose,
+# including commercial applications, and to alter it and redistribute it
+# freely, subject to the following restrictions:
+#
+# 1. The origin of this software must not be misrepresented; you must not
+#    claim that you wrote the original software. If you use this software
+#    in a product, an acknowledgment in the product documentation would be
+#    appreciated but is not required.
+# 2. Altered source versions must be plainly marked as such, and must not be
+#    misrepresented as being the original software.
+# 3. This notice may not be removed or altered from any source distribution.
+
+import sys
+import os, unittest
+import pysqlcipher.dbapi2 as sqlite
+
+def get_db_path():
+    return "sqlite_testdb"
+
+class TransactionTests(unittest.TestCase):
+    def setUp(self):
+        try:
+            os.remove(get_db_path())
+        except OSError:
+            pass
+
+        self.con1 = sqlite.connect(get_db_path(), timeout=0.1)
+        self.cur1 = self.con1.cursor()
+
+        self.con2 = sqlite.connect(get_db_path(), timeout=0.1)
+        self.cur2 = self.con2.cursor()
+
+    def tearDown(self):
+        self.cur1.close()
+        self.con1.close()
+
+        self.cur2.close()
+        self.con2.close()
+
+        try:
+            os.unlink(get_db_path())
+        except OSError:
+            pass
+
+    def CheckDMLdoesAutoCommitBefore(self):
+        self.cur1.execute("create table test(i)")
+        self.cur1.execute("insert into test(i) values (5)")
+        self.cur1.execute("create table test2(j)")
+        self.cur2.execute("select i from test")
+        res = self.cur2.fetchall()
+        self.assertEqual(len(res), 1)
+
+    def CheckInsertStartsTransaction(self):
+        self.cur1.execute("create table test(i)")
+        self.cur1.execute("insert into test(i) values (5)")
+        self.cur2.execute("select i from test")
+        res = self.cur2.fetchall()
+        self.assertEqual(len(res), 0)
+
+    def CheckUpdateStartsTransaction(self):
+        self.cur1.execute("create table test(i)")
+        self.cur1.execute("insert into test(i) values (5)")
+        self.con1.commit()
+        self.cur1.execute("update test set i=6")
+        self.cur2.execute("select i from test")
+        res = self.cur2.fetchone()[0]
+        self.assertEqual(res, 5)
+
+    def CheckDeleteStartsTransaction(self):
+        self.cur1.execute("create table test(i)")
+        self.cur1.execute("insert into test(i) values (5)")
+        self.con1.commit()
+        self.cur1.execute("delete from test")
+        self.cur2.execute("select i from test")
+        res = self.cur2.fetchall()
+        self.assertEqual(len(res), 1)
+
+    def CheckReplaceStartsTransaction(self):
+        self.cur1.execute("create table test(i)")
+        self.cur1.execute("insert into test(i) values (5)")
+        self.con1.commit()
+        self.cur1.execute("replace into test(i) values (6)")
+        self.cur2.execute("select i from test")
+        res = self.cur2.fetchall()
+        self.assertEqual(len(res), 1)
+        self.assertEqual(res[0][0], 5)
+
+    def CheckToggleAutoCommit(self):
+        self.cur1.execute("create table test(i)")
+        self.cur1.execute("insert into test(i) values (5)")
+        self.con1.isolation_level = None
+        self.assertEqual(self.con1.isolation_level, None)
+        self.cur2.execute("select i from test")
+        res = self.cur2.fetchall()
+        self.assertEqual(len(res), 1)
+
+        self.con1.isolation_level = "DEFERRED"
+        self.assertEqual(self.con1.isolation_level , "DEFERRED")
+        self.cur1.execute("insert into test(i) values (5)")
+        self.cur2.execute("select i from test")
+        res = self.cur2.fetchall()
+        self.assertEqual(len(res), 1)
+
+    def CheckRaiseTimeout(self):
+        if sqlite.sqlite_version_info < (3, 2, 2):
+            # This will fail (hang) on earlier versions of sqlite.
+            # Determine exact version it was fixed. 3.2.1 hangs.
+            return
+        self.cur1.execute("create table test(i)")
+        self.cur1.execute("insert into test(i) values (5)")
+        try:
+            self.cur2.execute("insert into test(i) values (5)")
+            self.fail("should have raised an OperationalError")
+        except sqlite.OperationalError:
+            pass
+        except:
+            self.fail("should have raised an OperationalError")
+
+    def CheckLocking(self):
+        """
+        This tests the improved concurrency with pysqlite 2.3.4. You needed
+        to roll back con2 before you could commit con1.
+        """
+        if sqlite.sqlite_version_info < (3, 2, 2):
+            # This will fail (hang) on earlier versions of sqlite.
+            # Determine exact version it was fixed. 3.2.1 hangs.
+            return
+        self.cur1.execute("create table test(i)")
+        self.cur1.execute("insert into test(i) values (5)")
+        try:
+            self.cur2.execute("insert into test(i) values (5)")
+            self.fail("should have raised an OperationalError")
+        except sqlite.OperationalError:
+            pass
+        except:
+            self.fail("should have raised an OperationalError")
+        # NO self.con2.rollback() HERE!!!
+        self.con1.commit()
+
+    def CheckRollbackCursorConsistency(self):
+        """
+        Checks if cursors on the connection are set into a "reset" state
+        when a rollback is done on the connection.
+        """
+        con = sqlite.connect(":memory:")
+        cur = con.cursor()
+        cur.execute("create table test(x)")
+        cur.execute("insert into test(x) values (5)")
+        cur.execute("select 1 union select 2 union select 3")
+
+        con.rollback()
+        try:
+            cur.fetchall()
+            self.fail("InterfaceError should have been raised")
+        except sqlite.InterfaceError, e:
+            pass
+        except:
+            self.fail("InterfaceError should have been raised")
+
+class SpecialCommandTests(unittest.TestCase):
+    def setUp(self):
+        self.con = sqlite.connect(":memory:")
+        self.cur = self.con.cursor()
+
+    def CheckVacuum(self):
+        self.cur.execute("create table test(i)")
+        self.cur.execute("insert into test(i) values (5)")
+        self.cur.execute("vacuum")
+
+    def CheckDropTable(self):
+        self.cur.execute("create table test(i)")
+        self.cur.execute("insert into test(i) values (5)")
+        self.cur.execute("drop table test")
+
+    def CheckPragma(self):
+        self.cur.execute("create table test(i)")
+        self.cur.execute("insert into test(i) values (5)")
+        self.cur.execute("pragma count_changes=1")
+
+    def tearDown(self):
+        self.cur.close()
+        self.con.close()
+
+def suite():
+    default_suite = unittest.makeSuite(TransactionTests, "Check")
+    special_command_suite = unittest.makeSuite(SpecialCommandTests, "Check")
+    return unittest.TestSuite((default_suite, special_command_suite))
+
+def test():
+    runner = unittest.TextTestRunner()
+    runner.run(suite())
+
+if __name__ == "__main__":
+    test()
diff --git a/lib/test/types.py b/lib/test/types.py
new file mode 100644 (file)
index 0000000..fc757be
--- /dev/null
@@ -0,0 +1,414 @@
+#-*- coding: ISO-8859-1 -*-
+# pysqlite2/test/types.py: tests for type conversion and detection
+#
+# Copyright (C) 2005-2007 Gerhard Häring <gh@ghaering.de>
+#
+# This file is part of pysqlite.
+#
+# This software is provided 'as-is', without any express or implied
+# warranty.  In no event will the authors be held liable for any damages
+# arising from the use of this software.
+#
+# Permission is granted to anyone to use this software for any purpose,
+# including commercial applications, and to alter it and redistribute it
+# freely, subject to the following restrictions:
+#
+# 1. The origin of this software must not be misrepresented; you must not
+#    claim that you wrote the original software. If you use this software
+#    in a product, an acknowledgment in the product documentation would be
+#    appreciated but is not required.
+# 2. Altered source versions must be plainly marked as such, and must not be
+#    misrepresented as being the original software.
+# 3. This notice may not be removed or altered from any source distribution.
+
+import datetime
+import unittest
+import pysqlcipher.dbapi2 as sqlite
+import zlib
+
+
+class SqliteTypeTests(unittest.TestCase):
+    def setUp(self):
+        self.con = sqlite.connect(":memory:")
+        self.cur = self.con.cursor()
+        self.cur.execute("create table test(i integer, s varchar, f number, b blob)")
+
+    def tearDown(self):
+        self.cur.close()
+        self.con.close()
+
+    def CheckString(self):
+        self.cur.execute("insert into test(s) values (?)", (u"Österreich",))
+        self.cur.execute("select s from test")
+        row = self.cur.fetchone()
+        self.assertEqual(row[0], u"Österreich")
+
+    def CheckSmallInt(self):
+        self.cur.execute("insert into test(i) values (?)", (42,))
+        self.cur.execute("select i from test")
+        row = self.cur.fetchone()
+        self.assertEqual(row[0], 42)
+
+    def CheckLargeInt(self):
+        num = 2**40
+        self.cur.execute("insert into test(i) values (?)", (num,))
+        self.cur.execute("select i from test")
+        row = self.cur.fetchone()
+        self.assertEqual(row[0], num)
+
+    def CheckFloat(self):
+        val = 3.14
+        self.cur.execute("insert into test(f) values (?)", (val,))
+        self.cur.execute("select f from test")
+        row = self.cur.fetchone()
+        self.assertEqual(row[0], val)
+
+    def CheckBlob(self):
+        val = buffer("Guglhupf")
+        self.cur.execute("insert into test(b) values (?)", (val,))
+        self.cur.execute("select b from test")
+        row = self.cur.fetchone()
+        self.assertEqual(row[0], val)
+
+    def CheckUnicodeExecute(self):
+        self.cur.execute(u"select 'Österreich'")
+        row = self.cur.fetchone()
+        self.assertEqual(row[0], u"Österreich")
+
+    def CheckNonUtf8_Default(self):
+        try:
+            self.cur.execute("select ?", (chr(150),))
+            self.fail("should have raised a ProgrammingError")
+        except sqlite.ProgrammingError:
+            pass
+
+    def CheckNonUtf8_TextFactoryString(self):
+        orig_text_factory = self.con.text_factory
+        try:
+            self.con.text_factory = str
+            self.cur.execute("select ?", (chr(150),))
+        finally:
+            self.con.text_factory = orig_text_factory
+
+    def CheckNonUtf8_TextFactoryOptimizedUnicode(self):
+        orig_text_factory = self.con.text_factory
+        try:
+            try:
+                self.con.text_factory = sqlite.OptimizedUnicode
+                self.cur.execute("select ?", (chr(150),))
+                self.fail("should have raised a ProgrammingError")
+            except sqlite.ProgrammingError:
+                pass
+        finally:
+            self.con.text_factory = orig_text_factory
+
+class DeclTypesTests(unittest.TestCase):
+    class Foo:
+        def __init__(self, _val):
+            self.val = _val
+
+        def __cmp__(self, other):
+            if not isinstance(other, DeclTypesTests.Foo):
+                raise ValueError
+            if self.val == other.val:
+                return 0
+            else:
+                return 1
+
+        def __conform__(self, protocol):
+            if protocol is sqlite.PrepareProtocol:
+                return self.val
+            else:
+                return None
+
+        def __str__(self):
+            return "<%s>" % self.val
+
+    def setUp(self):
+        self.con = sqlite.connect(":memory:", detect_types=sqlite.PARSE_DECLTYPES)
+        self.cur = self.con.cursor()
+        self.cur.execute("create table test(i int, s str, f float, b bool, u unicode, foo foo, bin blob, n1 number, n2 number(5))")
+
+        # override float, make them always return the same number
+        sqlite.converters["FLOAT"] = lambda x: 47.2
+
+        # and implement two custom ones
+        sqlite.converters["BOOL"] = lambda x: bool(int(x))
+        sqlite.converters["FOO"] = DeclTypesTests.Foo
+        sqlite.converters["WRONG"] = lambda x: "WRONG"
+        sqlite.converters["NUMBER"] = float
+
+    def tearDown(self):
+        del sqlite.converters["FLOAT"]
+        del sqlite.converters["BOOL"]
+        del sqlite.converters["FOO"]
+        del sqlite.converters["NUMBER"]
+        self.cur.close()
+        self.con.close()
+
+    def CheckString(self):
+        # default
+        self.cur.execute("insert into test(s) values (?)", ("foo",))
+        self.cur.execute('select s as "s [WRONG]" from test')
+        row = self.cur.fetchone()
+        self.assertEqual(row[0], "foo")
+
+    def CheckSmallInt(self):
+        # default
+        self.cur.execute("insert into test(i) values (?)", (42,))
+        self.cur.execute("select i from test")
+        row = self.cur.fetchone()
+        self.assertEqual(row[0], 42)
+
+    def CheckLargeInt(self):
+        # default
+        num = 2**40
+        self.cur.execute("insert into test(i) values (?)", (num,))
+        self.cur.execute("select i from test")
+        row = self.cur.fetchone()
+        self.assertEqual(row[0], num)
+
+    def CheckFloat(self):
+        # custom
+        val = 3.14
+        self.cur.execute("insert into test(f) values (?)", (val,))
+        self.cur.execute("select f from test")
+        row = self.cur.fetchone()
+        self.assertEqual(row[0], 47.2)
+
+    def CheckBool(self):
+        # custom
+        self.cur.execute("insert into test(b) values (?)", (False,))
+        self.cur.execute("select b from test")
+        row = self.cur.fetchone()
+        self.assertEqual(row[0], False)
+
+        self.cur.execute("delete from test")
+        self.cur.execute("insert into test(b) values (?)", (True,))
+        self.cur.execute("select b from test")
+        row = self.cur.fetchone()
+        self.assertEqual(row[0], True)
+
+    def CheckUnicode(self):
+        # default
+        val = u"\xd6sterreich"
+        self.cur.execute("insert into test(u) values (?)", (val,))
+        self.cur.execute("select u from test")
+        row = self.cur.fetchone()
+        self.assertEqual(row[0], val)
+
+    def CheckFoo(self):
+        val = DeclTypesTests.Foo("bla")
+        self.cur.execute("insert into test(foo) values (?)", (val,))
+        self.cur.execute("select foo from test")
+        row = self.cur.fetchone()
+        self.assertEqual(row[0], val)
+
+    def CheckUnsupportedSeq(self):
+        class Bar: pass
+        val = Bar()
+        try:
+            self.cur.execute("insert into test(f) values (?)", (val,))
+            self.fail("should have raised an InterfaceError")
+        except sqlite.InterfaceError:
+            pass
+        except:
+            self.fail("should have raised an InterfaceError")
+
+    def CheckUnsupportedDict(self):
+        class Bar: pass
+        val = Bar()
+        try:
+            self.cur.execute("insert into test(f) values (:val)", {"val": val})
+            self.fail("should have raised an InterfaceError")
+        except sqlite.InterfaceError:
+            pass
+        except:
+            self.fail("should have raised an InterfaceError")
+
+    def CheckBlob(self):
+        # default
+        val = buffer("Guglhupf")
+        self.cur.execute("insert into test(bin) values (?)", (val,))
+        self.cur.execute("select bin from test")
+        row = self.cur.fetchone()
+        self.assertEqual(row[0], val)
+
+    def CheckNumber1(self):
+        self.cur.execute("insert into test(n1) values (5)")
+        value = self.cur.execute("select n1 from test").fetchone()[0]
+        # if the converter is not used, it's an int instead of a float
+        self.assertEqual(type(value), float)
+
+    def CheckNumber2(self):
+        """Checks wether converter names are cut off at '(' characters"""
+        self.cur.execute("insert into test(n2) values (5)")
+        value = self.cur.execute("select n2 from test").fetchone()[0]
+        # if the converter is not used, it's an int instead of a float
+        self.assertEqual(type(value), float)
+
+class ColNamesTests(unittest.TestCase):
+    def setUp(self):
+        self.con = sqlite.connect(":memory:", detect_types=sqlite.PARSE_COLNAMES)
+        self.cur = self.con.cursor()
+        self.cur.execute("create table test(x foo)")
+
+        sqlite.converters["FOO"] = lambda x: "[%s]" % x
+        sqlite.converters["BAR"] = lambda x: "<%s>" % x
+        sqlite.converters["EXC"] = lambda x: 5 // 0
+        sqlite.converters["B1B1"] = lambda x: "MARKER"
+
+    def tearDown(self):
+        del sqlite.converters["FOO"]
+        del sqlite.converters["BAR"]
+        del sqlite.converters["EXC"]
+        del sqlite.converters["B1B1"]
+        self.cur.close()
+        self.con.close()
+
+    def CheckDeclTypeNotUsed(self):
+        """
+        Assures that the declared type is not used when PARSE_DECLTYPES
+        is not set.
+        """
+        self.cur.execute("insert into test(x) values (?)", ("xxx",))
+        self.cur.execute("select x from test")
+        val = self.cur.fetchone()[0]
+        self.assertEqual(val, "xxx")
+
+    def CheckNone(self):
+        self.cur.execute("insert into test(x) values (?)", (None,))
+        self.cur.execute("select x from test")
+        val = self.cur.fetchone()[0]
+        self.assertEqual(val, None)
+
+    def CheckColName(self):
+        self.cur.execute("insert into test(x) values (?)", ("xxx",))
+        self.cur.execute('select x as "x [bar]" from test')
+        val = self.cur.fetchone()[0]
+        self.assertEqual(val, "<xxx>")
+
+        # Check if the stripping of colnames works. Everything after the first
+        # whitespace should be stripped.
+        self.assertEqual(self.cur.description[0][0], "x")
+
+    def CheckCaseInConverterName(self):
+        self.cur.execute("""select 'other' as "x [b1b1]\"""")
+        val = self.cur.fetchone()[0]
+        self.assertEqual(val, "MARKER")
+
+    def CheckCursorDescriptionNoRow(self):
+        """
+        cursor.description should at least provide the column name(s), even if
+        no row returned.
+        """
+        self.cur.execute("select * from test where 0 = 1")
+        self.assert_(self.cur.description[0][0] == "x")
+
+class ObjectAdaptationTests(unittest.TestCase):
+    def cast(obj):
+        return float(obj)
+    cast = staticmethod(cast)
+
+    def setUp(self):
+        self.con = sqlite.connect(":memory:")
+        try:
+            del sqlite.adapters[int]
+        except:
+            pass
+        sqlite.register_adapter(int, ObjectAdaptationTests.cast)
+        self.cur = self.con.cursor()
+
+    def tearDown(self):
+        del sqlite.adapters[(int, sqlite.PrepareProtocol)]
+        self.cur.close()
+        self.con.close()
+
+    def CheckCasterIsUsed(self):
+        self.cur.execute("select ?", (4,))
+        val = self.cur.fetchone()[0]
+        self.assertEqual(type(val), float)
+
+class BinaryConverterTests(unittest.TestCase):
+    def convert(s):
+        return zlib.decompress(s)
+    convert = staticmethod(convert)
+
+    def setUp(self):
+        self.con = sqlite.connect(":memory:", detect_types=sqlite.PARSE_COLNAMES)
+        sqlite.register_converter("bin", BinaryConverterTests.convert)
+
+    def tearDown(self):
+        self.con.close()
+
+    def CheckBinaryInputForConverter(self):
+        testdata = "abcdefg" * 10
+        result = self.con.execute('select ? as "x [bin]"', (buffer(zlib.compress(testdata)),)).fetchone()[0]
+        self.assertEqual(testdata, result)
+
+class DateTimeTests(unittest.TestCase):
+    def setUp(self):
+        self.con = sqlite.connect(":memory:", detect_types=sqlite.PARSE_DECLTYPES)
+        self.cur = self.con.cursor()
+        self.cur.execute("create table test(d date, ts timestamp)")
+
+    def tearDown(self):
+        self.cur.close()
+        self.con.close()
+
+    def CheckSqliteDate(self):
+        d = sqlite.Date(2004, 2, 14)
+        self.cur.execute("insert into test(d) values (?)", (d,))
+        self.cur.execute("select d from test")
+        d2 = self.cur.fetchone()[0]
+        self.assertEqual(d, d2)
+
+    def CheckSqliteTimestamp(self):
+        ts = sqlite.Timestamp(2004, 2, 14, 7, 15, 0)
+        self.cur.execute("insert into test(ts) values (?)", (ts,))
+        self.cur.execute("select ts from test")
+        ts2 = self.cur.fetchone()[0]
+        self.assertEqual(ts, ts2)
+
+    def CheckSqlTimestamp(self):
+        # The date functions are only available in SQLite version 3.1 or later
+        if sqlite.sqlite_version_info < (3, 1):
+            return
+
+        # SQLite's current_timestamp uses UTC time, while datetime.datetime.now() uses local time.
+        now = datetime.datetime.now()
+        self.cur.execute("insert into test(ts) values (current_timestamp)")
+        self.cur.execute("select ts from test")
+        ts = self.cur.fetchone()[0]
+        self.assertEqual(type(ts), datetime.datetime)
+        self.assertEqual(ts.year, now.year)
+
+    def CheckDateTimeSubSeconds(self):
+        ts = sqlite.Timestamp(2004, 2, 14, 7, 15, 0, 500000)
+        self.cur.execute("insert into test(ts) values (?)", (ts,))
+        self.cur.execute("select ts from test")
+        ts2 = self.cur.fetchone()[0]
+        self.assertEqual(ts, ts2)
+
+    def CheckDateTimeSubSecondsFloatingPoint(self):
+        ts = sqlite.Timestamp(2004, 2, 14, 7, 15, 0, 510241)
+        self.cur.execute("insert into test(ts) values (?)", (ts,))
+        self.cur.execute("select ts from test")
+        ts2 = self.cur.fetchone()[0]
+        self.assertEqual(ts, ts2)
+
+def suite():
+    sqlite_type_suite = unittest.makeSuite(SqliteTypeTests, "Check")
+    decltypes_type_suite = unittest.makeSuite(DeclTypesTests, "Check")
+    colnames_type_suite = unittest.makeSuite(ColNamesTests, "Check")
+    adaptation_suite = unittest.makeSuite(ObjectAdaptationTests, "Check")
+    bin_suite = unittest.makeSuite(BinaryConverterTests, "Check")
+    date_suite = unittest.makeSuite(DateTimeTests, "Check")
+    return unittest.TestSuite((sqlite_type_suite, decltypes_type_suite, colnames_type_suite, adaptation_suite, bin_suite, date_suite))
+
+def test():
+    runner = unittest.TextTestRunner()
+    runner.run(suite())
+
+if __name__ == "__main__":
+    test()
diff --git a/lib/test/userfunctions.py b/lib/test/userfunctions.py
new file mode 100644 (file)
index 0000000..6b60f60
--- /dev/null
@@ -0,0 +1,413 @@
+#-*- coding: ISO-8859-1 -*-
+# pysqlite2/test/userfunctions.py: tests for user-defined functions and
+#                                  aggregates.
+#
+# Copyright (C) 2005-2007 Gerhard Häring <gh@ghaering.de>
+#
+# This file is part of pysqlite.
+#
+# This software is provided 'as-is', without any express or implied
+# warranty.  In no event will the authors be held liable for any damages
+# arising from the use of this software.
+#
+# Permission is granted to anyone to use this software for any purpose,
+# including commercial applications, and to alter it and redistribute it
+# freely, subject to the following restrictions:
+#
+# 1. The origin of this software must not be misrepresented; you must not
+#    claim that you wrote the original software. If you use this software
+#    in a product, an acknowledgment in the product documentation would be
+#    appreciated but is not required.
+# 2. Altered source versions must be plainly marked as such, and must not be
+#    misrepresented as being the original software.
+# 3. This notice may not be removed or altered from any source distribution.
+
+import unittest
+import pysqlcipher.dbapi2 as sqlite
+
+def func_returntext():
+    return "foo"
+def func_returnunicode():
+    return u"bar"
+def func_returnint():
+    return 42
+def func_returnfloat():
+    return 3.14
+def func_returnnull():
+    return None
+def func_returnblob():
+    return buffer("blob")
+def func_raiseexception():
+    5 // 0
+
+def func_isstring(v):
+    return type(v) is unicode
+def func_isint(v):
+    return type(v) is int
+def func_isfloat(v):
+    return type(v) is float
+def func_isnone(v):
+    return type(v) is type(None)
+def func_isblob(v):
+    return type(v) is buffer
+
+class AggrNoStep:
+    def __init__(self):
+        pass
+
+    def finalize(self):
+        return 1
+
+class AggrNoFinalize:
+    def __init__(self):
+        pass
+
+    def step(self, x):
+        pass
+
+class AggrExceptionInInit:
+    def __init__(self):
+        5 // 0
+
+    def step(self, x):
+        pass
+
+    def finalize(self):
+        pass
+
+class AggrExceptionInStep:
+    def __init__(self):
+        pass
+
+    def step(self, x):
+        5 // 0
+
+    def finalize(self):
+        return 42
+
+class AggrExceptionInFinalize:
+    def __init__(self):
+        pass
+
+    def step(self, x):
+        pass
+
+    def finalize(self):
+        5 // 0
+
+class AggrCheckType:
+    def __init__(self):
+        self.val = None
+
+    def step(self, whichType, val):
+        theType = {"str": unicode, "int": int, "float": float, "None": type(None), "blob": buffer}
+        self.val = int(theType[whichType] is type(val))
+
+    def finalize(self):
+        return self.val
+
+class AggrSum:
+    def __init__(self):
+        self.val = 0.0
+
+    def step(self, val):
+        self.val += val
+
+    def finalize(self):
+        return self.val
+
+class FunctionTests(unittest.TestCase):
+    def setUp(self):
+        self.con = sqlite.connect(":memory:")
+
+        self.con.create_function("returntext", 0, func_returntext)
+        self.con.create_function("returnunicode", 0, func_returnunicode)
+        self.con.create_function("returnint", 0, func_returnint)
+        self.con.create_function("returnfloat", 0, func_returnfloat)
+        self.con.create_function("returnnull", 0, func_returnnull)
+        self.con.create_function("returnblob", 0, func_returnblob)
+        self.con.create_function("raiseexception", 0, func_raiseexception)
+
+        self.con.create_function("isstring", 1, func_isstring)
+        self.con.create_function("isint", 1, func_isint)
+        self.con.create_function("isfloat", 1, func_isfloat)
+        self.con.create_function("isnone", 1, func_isnone)
+        self.con.create_function("isblob", 1, func_isblob)
+
+    def tearDown(self):
+        self.con.close()
+
+    def CheckFuncErrorOnCreate(self):
+        try:
+            self.con.create_function("bla", -100, lambda x: 2*x)
+            self.fail("should have raised an OperationalError")
+        except sqlite.OperationalError:
+            pass
+
+    def CheckFuncRefCount(self):
+        def getfunc():
+            def f():
+                return 1
+            return f
+        f = getfunc()
+        globals()["foo"] = f
+        # self.con.create_function("reftest", 0, getfunc())
+        self.con.create_function("reftest", 0, f)
+        cur = self.con.cursor()
+        cur.execute("select reftest()")
+
+    def CheckFuncReturnText(self):
+        cur = self.con.cursor()
+        cur.execute("select returntext()")
+        val = cur.fetchone()[0]
+        self.assertEqual(type(val), unicode)
+        self.assertEqual(val, "foo")
+
+    def CheckFuncReturnUnicode(self):
+        cur = self.con.cursor()
+        cur.execute("select returnunicode()")
+        val = cur.fetchone()[0]
+        self.assertEqual(type(val), unicode)
+        self.assertEqual(val, u"bar")
+
+    def CheckFuncReturnInt(self):
+        cur = self.con.cursor()
+        cur.execute("select returnint()")
+        val = cur.fetchone()[0]
+        self.assertEqual(type(val), int)
+        self.assertEqual(val, 42)
+
+    def CheckFuncReturnFloat(self):
+        cur = self.con.cursor()
+        cur.execute("select returnfloat()")
+        val = cur.fetchone()[0]
+        self.assertEqual(type(val), float)
+        if val < 3.139 or val > 3.141:
+            self.fail("wrong value")
+
+    def CheckFuncReturnNull(self):
+        cur = self.con.cursor()
+        cur.execute("select returnnull()")
+        val = cur.fetchone()[0]
+        self.assertEqual(type(val), type(None))
+        self.assertEqual(val, None)
+
+    def CheckFuncReturnBlob(self):
+        cur = self.con.cursor()
+        cur.execute("select returnblob()")
+        val = cur.fetchone()[0]
+        self.assertEqual(type(val), buffer)
+        self.assertEqual(val, buffer("blob"))
+
+    def CheckFuncException(self):
+        cur = self.con.cursor()
+        try:
+            cur.execute("select raiseexception()")
+            cur.fetchone()
+            self.fail("should have raised OperationalError")
+        except sqlite.OperationalError, e:
+            self.assertEqual(e.args[0], 'user-defined function raised exception')
+
+    def CheckParamString(self):
+        cur = self.con.cursor()
+        cur.execute("select isstring(?)", ("foo",))
+        val = cur.fetchone()[0]
+        self.assertEqual(val, 1)
+
+    def CheckParamInt(self):
+        cur = self.con.cursor()
+        cur.execute("select isint(?)", (42,))
+        val = cur.fetchone()[0]
+        self.assertEqual(val, 1)
+
+    def CheckParamFloat(self):
+        cur = self.con.cursor()
+        cur.execute("select isfloat(?)", (3.14,))
+        val = cur.fetchone()[0]
+        self.assertEqual(val, 1)
+
+    def CheckParamNone(self):
+        cur = self.con.cursor()
+        cur.execute("select isnone(?)", (None,))
+        val = cur.fetchone()[0]
+        self.assertEqual(val, 1)
+
+    def CheckParamBlob(self):
+        cur = self.con.cursor()
+        cur.execute("select isblob(?)", (buffer("blob"),))
+        val = cur.fetchone()[0]
+        self.assertEqual(val, 1)
+
+class AggregateTests(unittest.TestCase):
+    def setUp(self):
+        self.con = sqlite.connect(":memory:")
+        cur = self.con.cursor()
+        cur.execute("""
+            create table test(
+                t text,
+                i integer,
+                f float,
+                n,
+                b blob
+                )
+            """)
+        cur.execute("insert into test(t, i, f, n, b) values (?, ?, ?, ?, ?)",
+            ("foo", 5, 3.14, None, buffer("blob"),))
+
+        self.con.create_aggregate("nostep", 1, AggrNoStep)
+        self.con.create_aggregate("nofinalize", 1, AggrNoFinalize)
+        self.con.create_aggregate("excInit", 1, AggrExceptionInInit)
+        self.con.create_aggregate("excStep", 1, AggrExceptionInStep)
+        self.con.create_aggregate("excFinalize", 1, AggrExceptionInFinalize)
+        self.con.create_aggregate("checkType", 2, AggrCheckType)
+        self.con.create_aggregate("mysum", 1, AggrSum)
+
+    def tearDown(self):
+        #self.cur.close()
+        #self.con.close()
+        pass
+
+    def CheckAggrErrorOnCreate(self):
+        try:
+            self.con.create_function("bla", -100, AggrSum)
+            self.fail("should have raised an OperationalError")
+        except sqlite.OperationalError:
+            pass
+
+    def CheckAggrNoStep(self):
+        cur = self.con.cursor()
+        try:
+            cur.execute("select nostep(t) from test")
+            self.fail("should have raised an AttributeError")
+        except AttributeError, e:
+            self.assertEqual(e.args[0], "AggrNoStep instance has no attribute 'step'")
+
+    def CheckAggrNoFinalize(self):
+        cur = self.con.cursor()
+        try:
+            cur.execute("select nofinalize(t) from test")
+            val = cur.fetchone()[0]
+            self.fail("should have raised an OperationalError")
+        except sqlite.OperationalError, e:
+            self.assertEqual(e.args[0], "user-defined aggregate's 'finalize' method raised error")
+
+    def CheckAggrExceptionInInit(self):
+        cur = self.con.cursor()
+        try:
+            cur.execute("select excInit(t) from test")
+            val = cur.fetchone()[0]
+            self.fail("should have raised an OperationalError")
+        except sqlite.OperationalError, e:
+            self.assertEqual(e.args[0], "user-defined aggregate's '__init__' method raised error")
+
+    def CheckAggrExceptionInStep(self):
+        cur = self.con.cursor()
+        try:
+            cur.execute("select excStep(t) from test")
+            val = cur.fetchone()[0]
+            self.fail("should have raised an OperationalError")
+        except sqlite.OperationalError, e:
+            self.assertEqual(e.args[0], "user-defined aggregate's 'step' method raised error")
+
+    def CheckAggrExceptionInFinalize(self):
+        cur = self.con.cursor()
+        try:
+            cur.execute("select excFinalize(t) from test")
+            val = cur.fetchone()[0]
+            self.fail("should have raised an OperationalError")
+        except sqlite.OperationalError, e:
+            self.assertEqual(e.args[0], "user-defined aggregate's 'finalize' method raised error")
+
+    def CheckAggrCheckParamStr(self):
+        cur = self.con.cursor()
+        cur.execute("select checkType('str', ?)", ("foo",))
+        val = cur.fetchone()[0]
+        self.assertEqual(val, 1)
+
+    def CheckAggrCheckParamInt(self):
+        cur = self.con.cursor()
+        cur.execute("select checkType('int', ?)", (42,))
+        val = cur.fetchone()[0]
+        self.assertEqual(val, 1)
+
+    def CheckAggrCheckParamFloat(self):
+        cur = self.con.cursor()
+        cur.execute("select checkType('float', ?)", (3.14,))
+        val = cur.fetchone()[0]
+        self.assertEqual(val, 1)
+
+    def CheckAggrCheckParamNone(self):
+        cur = self.con.cursor()
+        cur.execute("select checkType('None', ?)", (None,))
+        val = cur.fetchone()[0]
+        self.assertEqual(val, 1)
+
+    def CheckAggrCheckParamBlob(self):
+        cur = self.con.cursor()
+        cur.execute("select checkType('blob', ?)", (buffer("blob"),))
+        val = cur.fetchone()[0]
+        self.assertEqual(val, 1)
+
+    def CheckAggrCheckAggrSum(self):
+        cur = self.con.cursor()
+        cur.execute("delete from test")
+        cur.executemany("insert into test(i) values (?)", [(10,), (20,), (30,)])
+        cur.execute("select mysum(i) from test")
+        val = cur.fetchone()[0]
+        self.assertEqual(val, 60)
+
+def authorizer_cb(action, arg1, arg2, dbname, source):
+    if action != sqlite.SQLITE_SELECT:
+        return sqlite.SQLITE_DENY
+    if arg2 == 'c2' or arg1 == 't2':
+        return sqlite.SQLITE_DENY
+    return sqlite.SQLITE_OK
+
+class AuthorizerTests(unittest.TestCase):
+    def setUp(self):
+        self.con = sqlite.connect(":memory:")
+        self.con.executescript("""
+            create table t1 (c1, c2);
+            create table t2 (c1, c2);
+            insert into t1 (c1, c2) values (1, 2);
+            insert into t2 (c1, c2) values (4, 5);
+            """)
+
+        # For our security test:
+        self.con.execute("select c2 from t2")
+
+        self.con.set_authorizer(authorizer_cb)
+
+    def tearDown(self):
+        pass
+
+    def CheckTableAccess(self):
+        try:
+            self.con.execute("select * from t2")
+        except sqlite.DatabaseError, e:
+            if not e.args[0].endswith("prohibited"):
+                self.fail("wrong exception text: %s" % e.args[0])
+            return
+        self.fail("should have raised an exception due to missing privileges")
+
+    def CheckColumnAccess(self):
+        try:
+            self.con.execute("select c2 from t1")
+        except sqlite.DatabaseError, e:
+            if not e.args[0].endswith("prohibited"):
+                self.fail("wrong exception text: %s" % e.args[0])
+            return
+        self.fail("should have raised an exception due to missing privileges")
+
+def suite():
+    function_suite = unittest.makeSuite(FunctionTests, "Check")
+    aggregate_suite = unittest.makeSuite(AggregateTests, "Check")
+    authorizer_suite = unittest.makeSuite(AuthorizerTests, "Check")
+    return unittest.TestSuite((function_suite, aggregate_suite, authorizer_suite))
+
+def test():
+    runner = unittest.TextTestRunner()
+    runner.run(suite())
+
+if __name__ == "__main__":
+    test()
diff --git a/setup.cfg b/setup.cfg
new file mode 100644 (file)
index 0000000..731e079
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,6 @@
+[build_ext]
+#define=
+#include_dirs=/usr/local/include
+#library_dirs=/usr/local/lib
+libraries=sqlite3
+#define=SQLITE_OMIT_LOAD_EXTENSION
diff --git a/setup.py b/setup.py
new file mode 100644 (file)
index 0000000..9a966c1
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,358 @@
+#-*- coding: ISO-8859-1 -*-
+# setup.py: the distutils script
+#
+# Copyright (C) Kali Kaneko <kali@futeisha.org> (sqlcipher support)
+# Copyright (C) 2005-2010 Gerhard Häring <gh@ghaering.de>
+#
+# This file is part of pysqlcipher.
+#
+# This software is provided 'as-is', without any express or implied
+# warranty.  In no event will the authors be held liable for any damages
+# arising from the use of this software.
+#
+# Permission is granted to anyone to use this software for any purpose,
+# including commercial applications, and to alter it and redistribute it
+# freely, subject to the following restrictions:
+#
+# 1. The origin of this software must not be misrepresented; you must not
+#    claim that you wrote the original software. If you use this software
+#    in a product, an acknowledgment in the product documentation would be
+#    appreciated but is not required.
+# 2. Altered source versions must be plainly marked as such, and must not be
+#    misrepresented as being the original software.
+# 3. This notice may not be removed or altered from any source distribution.
+
+import glob
+import os
+import re
+import sys
+import urllib
+import zipfile
+
+from types import ListType, TupleType
+
+from distutils.core import setup, Extension, Command
+from distutils.command.build import build
+from distutils.command.build_ext import build_ext
+from distutils.dep_util import newer_group
+from distutils.errors import DistutilsSetupError
+from distutils import log
+
+import cross_bdist_wininst
+
+# If you need to change anything, it should be enough to change setup.cfg.
+
+sqlite = "sqlite"
+
+PYSQLITE_EXPERIMENTAL = False
+
+sources = ["src/module.c", "src/connection.c", "src/cursor.c", "src/cache.c",
+           "src/microprotocols.c", "src/prepare_protocol.c", "src/statement.c",
+           "src/util.c", "src/row.c"]
+
+if PYSQLITE_EXPERIMENTAL:
+    sources.append("src/backup.c")
+
+include_dirs = []
+library_dirs = []
+libraries = []
+runtime_library_dirs = []
+extra_objects = []
+define_macros = []
+
+long_description = \
+"""Python interface to SQCipher
+
+pysqlcipher is an interface to the SQLite 3.x embedded relational database engine.
+It is almost fully compliant with the Python database API version 2.0 also
+exposes the unique features of SQLCipher."""
+
+if sys.platform != "win32":
+    define_macros.append(('MODULE_NAME', '"pysqlcipher.dbapi2"'))
+else:
+    define_macros.append(('MODULE_NAME', '\\"pysqlcipher.dbapi2\\"'))
+
+
+class DocBuilder(Command):
+    description = "Builds the documentation"
+    user_options = []
+
+    def initialize_options(self):
+        pass
+
+    def finalize_options(self):
+        pass
+
+    def run(self):
+        import shutil
+        try:
+            shutil.rmtree("build/doc")
+        except OSError:
+            pass
+        os.makedirs("build/doc")
+        rc = os.system("sphinx-build doc/sphinx build/doc")
+        if rc != 0:
+            print ("Is sphinx installed? If not, "
+                   "try 'sudo easy_install sphinx'.")
+
+AMALGAMATION_ROOT = "amalgamation"
+
+
+def get_amalgamation():
+    """Download the SQLite amalgamation if it isn't there, already."""
+    if os.path.exists(AMALGAMATION_ROOT):
+        return
+    os.mkdir(AMALGAMATION_ROOT)
+    print "Downloading amalgation."
+
+    # XXX upload the amalgamation file to a somewhat more
+    # official place
+    amalgamation_url = ("http://futeisha.org/sqlcipher/"
+                        "amalgamation-sqlcipher-2.1.0.zip")
+
+    # and download it
+    print 'amalgamation url: %s' % (amalgamation_url,)
+    urllib.urlretrieve(amalgamation_url, "tmp.zip")
+
+    zf = zipfile.ZipFile("tmp.zip")
+    files = ["sqlite3.c", "sqlite3.h"]
+    directory = zf.namelist()[0]
+
+    for fn in files:
+        print "Extracting", fn
+        outf = open(AMALGAMATION_ROOT + os.sep + fn, "wb")
+        outf.write(zf.read(directory + fn))
+        outf.close()
+    zf.close()
+    os.unlink("tmp.zip")
+
+
+class AmalgamationBuilder(build):
+    description = ("Build a statically built pysqlcipher "
+                   "downloading and using a sqlcipher amalgamation.")
+
+    def __init__(self, *args, **kwargs):
+        MyBuildExt.amalgamation = True
+        MyBuildExt.static = True
+        build.__init__(self, *args, **kwargs)
+
+
+class MyBuildExt(build_ext):
+    amalgamation = True  # We want amalgamation on the default build for now
+    static = False
+
+    def build_extension(self, ext):
+        if self.amalgamation:
+            get_amalgamation()
+            # build with fulltext search enabled
+            ext.define_macros.append(
+                ("SQLITE_ENABLE_FTS3", "1"))
+            ext.define_macros.append(
+                ("SQLITE_ENABLE_RTREE", "1"))
+
+            # SQLCipher options
+            ext.define_macros.append(
+                ("SQLITE_ENABLE_LOAD_EXTENSION", "1"))
+            ext.define_macros.append(
+                ("SQLITE_HAS_CODEC", "1"))
+            ext.define_macros.append(
+                ("SQLITE_TEMP_STORE", "2"))
+
+            ext.sources.append(os.path.join(AMALGAMATION_ROOT, "sqlite3.c"))
+            ext.include_dirs.append(AMALGAMATION_ROOT)
+
+            ext.extra_link_args.append("-lcrypto")
+
+        if self.static:
+            self._build_extension(ext)
+        else:
+            build_ext.build_extension(self, ext)
+
+    def _build_extension(self, ext):
+        sources = ext.sources
+        if sources is None or type(sources) not in (ListType, TupleType):
+            raise DistutilsSetupError, \
+                  ("in 'ext_modules' option (extension '%s'), " +
+                   "'sources' must be present and must be " +
+                   "a list of source filenames") % ext.name
+        sources = list(sources)
+
+        ext_path = self.get_ext_fullpath(ext.name)
+        depends = sources + ext.depends
+        if not (self.force or newer_group(depends, ext_path, 'newer')):
+            log.debug("skipping '%s' extension (up-to-date)", ext.name)
+            return
+        else:
+            log.info("building '%s' extension", ext.name)
+
+        # First, scan the sources for SWIG definition files (.i), run
+        # SWIG on 'em to create .c files, and modify the sources list
+        # accordingly.
+        sources = self.swig_sources(sources, ext)
+
+        # Next, compile the source code to object files.
+
+        # XXX not honouring 'define_macros' or 'undef_macros' -- the
+        # CCompiler API needs to change to accommodate this, and I
+        # want to do one thing at a time!
+
+        # Two possible sources for extra compiler arguments:
+        #   - 'extra_compile_args' in Extension object
+        #   - CFLAGS environment variable (not particularly
+        #     elegant, but people seem to expect it and I
+        #     guess it's useful)
+        # The environment variable should take precedence, and
+        # any sensible compiler will give precedence to later
+        # command line args.  Hence we combine them in order:
+        extra_args = ext.extra_compile_args or []
+
+        macros = ext.define_macros[:]
+        for undef in ext.undef_macros:
+            macros.append((undef,))
+
+        # XXX debug
+        #objects = []
+        objects = self.compiler.compile(sources,
+                                        output_dir=self.build_temp,
+                                        macros=macros,
+                                        include_dirs=ext.include_dirs,
+                                        debug=self.debug,
+                                        extra_postargs=extra_args,
+                                        depends=ext.depends)
+
+        # XXX -- this is a Vile HACK!
+        #
+        # The setup.py script for Python on Unix needs to be able to
+        # get this list so it can perform all the clean up needed to
+        # avoid keeping object files around when cleaning out a failed
+        # build of an extension module.  Since Distutils does not
+        # track dependencies, we have to get rid of intermediates to
+        # ensure all the intermediates will be properly re-built.
+        #
+        self._built_objects = objects[:]
+
+        # Now link the object files together into a "shared object" --
+        # of course, first we have to figure out all the other things
+        # that go into the mix.
+        if ext.extra_objects:
+            objects.extend(ext.extra_objects)
+        extra_args = ext.extra_link_args or []
+
+        # Detect target language, if not provided
+        language = ext.language or self.compiler.detect_language(sources)
+
+        #self.compiler.link_shared_object(
+            #objects, ext_path,
+            #libraries=self.get_libraries(ext),
+            #library_dirs=ext.library_dirs,
+            #runtime_library_dirs=ext.runtime_library_dirs,
+            #extra_postargs=extra_args,
+            #export_symbols=self.get_export_symbols(ext),
+            #debug=self.debug,
+            #build_temp=self.build_temp,
+            #target_lang=language)
+
+        # XXX may I have a static lib please?
+        # hmm but then I cannot load that extension, or can I?
+        output_dir = os.path.sep.join(ext_path.split(os.path.sep)[:-1])
+
+        self.compiler.create_static_lib(
+            objects,
+            #XXX get library name ... splitting ext_path?
+            "sqlite",
+            output_dir=output_dir,
+            target_lang=language)
+
+    def __setattr__(self, k, v):
+        # Make sure we don't link against the SQLite
+        # library, no matter what setup.cfg says
+        if self.amalgamation and k == "libraries":
+            v = None
+        self.__dict__[k] = v
+
+
+def get_setup_args():
+
+    PYSQLITE_VERSION = None
+
+    version_re = re.compile('#define PYSQLITE_VERSION "(.*)"')
+    f = open(os.path.join("src", "module.h"))
+    for line in f:
+        match = version_re.match(line)
+        if match:
+            PYSQLITE_VERSION = match.groups()[0]
+            PYSQLITE_MINOR_VERSION = ".".join(PYSQLITE_VERSION.split('.')[:2])
+            break
+    f.close()
+
+    if not PYSQLITE_VERSION:
+        print "Fatal error: PYSQLITE_VERSION could not be detected!"
+        sys.exit(1)
+
+    data_files = [("pysqlcipher-doc",
+                        glob.glob("doc/*.html") \
+                      + glob.glob("doc/*.txt") \
+                      + glob.glob("doc/*.css")),
+                   ("pysqlcipher-doc/code",
+                        glob.glob("doc/code/*.py"))]
+
+    py_modules = ["sqlcipher"],
+
+    setup_args = dict(
+        name="pysqlcipher",
+        version=PYSQLITE_VERSION,
+        description="DB-API 2.0 interface for SQLCIPHER 3.x",
+        long_description=long_description,
+        author="Kali Kaneko",
+        author_email="kali@futeisha.org",
+        license="zlib/libpng", # is THIS a license?
+        # It says MIT in the google project
+        platforms="ALL",
+        #XXX missing url
+        url="http://xxx.example.com/",
+
+        # Description of the modules and packages in the distribution
+        package_dir={"pysqlcipher": "lib"},
+        packages=["pysqlcipher", "pysqlcipher.test"] +
+            (["pysqlcipher.test.py25"], [])[sys.version_info < (2, 5)],
+        scripts=[],
+        data_files=data_files,
+
+        ext_modules=[
+            Extension(
+                name="pysqlcipher._sqlite",
+                sources=sources,
+                include_dirs=include_dirs,
+                library_dirs=library_dirs,
+                runtime_library_dirs=runtime_library_dirs,
+                libraries=libraries,
+                extra_objects=extra_objects,
+                define_macros=define_macros)
+        ],
+        classifiers=[
+            "Development Status :: 5 - Production/Stable",
+            "Intended Audience :: Developers",
+            "License :: OSI Approved :: zlib/libpng License",
+            "Operating System :: MacOS :: MacOS X",
+            "Operating System :: Microsoft :: Windows",
+            "Operating System :: POSIX",
+            "Programming Language :: C",
+            "Programming Language :: Python",
+            "Topic :: Database :: Database Engines/Servers",
+            "Topic :: Software Development :: Libraries :: Python Modules"],
+        cmdclass={"build_docs": DocBuilder}
+    )
+
+    setup_args["cmdclass"].update(
+        {"build_docs": DocBuilder,
+         "build_ext": MyBuildExt,
+         "build_static": AmalgamationBuilder,
+         "cross_bdist_wininst": cross_bdist_wininst.bdist_wininst})
+    return setup_args
+
+
+def main():
+    setup(**get_setup_args())
+
+if __name__ == "__main__":
+    main()
diff --git a/src/backup.h b/src/backup.h
new file mode 100644 (file)
index 0000000..10720f0
--- /dev/null
@@ -0,0 +1,43 @@
+/* backup.h - definitions for the backup type
+ *
+ * Copyright (C) 2010 Gerhard Häring <gh@ghaering.de>
+ *
+ * This file is part of pysqlite.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty.  In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ *    claim that you wrote the original software. If you use this software
+ *    in a product, an acknowledgment in the product documentation would be
+ *    appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ *    misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ */
+
+#ifndef PYSQLITE_BACKUP_H
+#define PYSQLITE_BACKUP_H
+#include "Python.h"
+
+#include "sqlite3.h"
+#include "connection.h"
+
+typedef struct
+{
+    PyObject_HEAD
+    sqlite3_backup* backup;
+    pysqlite_Connection* source_con;
+    pysqlite_Connection* dest_con;
+} pysqlite_Backup;
+
+extern PyTypeObject pysqlite_BackupType;
+
+int pysqlite_backup_setup_types(void);
+
+#endif
diff --git a/src/cache.c b/src/cache.c
new file mode 100644 (file)
index 0000000..0653367
--- /dev/null
@@ -0,0 +1,375 @@
+/* cache .c - a LRU cache
+ *
+ * Copyright (C) 2004-2010 Gerhard Häring <gh@ghaering.de>
+ *
+ * This file is part of pysqlite.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty.  In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ *    claim that you wrote the original software. If you use this software
+ *    in a product, an acknowledgment in the product documentation would be
+ *    appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ *    misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ */
+
+#include "sqlitecompat.h"
+#include "cache.h"
+#include <limits.h>
+
+/* only used internally */
+pysqlite_Node* pysqlite_new_node(PyObject* key, PyObject* data)
+{
+    pysqlite_Node* node;
+
+    node = (pysqlite_Node*) (pysqlite_NodeType.tp_alloc(&pysqlite_NodeType, 0));
+    if (!node) {
+        return NULL;
+    }
+
+    Py_INCREF(key);
+    node->key = key;
+
+    Py_INCREF(data);
+    node->data = data;
+
+    node->prev = NULL;
+    node->next = NULL;
+
+    return node;
+}
+
+void pysqlite_node_dealloc(pysqlite_Node* self)
+{
+    Py_DECREF(self->key);
+    Py_DECREF(self->data);
+
+    Py_TYPE(self)->tp_free((PyObject*)self);
+}
+
+int pysqlite_cache_init(pysqlite_Cache* self, PyObject* args, PyObject* kwargs)
+{
+    PyObject* factory;
+    int size = 10;
+
+    self->factory = NULL;
+
+    if (!PyArg_ParseTuple(args, "O|i", &factory, &size)) {
+        return -1;
+    }
+
+    /* minimum cache size is 5 entries */
+    if (size < 5) {
+        size = 5;
+    }
+    self->size = size;
+    self->first = NULL;
+    self->last = NULL;
+
+    self->mapping = PyDict_New();
+    if (!self->mapping) {
+        return -1;
+    }
+
+    Py_INCREF(factory);
+    self->factory = factory;
+
+    self->decref_factory = 1;
+
+    return 0;
+}
+
+void pysqlite_cache_dealloc(pysqlite_Cache* self)
+{
+    pysqlite_Node* node;
+    pysqlite_Node* delete_node;
+
+    if (!self->factory) {
+        /* constructor failed, just get out of here */
+        return;
+    }
+
+    /* iterate over all nodes and deallocate them */
+    node = self->first;
+    while (node) {
+        delete_node = node;
+        node = node->next;
+        Py_DECREF(delete_node);
+    }
+
+    if (self->decref_factory) {
+        Py_DECREF(self->factory);
+    }
+    Py_DECREF(self->mapping);
+
+    Py_TYPE(self)->tp_free((PyObject*)self);
+}
+
+PyObject* pysqlite_cache_get(pysqlite_Cache* self, PyObject* args)
+{
+    PyObject* key = args;
+    pysqlite_Node* node;
+    pysqlite_Node* ptr;
+    PyObject* data;
+
+    node = (pysqlite_Node*)PyDict_GetItem(self->mapping, key);
+    if (node) {
+        /* an entry for this key already exists in the cache */
+
+        /* increase usage counter of the node found */
+        if (node->count < LONG_MAX) {
+            node->count++;
+        }
+
+        /* if necessary, reorder entries in the cache by swapping positions */
+        if (node->prev && node->count > node->prev->count) {
+            ptr = node->prev;
+
+            while (ptr->prev && node->count > ptr->prev->count) {
+                ptr = ptr->prev;
+            }
+
+            if (node->next) {
+                node->next->prev = node->prev;
+            } else {
+                self->last = node->prev;
+            }
+            if (node->prev) {
+                node->prev->next = node->next;
+            }
+            if (ptr->prev) {
+                ptr->prev->next = node;
+            } else {
+                self->first = node;
+            }
+
+            node->next = ptr;
+            node->prev = ptr->prev;
+            if (!node->prev) {
+                self->first = node;
+            }
+            ptr->prev = node;
+        }
+    } else {
+        /* There is no entry for this key in the cache, yet. We'll insert a new
+         * entry in the cache, and make space if necessary by throwing the
+         * least used item out of the cache. */
+
+        if (PyDict_Size(self->mapping) == self->size) {
+            if (self->last) {
+                node = self->last;
+
+                if (PyDict_DelItem(self->mapping, self->last->key) != 0) {
+                    return NULL;
+                }
+
+                if (node->prev) {
+                    node->prev->next = NULL;
+                }
+                self->last = node->prev;
+                node->prev = NULL;
+
+                Py_DECREF(node);
+            }
+        }
+
+        data = PyObject_CallFunction(self->factory, "O", key);
+
+        if (!data) {
+            return NULL;
+        }
+
+        node = pysqlite_new_node(key, data);
+        if (!node) {
+            return NULL;
+        }
+        node->prev = self->last;
+
+        Py_DECREF(data);
+
+        if (PyDict_SetItem(self->mapping, key, (PyObject*)node) != 0) {
+            Py_DECREF(node);
+            return NULL;
+        }
+
+        if (self->last) {
+            self->last->next = node;
+        } else {
+            self->first = node;
+        }
+        self->last = node;
+    }
+
+    Py_INCREF(node->data);
+    return node->data;
+}
+
+PyObject* pysqlite_cache_display(pysqlite_Cache* self, PyObject* args)
+{
+    pysqlite_Node* ptr;
+    PyObject* prevkey;
+    PyObject* nextkey;
+    PyObject* fmt_args;
+    PyObject* template;
+    PyObject* display_str;
+
+    ptr = self->first;
+
+    while (ptr) {
+        if (ptr->prev) {
+            prevkey = ptr->prev->key;
+        } else {
+            prevkey = Py_None;
+        }
+        Py_INCREF(prevkey);
+
+        if (ptr->next) {
+            nextkey = ptr->next->key;
+        } else {
+            nextkey = Py_None;
+        }
+        Py_INCREF(nextkey);
+
+        fmt_args = Py_BuildValue("OOO", prevkey, ptr->key, nextkey);
+        if (!fmt_args) {
+            return NULL;
+        }
+        template = PyString_FromString("%s <- %s ->%s\n");
+        if (!template) {
+            Py_DECREF(fmt_args);
+            return NULL;
+        }
+        display_str = PyString_Format(template, fmt_args);
+        Py_DECREF(template);
+        Py_DECREF(fmt_args);
+        if (!display_str) {
+            return NULL;
+        }
+        PyObject_Print(display_str, stdout, Py_PRINT_RAW);
+        Py_DECREF(display_str);
+
+        Py_DECREF(prevkey);
+        Py_DECREF(nextkey);
+
+        ptr = ptr->next;
+    }
+
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+static PyMethodDef cache_methods[] = {
+    {"get", (PyCFunction)pysqlite_cache_get, METH_O,
+        PyDoc_STR("Gets an entry from the cache or calls the factory function to produce one.")},
+    {"display", (PyCFunction)pysqlite_cache_display, METH_NOARGS,
+        PyDoc_STR("For debugging only.")},
+    {NULL, NULL}
+};
+
+PyTypeObject pysqlite_NodeType = {
+        PyVarObject_HEAD_INIT(NULL, 0)
+        MODULE_NAME "Node",                             /* tp_name */
+        sizeof(pysqlite_Node),                          /* tp_basicsize */
+        0,                                              /* tp_itemsize */
+        (destructor)pysqlite_node_dealloc,              /* tp_dealloc */
+        0,                                              /* tp_print */
+        0,                                              /* tp_getattr */
+        0,                                              /* tp_setattr */
+        0,                                              /* tp_compare */
+        0,                                              /* tp_repr */
+        0,                                              /* tp_as_number */
+        0,                                              /* tp_as_sequence */
+        0,                                              /* tp_as_mapping */
+        0,                                              /* tp_hash */
+        0,                                              /* tp_call */
+        0,                                              /* tp_str */
+        0,                                              /* tp_getattro */
+        0,                                              /* tp_setattro */
+        0,                                              /* tp_as_buffer */
+        Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE,         /* tp_flags */
+        0,                                              /* tp_doc */
+        0,                                              /* tp_traverse */
+        0,                                              /* tp_clear */
+        0,                                              /* tp_richcompare */
+        0,                                              /* tp_weaklistoffset */
+        0,                                              /* tp_iter */
+        0,                                              /* tp_iternext */
+        0,                                              /* tp_methods */
+        0,                                              /* tp_members */
+        0,                                              /* tp_getset */
+        0,                                              /* tp_base */
+        0,                                              /* tp_dict */
+        0,                                              /* tp_descr_get */
+        0,                                              /* tp_descr_set */
+        0,                                              /* tp_dictoffset */
+        (initproc)0,                                    /* tp_init */
+        0,                                              /* tp_alloc */
+        0,                                              /* tp_new */
+        0                                               /* tp_free */
+};
+
+PyTypeObject pysqlite_CacheType = {
+        PyVarObject_HEAD_INIT(NULL, 0)
+        MODULE_NAME ".Cache",                           /* tp_name */
+        sizeof(pysqlite_Cache),                         /* tp_basicsize */
+        0,                                              /* tp_itemsize */
+        (destructor)pysqlite_cache_dealloc,             /* tp_dealloc */
+        0,                                              /* tp_print */
+        0,                                              /* tp_getattr */
+        0,                                              /* tp_setattr */
+        0,                                              /* tp_compare */
+        0,                                              /* tp_repr */
+        0,                                              /* tp_as_number */
+        0,                                              /* tp_as_sequence */
+        0,                                              /* tp_as_mapping */
+        0,                                              /* tp_hash */
+        0,                                              /* tp_call */
+        0,                                              /* tp_str */
+        0,                                              /* tp_getattro */
+        0,                                              /* tp_setattro */
+        0,                                              /* tp_as_buffer */
+        Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE,         /* tp_flags */
+        0,                                              /* tp_doc */
+        0,                                              /* tp_traverse */
+        0,                                              /* tp_clear */
+        0,                                              /* tp_richcompare */
+        0,                                              /* tp_weaklistoffset */
+        0,                                              /* tp_iter */
+        0,                                              /* tp_iternext */
+        cache_methods,                                  /* tp_methods */
+        0,                                              /* tp_members */
+        0,                                              /* tp_getset */
+        0,                                              /* tp_base */
+        0,                                              /* tp_dict */
+        0,                                              /* tp_descr_get */
+        0,                                              /* tp_descr_set */
+        0,                                              /* tp_dictoffset */
+        (initproc)pysqlite_cache_init,                  /* tp_init */
+        0,                                              /* tp_alloc */
+        0,                                              /* tp_new */
+        0                                               /* tp_free */
+};
+
+extern int pysqlite_cache_setup_types(void)
+{
+    int rc;
+
+    pysqlite_NodeType.tp_new = PyType_GenericNew;
+    pysqlite_CacheType.tp_new = PyType_GenericNew;
+
+    rc = PyType_Ready(&pysqlite_NodeType);
+    if (rc < 0) {
+        return rc;
+    }
+
+    rc = PyType_Ready(&pysqlite_CacheType);
+    return rc;
+}
diff --git a/src/cache.h b/src/cache.h
new file mode 100644 (file)
index 0000000..b09517c
--- /dev/null
@@ -0,0 +1,73 @@
+/* cache.h - definitions for the LRU cache
+ *
+ * Copyright (C) 2004-2010 Gerhard Häring <gh@ghaering.de>
+ *
+ * This file is part of pysqlite.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty.  In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ *    claim that you wrote the original software. If you use this software
+ *    in a product, an acknowledgment in the product documentation would be
+ *    appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ *    misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ */
+
+#ifndef PYSQLITE_CACHE_H
+#define PYSQLITE_CACHE_H
+#include "Python.h"
+
+/* The LRU cache is implemented as a combination of a doubly-linked with a
+ * dictionary. The list items are of type 'Node' and the dictionary has the
+ * nodes as values. */
+
+typedef struct _pysqlite_Node
+{
+    PyObject_HEAD
+    PyObject* key;
+    PyObject* data;
+    long count;
+    struct _pysqlite_Node* prev;
+    struct _pysqlite_Node* next;
+} pysqlite_Node;
+
+typedef struct
+{
+    PyObject_HEAD
+    int size;
+
+    /* a dictionary mapping keys to Node entries */
+    PyObject* mapping;
+
+    /* the factory callable */
+    PyObject* factory;
+
+    pysqlite_Node* first;
+    pysqlite_Node* last;
+
+    /* if set, decrement the factory function when the Cache is deallocated.
+     * this is almost always desirable, but not in the pysqlite context */
+    int decref_factory;
+} pysqlite_Cache;
+
+extern PyTypeObject pysqlite_NodeType;
+extern PyTypeObject pysqlite_CacheType;
+
+int pysqlite_node_init(pysqlite_Node* self, PyObject* args, PyObject* kwargs);
+void pysqlite_node_dealloc(pysqlite_Node* self);
+
+int pysqlite_cache_init(pysqlite_Cache* self, PyObject* args, PyObject* kwargs);
+void pysqlite_cache_dealloc(pysqlite_Cache* self);
+PyObject* pysqlite_cache_get(pysqlite_Cache* self, PyObject* args);
+
+int pysqlite_cache_setup_types(void);
+
+#endif
diff --git a/src/connection.c b/src/connection.c
new file mode 100644 (file)
index 0000000..481b9b5
--- /dev/null
@@ -0,0 +1,1704 @@
+/* connection.c - the connection type
+ *
+ * Copyright (C) 2004-2010 Gerhard Häring <gh@ghaering.de>
+ *
+ * This file is part of pysqlite.
+ * 
+ * This software is provided 'as-is', without any express or implied
+ * warranty.  In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ *    claim that you wrote the original software. If you use this software
+ *    in a product, an acknowledgment in the product documentation would be
+ *    appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ *    misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ */
+
+#include "cache.h"
+#include "module.h"
+#include "connection.h"
+#include "statement.h"
+#include "cursor.h"
+#include "prepare_protocol.h"
+#include "util.h"
+#include "sqlitecompat.h"
+
+#ifdef PYSQLITE_EXPERIMENTAL
+#include "backup.h"
+#endif
+
+#include "pythread.h"
+
+#define ACTION_FINALIZE 1
+#define ACTION_RESET 2
+
+#if SQLITE_VERSION_NUMBER >= 3003008
+#ifndef SQLITE_OMIT_LOAD_EXTENSION
+#define HAVE_LOAD_EXTENSION
+#endif
+#endif
+
+static int pysqlite_connection_set_isolation_level(pysqlite_Connection* self, PyObject* isolation_level);
+static void _pysqlite_drop_unused_cursor_references(pysqlite_Connection* self);
+
+
+static void _sqlite3_result_error(sqlite3_context* ctx, const char* errmsg, int len)
+{
+    /* in older SQLite versions, calling sqlite3_result_error in callbacks
+     * triggers a bug in SQLite that leads either to irritating results or
+     * segfaults, depending on the SQLite version */
+#if SQLITE_VERSION_NUMBER >= 3003003
+    sqlite3_result_error(ctx, errmsg, len);
+#else
+    PyErr_SetString(pysqlite_OperationalError, errmsg);
+#endif
+}
+
+int pysqlite_connection_init(pysqlite_Connection* self, PyObject* args, PyObject* kwargs)
+{
+    static char *kwlist[] = {"database", "timeout", "detect_types", "isolation_level", "check_same_thread", "factory", "cached_statements", NULL, NULL};
+
+    PyObject* database;
+    int detect_types = 0;
+    PyObject* isolation_level = NULL;
+    PyObject* factory = NULL;
+    int check_same_thread = 1;
+    int cached_statements = 100;
+    double timeout = 5.0;
+    int rc;
+    PyObject* class_attr = NULL;
+    PyObject* class_attr_str = NULL;
+    int is_apsw_connection = 0;
+    PyObject* database_utf8;
+
+    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|diOiOi", kwlist,
+                                     &database, &timeout, &detect_types, &isolation_level, &check_same_thread, &factory, &cached_statements))
+    {
+        return -1;
+    }
+
+    self->initialized = 1;
+
+    self->begin_statement = NULL;
+
+    self->statement_cache = NULL;
+    self->statements = NULL;
+    self->cursors = NULL;
+
+    Py_INCREF(Py_None);
+    self->row_factory = Py_None;
+
+    Py_INCREF(&PyUnicode_Type);
+    self->text_factory = (PyObject*)&PyUnicode_Type;
+
+    if (PyString_Check(database) || PyUnicode_Check(database)) {
+        if (PyString_Check(database)) {
+            database_utf8 = database;
+            Py_INCREF(database_utf8);
+        } else {
+            database_utf8 = PyUnicode_AsUTF8String(database);
+            if (!database_utf8) {
+                return -1;
+            }
+        }
+
+        Py_BEGIN_ALLOW_THREADS
+        rc = sqlite3_open(PyString_AsString(database_utf8), &self->db);
+        Py_END_ALLOW_THREADS
+
+        Py_DECREF(database_utf8);
+
+        if (rc != SQLITE_OK) {
+            _pysqlite_seterror(self->db, NULL);
+            return -1;
+        }
+    } else {
+        /* Create a pysqlite connection from a APSW connection */
+        class_attr = PyObject_GetAttrString(database, "__class__");
+        if (class_attr) {
+            class_attr_str = PyObject_Str(class_attr);
+            if (class_attr_str) {
+                if (strcmp(PyString_AsString(class_attr_str), "<type 'apsw.Connection'>") == 0) {
+                    /* In the APSW Connection object, the first entry after
+                     * PyObject_HEAD is the sqlite3* we want to get hold of.
+                     * Luckily, this is the same layout as we have in our
+                     * pysqlite_Connection */
+                    self->db = ((pysqlite_Connection*)database)->db;
+
+                    Py_INCREF(database);
+                    self->apsw_connection = database;
+                    is_apsw_connection = 1;
+                }
+            }
+        }
+        Py_XDECREF(class_attr_str);
+        Py_XDECREF(class_attr);
+
+        if (!is_apsw_connection) {
+            PyErr_SetString(PyExc_ValueError, "database parameter must be string or APSW Connection object");
+            return -1;
+        }
+    }
+
+    if (!isolation_level) {
+        isolation_level = PyString_FromString("");
+        if (!isolation_level) {
+            return -1;
+        }
+    } else {
+        Py_INCREF(isolation_level);
+    }
+    self->isolation_level = NULL;
+    pysqlite_connection_set_isolation_level(self, isolation_level);
+    Py_DECREF(isolation_level);
+
+    self->statement_cache = (pysqlite_Cache*)PyObject_CallFunction((PyObject*)&pysqlite_CacheType, "Oi", self, cached_statements);
+    if (PyErr_Occurred()) {
+        return -1;
+    }
+
+    self->created_statements = 0;
+    self->created_cursors = 0;
+
+    /* Create lists of weak references to statements/cursors */
+    self->statements = PyList_New(0);
+    self->cursors = PyList_New(0);
+    if (!self->statements || !self->cursors) {
+        return -1;
+    }
+
+    /* By default, the Cache class INCREFs the factory in its initializer, and
+     * decrefs it in its deallocator method. Since this would create a circular
+     * reference here, we're breaking it by decrementing self, and telling the
+     * cache class to not decref the factory (self) in its deallocator.
+     */
+    self->statement_cache->decref_factory = 0;
+    Py_DECREF(self);
+
+    self->inTransaction = 0;
+    self->detect_types = detect_types;
+    self->timeout = timeout;
+    (void)sqlite3_busy_timeout(self->db, (int)(timeout*1000));
+#ifdef WITH_THREAD
+    self->thread_ident = PyThread_get_thread_ident();
+#endif
+    self->check_same_thread = check_same_thread;
+
+    self->function_pinboard = PyDict_New();
+    if (!self->function_pinboard) {
+        return -1;
+    }
+
+    self->collations = PyDict_New();
+    if (!self->collations) {
+        return -1;
+    }
+
+    self->Warning               = pysqlite_Warning;
+    self->Error                 = pysqlite_Error;
+    self->InterfaceError        = pysqlite_InterfaceError;
+    self->DatabaseError         = pysqlite_DatabaseError;
+    self->DataError             = pysqlite_DataError;
+    self->OperationalError      = pysqlite_OperationalError;
+    self->IntegrityError        = pysqlite_IntegrityError;
+    self->InternalError         = pysqlite_InternalError;
+    self->ProgrammingError      = pysqlite_ProgrammingError;
+    self->NotSupportedError     = pysqlite_NotSupportedError;
+
+    return 0;
+}
+
+/* Empty the entire statement cache of this connection */
+void pysqlite_flush_statement_cache(pysqlite_Connection* self)
+{
+    pysqlite_Node* node;
+    pysqlite_Statement* statement;
+
+    node = self->statement_cache->first;
+
+    while (node) {
+        statement = (pysqlite_Statement*)(node->data);
+        (void)pysqlite_statement_finalize(statement);
+        node = node->next;
+    }
+
+    Py_DECREF(self->statement_cache);
+    self->statement_cache = (pysqlite_Cache*)PyObject_CallFunction((PyObject*)&pysqlite_CacheType, "O", self);
+    Py_DECREF(self);
+    self->statement_cache->decref_factory = 0;
+}
+
+/* action in (ACTION_RESET, ACTION_FINALIZE) */
+void pysqlite_do_all_statements(pysqlite_Connection* self, int action, int reset_cursors)
+{
+    int i;
+    PyObject* weakref;
+    PyObject* statement;
+    pysqlite_Cursor* cursor;
+
+    for (i = 0; i < PyList_Size(self->statements); i++) {
+        weakref = PyList_GetItem(self->statements, i);
+        statement = PyWeakref_GetObject(weakref);
+        if (statement != Py_None) {
+            if (action == ACTION_RESET) {
+                (void)pysqlite_statement_reset((pysqlite_Statement*)statement);
+            } else {
+                (void)pysqlite_statement_finalize((pysqlite_Statement*)statement);
+            }
+        }
+    }
+
+    if (reset_cursors) {
+        for (i = 0; i < PyList_Size(self->cursors); i++) {
+            weakref = PyList_GetItem(self->cursors, i);
+            cursor = (pysqlite_Cursor*)PyWeakref_GetObject(weakref);
+            if ((PyObject*)cursor != Py_None) {
+                cursor->reset = 1;
+            }
+        }
+    }
+}
+
+void pysqlite_connection_dealloc(pysqlite_Connection* self)
+{
+    PyObject* ret = NULL;
+
+    Py_XDECREF(self->statement_cache);
+
+    /* Clean up if user has not called .close() explicitly. */
+    if (self->db) {
+        Py_BEGIN_ALLOW_THREADS
+        sqlite3_close(self->db);
+        Py_END_ALLOW_THREADS
+    } else if (self->apsw_connection) {
+        ret = PyObject_CallMethod(self->apsw_connection, "close", "");
+        Py_XDECREF(ret);
+        Py_XDECREF(self->apsw_connection);
+    }
+
+    if (self->begin_statement) {
+        PyMem_Free(self->begin_statement);
+    }
+    Py_XDECREF(self->isolation_level);
+    Py_XDECREF(self->function_pinboard);
+    Py_XDECREF(self->row_factory);
+    Py_XDECREF(self->text_factory);
+    Py_XDECREF(self->collations);
+    Py_XDECREF(self->statements);
+    Py_XDECREF(self->cursors);
+
+    self->ob_type->tp_free((PyObject*)self);
+}
+
+/*
+ * Registers a cursor with the connection.
+ *
+ * 0 => error; 1 => ok
+ */
+int pysqlite_connection_register_cursor(pysqlite_Connection* connection, PyObject* cursor)
+{
+    PyObject* weakref;
+
+    weakref = PyWeakref_NewRef((PyObject*)cursor, NULL);
+    if (!weakref) {
+        goto error;
+    }
+
+    if (PyList_Append(connection->cursors, weakref) != 0) {
+        Py_CLEAR(weakref);
+        goto error;
+    }
+
+    Py_DECREF(weakref);
+
+    return 1;
+error:
+    return 0;
+}
+
+PyObject* pysqlite_connection_cursor(pysqlite_Connection* self, PyObject* args, PyObject* kwargs)
+{
+    static char *kwlist[] = {"factory", NULL, NULL};
+    PyObject* factory = NULL;
+    PyObject* cursor;
+
+    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|O", kwlist,
+                                     &factory)) {
+        return NULL;
+    }
+
+    if (!pysqlite_check_thread(self) || !pysqlite_check_connection(self)) {
+        return NULL;
+    }
+
+    if (factory == NULL) {
+        factory = (PyObject*)&pysqlite_CursorType;
+    }
+
+    cursor = PyObject_CallFunction(factory, "O", self);
+
+    _pysqlite_drop_unused_cursor_references(self);
+
+    if (cursor && self->row_factory != Py_None) {
+        Py_XDECREF(((pysqlite_Cursor*)cursor)->row_factory);
+        Py_INCREF(self->row_factory);
+        ((pysqlite_Cursor*)cursor)->row_factory = self->row_factory;
+    }
+
+    return cursor;
+}
+
+#ifdef PYSQLITE_EXPERIMENTAL
+PyObject* pysqlite_connection_backup(pysqlite_Connection* self, PyObject* args, PyObject* kwargs)
+{
+    static char *kwlist[] = {"dest_db", "source_name", "dest_db_name", NULL, NULL};
+    char* source_name;
+    char* dest_name;
+    pysqlite_Connection* dest_con;
+    pysqlite_Backup* backup;
+
+    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!ss", kwlist,
+                                     &pysqlite_ConnectionType, &dest_con, &source_name, &dest_name)) {
+        return NULL;
+    }
+
+    if (!pysqlite_check_thread(self) || !pysqlite_check_connection(self)) {
+        return NULL;
+    }
+
+    backup = PyObject_New(pysqlite_Backup, &pysqlite_BackupType);
+    if (!backup) {
+        return NULL;
+    }
+
+    Py_INCREF(self);
+    backup->source_con = self;
+
+    Py_INCREF(dest_con);
+    backup->dest_con = dest_con;
+
+    backup->backup = sqlite3_backup_init(dest_con->db, dest_name, self->db, source_name);
+
+    return (PyObject*)backup;
+}
+#endif
+
+PyObject* pysqlite_connection_close(pysqlite_Connection* self, PyObject* args)
+{
+    PyObject* ret;
+    int rc;
+
+    if (!pysqlite_check_thread(self)) {
+        return NULL;
+    }
+
+    pysqlite_do_all_statements(self, ACTION_FINALIZE, 1);
+
+    if (self->db) {
+        if (self->apsw_connection) {
+            ret = PyObject_CallMethod(self->apsw_connection, "close", "");
+            Py_XDECREF(ret);
+            Py_XDECREF(self->apsw_connection);
+            self->apsw_connection = NULL;
+            self->db = NULL;
+        } else {
+            Py_BEGIN_ALLOW_THREADS
+            rc = sqlite3_close(self->db);
+            Py_END_ALLOW_THREADS
+
+            if (rc != SQLITE_OK) {
+                _pysqlite_seterror(self->db, NULL);
+                return NULL;
+            } else {
+                self->db = NULL;
+            }
+        }
+    }
+
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+/*
+ * Checks if a connection object is usable (i. e. not closed).
+ *
+ * 0 => error; 1 => ok
+ */
+int pysqlite_check_connection(pysqlite_Connection* con)
+{
+    if (!con->initialized) {
+        PyErr_SetString(pysqlite_ProgrammingError, "Base Connection.__init__ not called.");
+        return 0;
+    }
+
+    if (!con->db) {
+        PyErr_SetString(pysqlite_ProgrammingError, "Cannot operate on a closed database.");
+        return 0;
+    } else {
+        return 1;
+    }
+}
+
+PyObject* _pysqlite_connection_begin(pysqlite_Connection* self)
+{
+    int rc;
+    const char* tail;
+    sqlite3_stmt* statement;
+
+    Py_BEGIN_ALLOW_THREADS
+    rc = sqlite3_prepare(self->db, self->begin_statement, -1, &statement, &tail);
+    Py_END_ALLOW_THREADS
+
+    if (rc != SQLITE_OK) {
+        _pysqlite_seterror(self->db, statement);
+        goto error;
+    }
+
+    rc = pysqlite_step(statement, self);
+    if (rc == SQLITE_DONE) {
+        self->inTransaction = 1;
+    } else {
+        _pysqlite_seterror(self->db, statement);
+    }
+
+    Py_BEGIN_ALLOW_THREADS
+    rc = sqlite3_finalize(statement);
+    Py_END_ALLOW_THREADS
+
+    if (rc != SQLITE_OK && !PyErr_Occurred()) {
+        _pysqlite_seterror(self->db, NULL);
+    }
+
+error:
+    if (PyErr_Occurred()) {
+        return NULL;
+    } else {
+        Py_INCREF(Py_None);
+        return Py_None;
+    }
+}
+
+PyObject* pysqlite_connection_commit(pysqlite_Connection* self, PyObject* args)
+{
+    int rc;
+    const char* tail;
+    sqlite3_stmt* statement;
+
+    if (!pysqlite_check_thread(self) || !pysqlite_check_connection(self)) {
+        return NULL;
+    }
+
+    if (self->inTransaction) {
+        pysqlite_do_all_statements(self, ACTION_RESET, 0);
+
+        Py_BEGIN_ALLOW_THREADS
+        rc = sqlite3_prepare(self->db, "COMMIT", -1, &statement, &tail);
+        Py_END_ALLOW_THREADS
+        if (rc != SQLITE_OK) {
+            _pysqlite_seterror(self->db, NULL);
+            goto error;
+        }
+
+        rc = pysqlite_step(statement, self);
+        if (rc == SQLITE_DONE) {
+            self->inTransaction = 0;
+        } else {
+            _pysqlite_seterror(self->db, statement);
+        }
+
+        Py_BEGIN_ALLOW_THREADS
+        rc = sqlite3_finalize(statement);
+        Py_END_ALLOW_THREADS
+        if (rc != SQLITE_OK && !PyErr_Occurred()) {
+            _pysqlite_seterror(self->db, NULL);
+        }
+
+    }
+
+error:
+    if (PyErr_Occurred()) {
+        return NULL;
+    } else {
+        Py_INCREF(Py_None);
+        return Py_None;
+    }
+}
+
+PyObject* pysqlite_connection_rollback(pysqlite_Connection* self, PyObject* args)
+{
+    int rc;
+    const char* tail;
+    sqlite3_stmt* statement;
+
+    if (!pysqlite_check_thread(self) || !pysqlite_check_connection(self)) {
+        return NULL;
+    }
+
+    if (self->inTransaction) {
+        pysqlite_do_all_statements(self, ACTION_RESET, 1);
+
+        Py_BEGIN_ALLOW_THREADS
+        rc = sqlite3_prepare(self->db, "ROLLBACK", -1, &statement, &tail);
+        Py_END_ALLOW_THREADS
+        if (rc != SQLITE_OK) {
+            _pysqlite_seterror(self->db, NULL);
+            goto error;
+        }
+
+        rc = pysqlite_step(statement, self);
+        if (rc == SQLITE_DONE) {
+            self->inTransaction = 0;
+        } else {
+            _pysqlite_seterror(self->db, statement);
+        }
+
+        Py_BEGIN_ALLOW_THREADS
+        rc = sqlite3_finalize(statement);
+        Py_END_ALLOW_THREADS
+        if (rc != SQLITE_OK && !PyErr_Occurred()) {
+            _pysqlite_seterror(self->db, NULL);
+        }
+
+    }
+
+error:
+    if (PyErr_Occurred()) {
+        return NULL;
+    } else {
+        Py_INCREF(Py_None);
+        return Py_None;
+    }
+}
+
+void _pysqlite_set_result(sqlite3_context* context, PyObject* py_val)
+{
+    long longval;
+    const char* buffer;
+    Py_ssize_t buflen;
+    PyObject* stringval;
+
+    if ((!py_val) || PyErr_Occurred()) {
+        sqlite3_result_null(context);
+    } else if (py_val == Py_None) {
+        sqlite3_result_null(context);
+    } else if (PyInt_Check(py_val)) {
+        longval = PyInt_AsLong(py_val);
+        sqlite3_result_int64(context, (PY_LONG_LONG)longval);
+    } else if (PyFloat_Check(py_val)) {
+        sqlite3_result_double(context, PyFloat_AsDouble(py_val));
+    } else if (PyBuffer_Check(py_val)) {
+        if (PyObject_AsCharBuffer(py_val, &buffer, &buflen) != 0) {
+            PyErr_SetString(PyExc_ValueError, "could not convert BLOB to buffer");
+        } else {
+            sqlite3_result_blob(context, buffer, buflen, SQLITE_TRANSIENT);
+        }
+    } else if (PyString_Check(py_val)) {
+        sqlite3_result_text(context, PyString_AsString(py_val), -1, SQLITE_TRANSIENT);
+    } else if (PyUnicode_Check(py_val)) {
+        stringval = PyUnicode_AsUTF8String(py_val);
+        if (stringval) {
+            sqlite3_result_text(context, PyString_AsString(stringval), -1, SQLITE_TRANSIENT);
+            Py_DECREF(stringval);
+        }
+    } else {
+        /* TODO: raise error */
+    }
+}
+
+PyObject* _pysqlite_build_py_params(sqlite3_context *context, int argc, sqlite3_value** argv)
+{
+    PyObject* args;
+    int i;
+    sqlite3_value* cur_value;
+    PyObject* cur_py_value;
+    const char* val_str;
+    PY_LONG_LONG val_int;
+    Py_ssize_t buflen;
+    void* raw_buffer;
+
+    args = PyTuple_New(argc);
+    if (!args) {
+        return NULL;
+    }
+
+    for (i = 0; i < argc; i++) {
+        cur_value = argv[i];
+        switch (sqlite3_value_type(argv[i])) {
+            case SQLITE_INTEGER:
+                val_int = sqlite3_value_int64(cur_value);
+                cur_py_value = PyInt_FromLong((long)val_int);
+                break;
+            case SQLITE_FLOAT:
+                cur_py_value = PyFloat_FromDouble(sqlite3_value_double(cur_value));
+                break;
+            case SQLITE_TEXT:
+                val_str = (const char*)sqlite3_value_text(cur_value);
+                cur_py_value = PyUnicode_DecodeUTF8(val_str, strlen(val_str), NULL);
+                /* TODO: have a way to show errors here */
+                if (!cur_py_value) {
+                    PyErr_Clear();
+                    Py_INCREF(Py_None);
+                    cur_py_value = Py_None;
+                }
+                break;
+            case SQLITE_BLOB:
+                buflen = sqlite3_value_bytes(cur_value);
+                cur_py_value = PyBuffer_New(buflen);
+                if (!cur_py_value) {
+                    break;
+                }
+                if (PyObject_AsWriteBuffer(cur_py_value, &raw_buffer, &buflen)) {
+                    Py_DECREF(cur_py_value);
+                    cur_py_value = NULL;
+                    break;
+                }
+                memcpy(raw_buffer, sqlite3_value_blob(cur_value), buflen);
+                break;
+            case SQLITE_NULL:
+            default:
+                Py_INCREF(Py_None);
+                cur_py_value = Py_None;
+        }
+
+        if (!cur_py_value) {
+            Py_DECREF(args);
+            return NULL;
+        }
+
+        PyTuple_SetItem(args, i, cur_py_value);
+
+    }
+
+    return args;
+}
+
+void _pysqlite_func_callback(sqlite3_context* context, int argc, sqlite3_value** argv)
+{
+    PyObject* args;
+    PyObject* py_func;
+    PyObject* py_retval = NULL;
+
+#ifdef WITH_THREAD
+    PyGILState_STATE threadstate;
+
+    threadstate = PyGILState_Ensure();
+#endif
+
+    py_func = (PyObject*)sqlite3_user_data(context);
+
+    args = _pysqlite_build_py_params(context, argc, argv);
+    if (args) {
+        py_retval = PyObject_CallObject(py_func, args);
+        Py_DECREF(args);
+    }
+
+    if (py_retval) {
+        _pysqlite_set_result(context, py_retval);
+        Py_DECREF(py_retval);
+    } else {
+        if (_enable_callback_tracebacks) {
+            PyErr_Print();
+        } else {
+            PyErr_Clear();
+        }
+        _sqlite3_result_error(context, "user-defined function raised exception", -1);
+    }
+
+#ifdef WITH_THREAD
+    PyGILState_Release(threadstate);
+#endif
+}
+
+static void _pysqlite_step_callback(sqlite3_context *context, int argc, sqlite3_value** params)
+{
+    PyObject* args;
+    PyObject* function_result = NULL;
+    PyObject* aggregate_class;
+    PyObject** aggregate_instance;
+    PyObject* stepmethod = NULL;
+
+#ifdef WITH_THREAD
+    PyGILState_STATE threadstate;
+
+    threadstate = PyGILState_Ensure();
+#endif
+
+    aggregate_class = (PyObject*)sqlite3_user_data(context);
+
+    aggregate_instance = (PyObject**)sqlite3_aggregate_context(context, sizeof(PyObject*));
+
+    if (*aggregate_instance == 0) {
+        *aggregate_instance = PyObject_CallFunction(aggregate_class, "");
+
+        if (PyErr_Occurred()) {
+            *aggregate_instance = 0;
+            if (_enable_callback_tracebacks) {
+                PyErr_Print();
+            } else {
+                PyErr_Clear();
+            }
+            _sqlite3_result_error(context, "user-defined aggregate's '__init__' method raised error", -1);
+            goto error;
+        }
+    }
+
+    stepmethod = PyObject_GetAttrString(*aggregate_instance, "step");
+    if (!stepmethod) {
+        goto error;
+    }
+
+    args = _pysqlite_build_py_params(context, argc, params);
+    if (!args) {
+        goto error;
+    }
+
+    function_result = PyObject_CallObject(stepmethod, args);
+    Py_DECREF(args);
+
+    if (!function_result) {
+        if (_enable_callback_tracebacks) {
+            PyErr_Print();
+        } else {
+            PyErr_Clear();
+        }
+        _sqlite3_result_error(context, "user-defined aggregate's 'step' method raised error", -1);
+    }
+
+error:
+    Py_XDECREF(stepmethod);
+    Py_XDECREF(function_result);
+
+#ifdef WITH_THREAD
+    PyGILState_Release(threadstate);
+#endif
+}
+
+void _pysqlite_final_callback(sqlite3_context* context)
+{
+    PyObject* function_result = NULL;
+    PyObject** aggregate_instance;
+    PyObject* aggregate_class;
+
+#ifdef WITH_THREAD
+    PyGILState_STATE threadstate;
+
+    threadstate = PyGILState_Ensure();
+#endif
+
+    aggregate_class = (PyObject*)sqlite3_user_data(context);
+
+    aggregate_instance = (PyObject**)sqlite3_aggregate_context(context, sizeof(PyObject*));
+    if (!*aggregate_instance) {
+        /* this branch is executed if there was an exception in the aggregate's
+         * __init__ */
+
+        goto error;
+    }
+
+    function_result = PyObject_CallMethod(*aggregate_instance, "finalize", "");
+    if (!function_result) {
+        if (_enable_callback_tracebacks) {
+            PyErr_Print();
+        } else {
+            PyErr_Clear();
+        }
+        _sqlite3_result_error(context, "user-defined aggregate's 'finalize' method raised error", -1);
+    } else {
+        _pysqlite_set_result(context, function_result);
+    }
+
+error:
+    Py_XDECREF(*aggregate_instance);
+    Py_XDECREF(function_result);
+
+#ifdef WITH_THREAD
+    PyGILState_Release(threadstate);
+#endif
+}
+
+static void _pysqlite_drop_unused_statement_references(pysqlite_Connection* self)
+{
+    PyObject* new_list;
+    PyObject* weakref;
+    int i;
+
+    /* we only need to do this once in a while */
+    if (self->created_statements++ < 200) {
+        return;
+    }
+
+    self->created_statements = 0;
+
+    new_list = PyList_New(0);
+    if (!new_list) {
+        return;
+    }
+
+    for (i = 0; i < PyList_Size(self->statements); i++) {
+        weakref = PyList_GetItem(self->statements, i);
+        if (PyWeakref_GetObject(weakref) != Py_None) {
+            if (PyList_Append(new_list, weakref) != 0) {
+                Py_DECREF(new_list);
+                return;
+            }
+        }
+    }
+
+    Py_DECREF(self->statements);
+    self->statements = new_list;
+}
+
+static void _pysqlite_drop_unused_cursor_references(pysqlite_Connection* self)
+{
+    PyObject* new_list;
+    PyObject* weakref;
+    int i;
+
+    /* we only need to do this once in a while */
+    if (self->created_cursors++ < 200) {
+        return;
+    }
+
+    self->created_cursors = 0;
+
+    new_list = PyList_New(0);
+    if (!new_list) {
+        return;
+    }
+
+    for (i = 0; i < PyList_Size(self->cursors); i++) {
+        weakref = PyList_GetItem(self->cursors, i);
+        if (PyWeakref_GetObject(weakref) != Py_None) {
+            if (PyList_Append(new_list, weakref) != 0) {
+                Py_DECREF(new_list);
+                return;
+            }
+        }
+    }
+
+    Py_DECREF(self->cursors);
+    self->cursors = new_list;
+}
+
+PyObject* pysqlite_connection_create_function(pysqlite_Connection* self, PyObject* args, PyObject* kwargs)
+{
+    static char *kwlist[] = {"name", "narg", "func", NULL, NULL};
+
+    PyObject* func;
+    char* name;
+    int narg;
+    int rc;
+
+    if (!pysqlite_check_thread(self) || !pysqlite_check_connection(self)) {
+        return NULL;
+    }
+
+    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "siO", kwlist,
+                                     &name, &narg, &func))
+    {
+        return NULL;
+    }
+
+    rc = sqlite3_create_function(self->db, name, narg, SQLITE_UTF8, (void*)func, _pysqlite_func_callback, NULL, NULL);
+
+    if (rc != SQLITE_OK) {
+        /* Workaround for SQLite bug: no error code or string is available here */
+        PyErr_SetString(pysqlite_OperationalError, "Error creating function");
+        return NULL;
+    } else {
+        if (PyDict_SetItem(self->function_pinboard, func, Py_None) == -1)
+            return NULL;
+
+        Py_INCREF(Py_None);
+        return Py_None;
+    }
+}
+
+PyObject* pysqlite_connection_create_aggregate(pysqlite_Connection* self, PyObject* args, PyObject* kwargs)
+{
+    PyObject* aggregate_class;
+
+    int n_arg;
+    char* name;
+    static char *kwlist[] = { "name", "n_arg", "aggregate_class", NULL };
+    int rc;
+
+    if (!pysqlite_check_thread(self) || !pysqlite_check_connection(self)) {
+        return NULL;
+    }
+
+    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "siO:create_aggregate",
+                                      kwlist, &name, &n_arg, &aggregate_class)) {
+        return NULL;
+    }
+
+    rc = sqlite3_create_function(self->db, name, n_arg, SQLITE_UTF8, (void*)aggregate_class, 0, &_pysqlite_step_callback, &_pysqlite_final_callback);
+    if (rc != SQLITE_OK) {
+        /* Workaround for SQLite bug: no error code or string is available here */
+        PyErr_SetString(pysqlite_OperationalError, "Error creating aggregate");
+        return NULL;
+    } else {
+        if (PyDict_SetItem(self->function_pinboard, aggregate_class, Py_None) == -1)
+            return NULL;
+
+        Py_INCREF(Py_None);
+        return Py_None;
+    }
+}
+
+static int _authorizer_callback(void* user_arg, int action, const char* arg1, const char* arg2 , const char* dbname, const char* access_attempt_source)
+{
+    PyObject *ret;
+    int rc;
+#ifdef WITH_THREAD
+    PyGILState_STATE gilstate;
+
+    gilstate = PyGILState_Ensure();
+#endif
+    ret = PyObject_CallFunction((PyObject*)user_arg, "issss", action, arg1, arg2, dbname, access_attempt_source);
+
+    if (!ret) {
+        if (_enable_callback_tracebacks) {
+            PyErr_Print();
+        } else {
+            PyErr_Clear();
+        }
+
+        rc = SQLITE_DENY;
+    } else {
+        if (PyInt_Check(ret)) {
+            rc = (int)PyInt_AsLong(ret);
+        } else {
+            rc = SQLITE_DENY;
+        }
+        Py_DECREF(ret);
+    }
+
+#ifdef WITH_THREAD
+    PyGILState_Release(gilstate);
+#endif
+    return rc;
+}
+
+static int _progress_handler(void* user_arg)
+{
+    int rc;
+    PyObject *ret;
+#ifdef WITH_THREAD
+    PyGILState_STATE gilstate;
+
+    gilstate = PyGILState_Ensure();
+#endif
+    ret = PyObject_CallFunction((PyObject*)user_arg, "");
+
+    if (!ret) {
+        if (_enable_callback_tracebacks) {
+            PyErr_Print();
+        } else {
+            PyErr_Clear();
+        }
+
+        /* abort query if error occurred */
+        rc = 1; 
+    } else {
+        rc = (int)PyObject_IsTrue(ret);
+        Py_DECREF(ret);
+    }
+
+#ifdef WITH_THREAD
+    PyGILState_Release(gilstate);
+#endif
+    return rc;
+}
+
+static PyObject* pysqlite_connection_set_authorizer(pysqlite_Connection* self, PyObject* args, PyObject* kwargs)
+{
+    PyObject* authorizer_cb;
+
+    static char *kwlist[] = { "authorizer_callback", NULL };
+    int rc;
+
+    if (!pysqlite_check_thread(self) || !pysqlite_check_connection(self)) {
+        return NULL;
+    }
+
+    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O:set_authorizer",
+                                      kwlist, &authorizer_cb)) {
+        return NULL;
+    }
+
+    rc = sqlite3_set_authorizer(self->db, _authorizer_callback, (void*)authorizer_cb);
+
+    if (rc != SQLITE_OK) {
+        PyErr_SetString(pysqlite_OperationalError, "Error setting authorizer callback");
+        return NULL;
+    } else {
+        if (PyDict_SetItem(self->function_pinboard, authorizer_cb, Py_None) == -1)
+            return NULL;
+
+        Py_INCREF(Py_None);
+        return Py_None;
+    }
+}
+
+static PyObject* pysqlite_connection_set_progress_handler(pysqlite_Connection* self, PyObject* args, PyObject* kwargs)
+{
+    PyObject* progress_handler;
+    int n;
+
+    static char *kwlist[] = { "progress_handler", "n", NULL };
+
+    if (!pysqlite_check_thread(self) || !pysqlite_check_connection(self)) {
+        return NULL;
+    }
+
+    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "Oi:set_progress_handler",
+                                      kwlist, &progress_handler, &n)) {
+        return NULL;
+    }
+
+    if (progress_handler == Py_None) {
+        /* None clears the progress handler previously set */
+        sqlite3_progress_handler(self->db, 0, 0, (void*)0);
+    } else {
+        sqlite3_progress_handler(self->db, n, _progress_handler, progress_handler);
+        if (PyDict_SetItem(self->function_pinboard, progress_handler, Py_None) == -1)
+            return NULL;
+    }
+
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+#ifdef HAVE_LOAD_EXTENSION
+static PyObject* pysqlite_enable_load_extension(pysqlite_Connection* self, PyObject* args)
+{
+    int rc;
+    int onoff;
+
+    if (!pysqlite_check_thread(self) || !pysqlite_check_connection(self)) {
+        return NULL;
+    }
+
+    if (!PyArg_ParseTuple(args, "i", &onoff)) {
+        return NULL;
+    }
+
+    rc = sqlite3_enable_load_extension(self->db, onoff);
+
+    if (rc != SQLITE_OK) {
+        PyErr_SetString(pysqlite_OperationalError, "Error enabling load extension");
+        return NULL;
+    } else {
+        Py_INCREF(Py_None);
+        return Py_None;
+    }
+}
+
+static PyObject* pysqlite_load_extension(pysqlite_Connection* self, PyObject* args)
+{
+    int rc;
+    char* extension_name;
+    char* errmsg;
+
+    if (!pysqlite_check_thread(self) || !pysqlite_check_connection(self)) {
+        return NULL;
+    }
+
+    if (!PyArg_ParseTuple(args, "s", &extension_name)) {
+        return NULL;
+    }
+
+    rc = sqlite3_load_extension(self->db, extension_name, 0, &errmsg);
+    if (rc != 0) {
+        PyErr_SetString(pysqlite_OperationalError, errmsg);
+        return NULL;
+    } else {
+        Py_INCREF(Py_None);
+        return Py_None;
+    }
+}
+#endif
+
+int pysqlite_check_thread(pysqlite_Connection* self)
+{
+#ifdef WITH_THREAD
+    if (self->check_same_thread) {
+        if (PyThread_get_thread_ident() != self->thread_ident) {
+            PyErr_Format(pysqlite_ProgrammingError,
+                        "SQLite objects created in a thread can only be used in that same thread."
+                        "The object was created in thread id %ld and this is thread id %ld",
+                        self->thread_ident, PyThread_get_thread_ident());
+            return 0;
+        }
+
+    }
+#endif
+    return 1;
+}
+
+static PyObject* pysqlite_connection_get_isolation_level(pysqlite_Connection* self, void* unused)
+{
+    Py_INCREF(self->isolation_level);
+    return self->isolation_level;
+}
+
+static PyObject* pysqlite_connection_get_total_changes(pysqlite_Connection* self, void* unused)
+{
+    if (!pysqlite_check_connection(self)) {
+        return NULL;
+    } else {
+        return Py_BuildValue("i", sqlite3_total_changes(self->db));
+    }
+}
+
+static int pysqlite_connection_set_isolation_level(pysqlite_Connection* self, PyObject* isolation_level)
+{
+    PyObject* res;
+    PyObject* begin_statement;
+    char* begin_statement_str;
+
+    Py_XDECREF(self->isolation_level);
+
+    if (self->begin_statement) {
+        PyMem_Free(self->begin_statement);
+        self->begin_statement = NULL;
+    }
+
+    if (isolation_level == Py_None) {
+        Py_INCREF(Py_None);
+        self->isolation_level = Py_None;
+
+        res = pysqlite_connection_commit(self, NULL);
+        if (!res) {
+            return -1;
+        }
+        Py_DECREF(res);
+
+        self->inTransaction = 0;
+    } else {
+        Py_INCREF(isolation_level);
+        self->isolation_level = isolation_level;
+
+        begin_statement = PyString_FromString("BEGIN ");
+        if (!begin_statement) {
+            return -1;
+        }
+        PyString_Concat(&begin_statement, isolation_level);
+        if (!begin_statement) {
+            return -1;
+        }
+
+        begin_statement_str = PyString_AsString(begin_statement);
+        if (!begin_statement_str) {
+            Py_DECREF(begin_statement);
+            return -1;
+        }
+        self->begin_statement = PyMem_Malloc(strlen(begin_statement_str) + 2);
+        if (!self->begin_statement) {
+            Py_DECREF(begin_statement);
+            return -1;
+        }
+
+        strcpy(self->begin_statement, begin_statement_str);
+        Py_DECREF(begin_statement);
+    }
+
+    return 0;
+}
+
+PyObject* pysqlite_connection_call(pysqlite_Connection* self, PyObject* args, PyObject* kwargs)
+{
+    PyObject* sql;
+    pysqlite_Statement* statement;
+    PyObject* weakref;
+    int rc;
+
+    if (!pysqlite_check_thread(self) || !pysqlite_check_connection(self)) {
+        return NULL;
+    }
+
+    if (!PyArg_ParseTuple(args, "O", &sql)) {
+        return NULL;
+    }
+
+    _pysqlite_drop_unused_statement_references(self);
+
+    statement = PyObject_New(pysqlite_Statement, &pysqlite_StatementType);
+    if (!statement) {
+        return NULL;
+    }
+
+    rc = pysqlite_statement_create(statement, self, sql);
+
+    if (rc != SQLITE_OK) {
+        if (rc == PYSQLITE_TOO_MUCH_SQL) {
+            PyErr_SetString(pysqlite_ProgrammingError, "You can only execute one statement at a time.");
+        } else if (rc == PYSQLITE_SQL_WRONG_TYPE) {
+            PyErr_SetString(pysqlite_ProgrammingError, "SQL is of wrong type. Must be string or unicode.");
+        } else {
+            (void)pysqlite_statement_reset(statement);
+            _pysqlite_seterror(self->db, NULL);
+        }
+
+        Py_CLEAR(statement);
+    } else {
+        weakref = PyWeakref_NewRef((PyObject*)statement, NULL);
+        if (!weakref) {
+            Py_CLEAR(statement);
+            goto error;
+        }
+
+        if (PyList_Append(self->statements, weakref) != 0) {
+            Py_CLEAR(weakref);
+            goto error;
+        }
+
+        Py_DECREF(weakref);
+    }
+
+error:
+    return (PyObject*)statement;
+}
+
+PyObject* pysqlite_connection_execute(pysqlite_Connection* self, PyObject* args, PyObject* kwargs)
+{
+    PyObject* cursor = 0;
+    PyObject* result = 0;
+    PyObject* method = 0;
+
+    cursor = PyObject_CallMethod((PyObject*)self, "cursor", "");
+    if (!cursor) {
+        goto error;
+    }
+
+    method = PyObject_GetAttrString(cursor, "execute");
+    if (!method) {
+        Py_CLEAR(cursor);
+        goto error;
+    }
+
+    result = PyObject_CallObject(method, args);
+    if (!result) {
+        Py_CLEAR(cursor);
+    }
+
+error:
+    Py_XDECREF(result);
+    Py_XDECREF(method);
+
+    return cursor;
+}
+
+PyObject* pysqlite_connection_executemany(pysqlite_Connection* self, PyObject* args, PyObject* kwargs)
+{
+    PyObject* cursor = 0;
+    PyObject* result = 0;
+    PyObject* method = 0;
+
+    cursor = PyObject_CallMethod((PyObject*)self, "cursor", "");
+    if (!cursor) {
+        goto error;
+    }
+
+    method = PyObject_GetAttrString(cursor, "executemany");
+    if (!method) {
+        Py_CLEAR(cursor);
+        goto error;
+    }
+
+    result = PyObject_CallObject(method, args);
+    if (!result) {
+        Py_CLEAR(cursor);
+    }
+
+error:
+    Py_XDECREF(result);
+    Py_XDECREF(method);
+
+    return cursor;
+}
+
+PyObject* pysqlite_connection_executescript(pysqlite_Connection* self, PyObject* args, PyObject* kwargs)
+{
+    PyObject* cursor = 0;
+    PyObject* result = 0;
+    PyObject* method = 0;
+
+    cursor = PyObject_CallMethod((PyObject*)self, "cursor", "");
+    if (!cursor) {
+        goto error;
+    }
+
+    method = PyObject_GetAttrString(cursor, "executescript");
+    if (!method) {
+        Py_CLEAR(cursor);
+        goto error;
+    }
+
+    result = PyObject_CallObject(method, args);
+    if (!result) {
+        Py_CLEAR(cursor);
+    }
+
+error:
+    Py_XDECREF(result);
+    Py_XDECREF(method);
+
+    return cursor;
+}
+
+/* ------------------------- COLLATION CODE ------------------------ */
+
+static int
+pysqlite_collation_callback(
+        void* context,
+        int text1_length, const void* text1_data,
+        int text2_length, const void* text2_data)
+{
+    PyObject* callback = (PyObject*)context;
+    PyObject* string1 = 0;
+    PyObject* string2 = 0;
+#ifdef WITH_THREAD
+    PyGILState_STATE gilstate;
+#endif
+    PyObject* retval = NULL;
+    int result = 0;
+#ifdef WITH_THREAD
+    gilstate = PyGILState_Ensure();
+#endif
+
+    if (PyErr_Occurred()) {
+        goto finally;
+    }
+
+    string1 = PyString_FromStringAndSize((const char*)text1_data, text1_length);
+    string2 = PyString_FromStringAndSize((const char*)text2_data, text2_length);
+
+    if (!string1 || !string2) {
+        goto finally; /* failed to allocate strings */
+    }
+
+    retval = PyObject_CallFunctionObjArgs(callback, string1, string2, NULL);
+
+    if (!retval) {
+        /* execution failed */
+        goto finally;
+    }
+
+    result = PyInt_AsLong(retval);
+    if (PyErr_Occurred()) {
+        result = 0;
+    }
+
+finally:
+    Py_XDECREF(string1);
+    Py_XDECREF(string2);
+    Py_XDECREF(retval);
+#ifdef WITH_THREAD
+    PyGILState_Release(gilstate);
+#endif
+    return result;
+}
+
+static PyObject *
+pysqlite_connection_interrupt(pysqlite_Connection* self, PyObject* args)
+{
+    PyObject* retval = NULL;
+
+    if (!pysqlite_check_connection(self)) {
+        goto finally;
+    }
+
+    sqlite3_interrupt(self->db);
+
+    Py_INCREF(Py_None);
+    retval = Py_None;
+
+finally:
+    return retval;
+}
+
+/* Function author: Paul Kippes <kippesp@gmail.com>
+ * 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("pysqlite2.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;
+    PyObject* uppercase_name = 0;
+    PyObject* name;
+    PyObject* retval;
+    char* chk;
+    int rc;
+
+    if (!pysqlite_check_thread(self) || !pysqlite_check_connection(self)) {
+        goto finally;
+    }
+
+    if (!PyArg_ParseTuple(args, "O!O:create_collation(name, callback)", &PyString_Type, &name, &callable)) {
+        goto finally;
+    }
+
+    uppercase_name = PyObject_CallMethod(name, "upper", "");
+    if (!uppercase_name) {
+        goto finally;
+    }
+
+    chk = PyString_AsString(uppercase_name);
+    while (*chk) {
+        if ((*chk >= '0' && *chk <= '9')
+         || (*chk >= 'A' && *chk <= 'Z')
+         || (*chk == '_'))
+        {
+            chk++;
+        } else {
+            PyErr_SetString(pysqlite_ProgrammingError, "invalid character in collation name");
+            goto finally;
+        }
+    }
+
+    if (callable != Py_None && !PyCallable_Check(callable)) {
+        PyErr_SetString(PyExc_TypeError, "parameter must be callable");
+        goto finally;
+    }
+
+    if (callable != Py_None) {
+        if (PyDict_SetItem(self->collations, uppercase_name, callable) == -1)
+            goto finally;
+    } else {
+        if (PyDict_DelItem(self->collations, uppercase_name) == -1)
+            goto finally;
+    }
+
+    rc = sqlite3_create_collation(self->db,
+                                  PyString_AsString(uppercase_name),
+                                  SQLITE_UTF8,
+                                  (callable != Py_None) ? callable : NULL,
+                                  (callable != Py_None) ? pysqlite_collation_callback : NULL);
+    if (rc != SQLITE_OK) {
+        PyDict_DelItem(self->collations, uppercase_name);
+        _pysqlite_seterror(self->db, NULL);
+        goto finally;
+    }
+
+finally:
+    Py_XDECREF(uppercase_name);
+
+    if (PyErr_Occurred()) {
+        retval = NULL;
+    } else {
+        Py_INCREF(Py_None);
+        retval = Py_None;
+    }
+
+    return retval;
+}
+
+/* Called when the connection is used as a context manager. Returns itself as a
+ * convenience to the caller. */
+static PyObject *
+pysqlite_connection_enter(pysqlite_Connection* self, PyObject* args)
+{
+    Py_INCREF(self);
+    return (PyObject*)self;
+}
+
+/** Called when the connection is used as a context manager. If there was any
+ * exception, a rollback takes place; otherwise we commit. */
+static PyObject *
+pysqlite_connection_exit(pysqlite_Connection* self, PyObject* args)
+{
+    PyObject* exc_type, *exc_value, *exc_tb;
+    char* method_name;
+    PyObject* result;
+
+    if (!PyArg_ParseTuple(args, "OOO", &exc_type, &exc_value, &exc_tb)) {
+        return NULL;
+    }
+
+    if (exc_type == Py_None && exc_value == Py_None && exc_tb == Py_None) {
+        method_name = "commit";
+    } else {
+        method_name = "rollback";
+    }
+
+    result = PyObject_CallMethod((PyObject*)self, method_name, "");
+    if (!result) {
+        return NULL;
+    }
+    Py_DECREF(result);
+
+    Py_INCREF(Py_False);
+    return Py_False;
+}
+
+static char connection_doc[] =
+PyDoc_STR("SQLite database connection object.");
+
+static PyGetSetDef connection_getset[] = {
+    {"isolation_level",  (getter)pysqlite_connection_get_isolation_level, (setter)pysqlite_connection_set_isolation_level},
+    {"total_changes",  (getter)pysqlite_connection_get_total_changes, (setter)0},
+    {NULL}
+};
+
+static PyMethodDef connection_methods[] = {
+    #ifdef PYSQLITE_EXPERIMENTAL
+    {"backup", (PyCFunction)pysqlite_connection_backup, METH_VARARGS|METH_KEYWORDS,
+        PyDoc_STR("Backup database.")},
+    #endif
+    {"cursor", (PyCFunction)pysqlite_connection_cursor, METH_VARARGS|METH_KEYWORDS,
+        PyDoc_STR("Return a cursor for the connection.")},
+    {"close", (PyCFunction)pysqlite_connection_close, METH_NOARGS,
+        PyDoc_STR("Closes the connection.")},
+    {"commit", (PyCFunction)pysqlite_connection_commit, METH_NOARGS,
+        PyDoc_STR("Commit the current transaction.")},
+    {"rollback", (PyCFunction)pysqlite_connection_rollback, METH_NOARGS,
+        PyDoc_STR("Roll back the current transaction.")},
+    {"create_function", (PyCFunction)pysqlite_connection_create_function, METH_VARARGS|METH_KEYWORDS,
+        PyDoc_STR("Creates a new function. Non-standard.")},
+    {"create_aggregate", (PyCFunction)pysqlite_connection_create_aggregate, METH_VARARGS|METH_KEYWORDS,
+        PyDoc_STR("Creates a new aggregate. Non-standard.")},
+    {"set_authorizer", (PyCFunction)pysqlite_connection_set_authorizer, METH_VARARGS|METH_KEYWORDS,
+        PyDoc_STR("Sets authorizer callback. Non-standard.")},
+    #ifdef HAVE_LOAD_EXTENSION
+    {"enable_load_extension", (PyCFunction)pysqlite_enable_load_extension, METH_VARARGS,
+        PyDoc_STR("Enable dynamic loading of SQLite extension modules. Non-standard.")},
+    {"load_extension", (PyCFunction)pysqlite_load_extension, METH_VARARGS,
+        PyDoc_STR("Load SQLite extension module. Non-standard.")},
+    #endif
+    {"set_progress_handler", (PyCFunction)pysqlite_connection_set_progress_handler, METH_VARARGS|METH_KEYWORDS,
+        PyDoc_STR("Sets progress handler callback. Non-standard.")},
+    {"execute", (PyCFunction)pysqlite_connection_execute, METH_VARARGS,
+        PyDoc_STR("Executes a SQL statement. Non-standard.")},
+    {"executemany", (PyCFunction)pysqlite_connection_executemany, METH_VARARGS,
+        PyDoc_STR("Repeatedly executes a SQL statement. Non-standard.")},
+    {"executescript", (PyCFunction)pysqlite_connection_executescript, METH_VARARGS,
+        PyDoc_STR("Executes a multiple SQL statements at once. Non-standard.")},
+    {"create_collation", (PyCFunction)pysqlite_connection_create_collation, METH_VARARGS,
+        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. Non-standard.")},
+    {"__enter__", (PyCFunction)pysqlite_connection_enter, METH_NOARGS,
+        PyDoc_STR("For context manager. Non-standard.")},
+    {"__exit__", (PyCFunction)pysqlite_connection_exit, METH_VARARGS,
+        PyDoc_STR("For context manager. Non-standard.")},
+    {NULL, NULL}
+};
+
+static struct PyMemberDef connection_members[] =
+{
+    {"Warning", T_OBJECT, offsetof(pysqlite_Connection, Warning), RO},
+    {"Error", T_OBJECT, offsetof(pysqlite_Connection, Error), RO},
+    {"InterfaceError", T_OBJECT, offsetof(pysqlite_Connection, InterfaceError), RO},
+    {"DatabaseError", T_OBJECT, offsetof(pysqlite_Connection, DatabaseError), RO},
+    {"DataError", T_OBJECT, offsetof(pysqlite_Connection, DataError), RO},
+    {"OperationalError", T_OBJECT, offsetof(pysqlite_Connection, OperationalError), RO},
+    {"IntegrityError", T_OBJECT, offsetof(pysqlite_Connection, IntegrityError), RO},
+    {"InternalError", T_OBJECT, offsetof(pysqlite_Connection, InternalError), RO},
+    {"ProgrammingError", T_OBJECT, offsetof(pysqlite_Connection, ProgrammingError), RO},
+    {"NotSupportedError", T_OBJECT, offsetof(pysqlite_Connection, NotSupportedError), RO},
+    {"row_factory", T_OBJECT, offsetof(pysqlite_Connection, row_factory)},
+    {"text_factory", T_OBJECT, offsetof(pysqlite_Connection, text_factory)},
+    {NULL}
+};
+
+PyTypeObject pysqlite_ConnectionType = {
+        PyVarObject_HEAD_INIT(NULL, 0)
+        MODULE_NAME ".Connection",                      /* tp_name */
+        sizeof(pysqlite_Connection),                    /* tp_basicsize */
+        0,                                              /* tp_itemsize */
+        (destructor)pysqlite_connection_dealloc,        /* tp_dealloc */
+        0,                                              /* tp_print */
+        0,                                              /* tp_getattr */
+        0,                                              /* tp_setattr */
+        0,                                              /* tp_compare */
+        0,                                              /* tp_repr */
+        0,                                              /* tp_as_number */
+        0,                                              /* tp_as_sequence */
+        0,                                              /* tp_as_mapping */
+        0,                                              /* tp_hash */
+        (ternaryfunc)pysqlite_connection_call,          /* tp_call */
+        0,                                              /* tp_str */
+        0,                                              /* tp_getattro */
+        0,                                              /* tp_setattro */
+        0,                                              /* tp_as_buffer */
+        Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE,         /* tp_flags */
+        connection_doc,                                 /* tp_doc */
+        0,                                              /* tp_traverse */
+        0,                                              /* tp_clear */
+        0,                                              /* tp_richcompare */
+        0,                                              /* tp_weaklistoffset */
+        0,                                              /* tp_iter */
+        0,                                              /* tp_iternext */
+        connection_methods,                             /* tp_methods */
+        connection_members,                             /* tp_members */
+        connection_getset,                              /* tp_getset */
+        0,                                              /* tp_base */
+        0,                                              /* tp_dict */
+        0,                                              /* tp_descr_get */
+        0,                                              /* tp_descr_set */
+        0,                                              /* tp_dictoffset */
+        (initproc)pysqlite_connection_init,             /* tp_init */
+        0,                                              /* tp_alloc */
+        0,                                              /* tp_new */
+        0                                               /* tp_free */
+};
+
+extern int pysqlite_connection_setup_types(void)
+{
+    pysqlite_ConnectionType.tp_new = PyType_GenericNew;
+    return PyType_Ready(&pysqlite_ConnectionType);
+}
diff --git a/src/connection.h b/src/connection.h
new file mode 100644 (file)
index 0000000..3fc5a70
--- /dev/null
@@ -0,0 +1,138 @@
+/* connection.h - definitions for the connection type
+ *
+ * Copyright (C) 2004-2010 Gerhard Häring <gh@ghaering.de>
+ *
+ * This file is part of pysqlite.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty.  In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ *    claim that you wrote the original software. If you use this software
+ *    in a product, an acknowledgment in the product documentation would be
+ *    appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ *    misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ */
+
+#ifndef PYSQLITE_CONNECTION_H
+#define PYSQLITE_CONNECTION_H
+#include "Python.h"
+#include "pythread.h"
+#include "structmember.h"
+
+#include "cache.h"
+#include "module.h"
+
+#include "sqlite3.h"
+
+typedef struct
+{
+    PyObject_HEAD
+    sqlite3* db;
+
+    /* 1 if we are currently within a transaction, i. e. if a BEGIN has been
+     * issued */
+    int inTransaction;
+
+    /* the type detection mode. Only 0, PARSE_DECLTYPES, PARSE_COLNAMES or a
+     * bitwise combination thereof makes sense */
+    int detect_types;
+
+    /* the timeout value in seconds for database locks */
+    double timeout;
+
+    /* for internal use in the timeout handler: when did the timeout handler
+     * first get called with count=0? */
+    double timeout_started;
+
+    /* None for autocommit, otherwise a PyString with the isolation level */
+    PyObject* isolation_level;
+
+    /* NULL for autocommit, otherwise a string with the BEGIN statment; will be
+     * freed in connection destructor */
+    char* begin_statement;
+
+    /* 1 if a check should be performed for each API call if the connection is
+     * used from the same thread it was created in */
+    int check_same_thread;
+
+    int initialized;
+
+    /* thread identification of the thread the connection was created in */
+    long thread_ident;
+
+    pysqlite_Cache* statement_cache;
+
+    /* Lists of weak references to statements and cursors used within this connection */
+    PyObject* statements;
+    PyObject* cursors;
+
+    /* Counters for how many statements/cursors were created in the connection. May be
+     * reset to 0 at certain intervals */
+    int created_statements;
+    int created_cursors;
+
+    PyObject* row_factory;
+
+    /* Determines how bytestrings from SQLite are converted to Python objects:
+     * - PyUnicode_Type:        Python Unicode objects are constructed from UTF-8 bytestrings
+     * - OptimizedUnicode:      Like before, but for ASCII data, only PyStrings are created.
+     * - PyString_Type:         PyStrings are created as-is.
+     * - Any custom callable:   Any object returned from the callable called with the bytestring
+     *                          as single parameter.
+     */
+    PyObject* text_factory;
+
+    /* remember references to functions/classes used in
+     * create_function/create/aggregate, use these as dictionary keys, so we
+     * can keep the total system refcount constant by clearing that dictionary
+     * in connection_dealloc */
+    PyObject* function_pinboard;
+
+    /* a dictionary of registered collation name => collation callable mappings */
+    PyObject* collations;
+
+    /* if our connection was created from a APSW connection, we keep a
+     * reference to the APSW connection around and get rid of it in our
+     * destructor */
+    PyObject* apsw_connection;
+
+    /* Exception objects */
+    PyObject* Warning;
+    PyObject* Error;
+    PyObject* InterfaceError;
+    PyObject* DatabaseError;
+    PyObject* DataError;
+    PyObject* OperationalError;
+    PyObject* IntegrityError;
+    PyObject* InternalError;
+    PyObject* ProgrammingError;
+    PyObject* NotSupportedError;
+} pysqlite_Connection;
+
+extern PyTypeObject pysqlite_ConnectionType;
+
+PyObject* pysqlite_connection_alloc(PyTypeObject* type, int aware);
+void pysqlite_connection_dealloc(pysqlite_Connection* self);
+PyObject* pysqlite_connection_cursor(pysqlite_Connection* self, PyObject* args, PyObject* kwargs);
+PyObject* pysqlite_connection_close(pysqlite_Connection* self, PyObject* args);
+PyObject* _pysqlite_connection_begin(pysqlite_Connection* self);
+PyObject* pysqlite_connection_commit(pysqlite_Connection* self, PyObject* args);
+PyObject* pysqlite_connection_rollback(pysqlite_Connection* self, PyObject* args);
+PyObject* pysqlite_connection_new(PyTypeObject* type, PyObject* args, PyObject* kw);
+int pysqlite_connection_init(pysqlite_Connection* self, PyObject* args, PyObject* kwargs);
+
+int pysqlite_connection_register_cursor(pysqlite_Connection* connection, PyObject* cursor);
+int pysqlite_check_thread(pysqlite_Connection* self);
+int pysqlite_check_connection(pysqlite_Connection* con);
+
+int pysqlite_connection_setup_types(void);
+
+#endif
diff --git a/src/cursor.c b/src/cursor.c
new file mode 100644 (file)
index 0000000..26e3307
--- /dev/null
@@ -0,0 +1,1126 @@
+/* cursor.c - the cursor type
+ *
+ * Copyright (C) 2004-2010 Gerhard Häring <gh@ghaering.de>
+ *
+ * This file is part of pysqlite.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty.  In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ *    claim that you wrote the original software. If you use this software
+ *    in a product, an acknowledgment in the product documentation would be
+ *    appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ *    misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ */
+
+#include "cursor.h"
+#include "module.h"
+#include "util.h"
+#include "sqlitecompat.h"
+
+/* used to decide wether to call PyInt_FromLong or PyLong_FromLongLong */
+#ifndef INT32_MIN
+#define INT32_MIN (-2147483647 - 1)
+#endif
+#ifndef INT32_MAX
+#define INT32_MAX 2147483647
+#endif
+
+PyObject* pysqlite_cursor_iternext(pysqlite_Cursor* self);
+
+static char* errmsg_fetch_across_rollback = "Cursor needed to be reset because of commit/rollback and can no longer be fetched from.";
+
+static pysqlite_StatementKind detect_statement_type(char* statement)
+{
+    char buf[20];
+    char* src;
+    char* dst;
+
+    src = statement;
+    /* skip over whitepace */
+    while (*src == '\r' || *src == '\n' || *src == ' ' || *src == '\t') {
+        src++;
+    }
+
+    if (*src == 0)
+        return STATEMENT_INVALID;
+
+    dst = buf;
+    *dst = 0;
+    while (isalpha(*src) && dst - buf < sizeof(buf) - 2) {
+        *dst++ = tolower(*src++);
+    }
+
+    *dst = 0;
+
+    if (!strcmp(buf, "select")) {
+        return STATEMENT_SELECT;
+    } else if (!strcmp(buf, "insert")) {
+        return STATEMENT_INSERT;
+    } else if (!strcmp(buf, "update")) {
+        return STATEMENT_UPDATE;
+    } else if (!strcmp(buf, "delete")) {
+        return STATEMENT_DELETE;
+    } else if (!strcmp(buf, "replace")) {
+        return STATEMENT_REPLACE;
+    } else {
+        return STATEMENT_OTHER;
+    }
+}
+
+static int pysqlite_cursor_init(pysqlite_Cursor* self, PyObject* args, PyObject* kwargs)
+{
+    pysqlite_Connection* connection;
+
+    if (!PyArg_ParseTuple(args, "O!", &pysqlite_ConnectionType, &connection))
+    {
+        return -1;
+    }
+
+    Py_INCREF(connection);
+    self->connection = connection;
+    self->statement = NULL;
+    self->next_row = NULL;
+    self->in_weakreflist = NULL;
+
+    self->row_cast_map = PyList_New(0);
+    if (!self->row_cast_map) {
+        return -1;
+    }
+
+    Py_INCREF(Py_None);
+    self->description = Py_None;
+
+    Py_INCREF(Py_None);
+    self->lastrowid= Py_None;
+
+    self->arraysize = 1;
+    self->closed = 0;
+    self->reset = 0;
+
+    self->rowcount = -1L;
+
+    Py_INCREF(Py_None);
+    self->row_factory = Py_None;
+
+    if (!pysqlite_check_thread(self->connection)) {
+        return -1;
+    }
+
+    if (!pysqlite_connection_register_cursor(connection, (PyObject*)self)) {
+        return -1;
+    }
+
+    self->initialized = 1;
+
+    return 0;
+}
+
+static void pysqlite_cursor_dealloc(pysqlite_Cursor* self)
+{
+    int rc;
+
+    /* Reset the statement if the user has not closed the cursor */
+    if (self->statement) {
+        rc = pysqlite_statement_reset(self->statement);
+        Py_DECREF(self->statement);
+    }
+
+    Py_XDECREF(self->connection);
+    Py_XDECREF(self->row_cast_map);
+    Py_XDECREF(self->description);
+    Py_XDECREF(self->lastrowid);
+    Py_XDECREF(self->row_factory);
+    Py_XDECREF(self->next_row);
+
+    if (self->in_weakreflist != NULL) {
+        PyObject_ClearWeakRefs((PyObject*)self);
+    }
+
+    self->ob_type->tp_free((PyObject*)self);
+}
+
+PyObject* _pysqlite_get_converter(PyObject* key)
+{
+    PyObject* upcase_key;
+    PyObject* retval;
+
+    upcase_key = PyObject_CallMethod(key, "upper", "");
+    if (!upcase_key) {
+        return NULL;
+    }
+
+    retval = PyDict_GetItem(converters, upcase_key);
+    Py_DECREF(upcase_key);
+
+    return retval;
+}
+
+int pysqlite_build_row_cast_map(pysqlite_Cursor* self)
+{
+    int i;
+    const char* type_start = (const char*)-1;
+    const char* pos;
+
+    const char* colname;
+    const char* decltype;
+    PyObject* py_decltype;
+    PyObject* converter;
+    PyObject* key;
+
+    if (!self->connection->detect_types) {
+        return 0;
+    }
+
+    Py_XDECREF(self->row_cast_map);
+    self->row_cast_map = PyList_New(0);
+
+    for (i = 0; i < sqlite3_column_count(self->statement->st); i++) {
+        converter = NULL;
+
+        if (self->connection->detect_types & PARSE_COLNAMES) {
+            colname = sqlite3_column_name(self->statement->st, i);
+            if (colname) {
+                for (pos = colname; *pos != 0; pos++) {
+                    if (*pos == '[') {
+                        type_start = pos + 1;
+                    } else if (*pos == ']' && type_start != (const char*)-1) {
+                        key = PyString_FromStringAndSize(type_start, pos - type_start);
+                        if (!key) {
+                            /* creating a string failed, but it is too complicated
+                             * to propagate the error here, we just assume there is
+                             * no converter and proceed */
+                            break;
+                        }
+
+                        converter = _pysqlite_get_converter(key);
+                        Py_DECREF(key);
+                        break;
+                    }
+                }
+            }
+        }
+
+        if (!converter && self->connection->detect_types & PARSE_DECLTYPES) {
+            decltype = sqlite3_column_decltype(self->statement->st, i);
+            if (decltype) {
+                for (pos = decltype;;pos++) {
+                    /* Converter names are split at '(' and blanks.
+                     * This allows 'INTEGER NOT NULL' to be treated as 'INTEGER' and
+                     * 'NUMBER(10)' to be treated as 'NUMBER', for example.
+                     * In other words, it will work as people expect it to work.*/
+                    if (*pos == ' ' || *pos == '(' || *pos == 0) {
+                        py_decltype = PyString_FromStringAndSize(decltype, pos - decltype);
+                        if (!py_decltype) {
+                            return -1;
+                        }
+                        break;
+                    }
+                }
+
+                converter = _pysqlite_get_converter(py_decltype);
+                Py_DECREF(py_decltype);
+            }
+        }
+
+        if (!converter) {
+            converter = Py_None;
+        }
+
+        if (PyList_Append(self->row_cast_map, converter) != 0) {
+            if (converter != Py_None) {
+                Py_DECREF(converter);
+            }
+            Py_XDECREF(self->row_cast_map);
+            self->row_cast_map = NULL;
+
+            return -1;
+        }
+    }
+
+    return 0;
+}
+
+PyObject* _pysqlite_build_column_name(const char* colname)
+{
+    const char* pos;
+
+    if (!colname) {
+        Py_INCREF(Py_None);
+        return Py_None;
+    }
+
+    for (pos = colname;; pos++) {
+        if (*pos == 0 || *pos == '[') {
+            if ((*pos == '[') && (pos > colname) && (*(pos-1) == ' ')) {
+                pos--;
+            }
+            return PyString_FromStringAndSize(colname, pos - colname);
+        }
+    }
+}
+
+PyObject* pysqlite_unicode_from_string(const char* val_str, int optimize)
+{
+    const char* check;
+    int is_ascii = 0;
+
+    if (optimize) {
+        is_ascii = 1;
+
+        check = val_str;
+        while (*check) {
+            if (*check & 0x80) {
+                is_ascii = 0;
+                break;
+            }
+
+            check++;
+        }
+    }
+
+    if (is_ascii) {
+        return PyString_FromString(val_str);
+    } else {
+        return PyUnicode_DecodeUTF8(val_str, strlen(val_str), NULL);
+    }
+}
+
+/*
+ * Returns a row from the currently active SQLite statement
+ *
+ * Precondidition:
+ * - sqlite3_step() has been called before and it returned SQLITE_ROW.
+ */
+PyObject* _pysqlite_fetch_one_row(pysqlite_Cursor* self)
+{
+    int i, numcols;
+    PyObject* row;
+    PyObject* item = NULL;
+    int coltype;
+    PY_LONG_LONG intval;
+    PyObject* converter;
+    PyObject* converted;
+    Py_ssize_t nbytes;
+    PyObject* buffer;
+    void* raw_buffer;
+    const char* val_str;
+    char buf[200];
+    const char* colname;
+
+    if (self->reset) {
+        PyErr_SetString(pysqlite_InterfaceError, errmsg_fetch_across_rollback);
+        return NULL;
+    }
+
+    Py_BEGIN_ALLOW_THREADS
+    numcols = sqlite3_data_count(self->statement->st);
+    Py_END_ALLOW_THREADS
+
+    row = PyTuple_New(numcols);
+    if (!row) {
+        return NULL;
+    }
+
+    for (i = 0; i < numcols; i++) {
+        if (self->connection->detect_types) {
+            converter = PyList_GetItem(self->row_cast_map, i);
+            if (!converter) {
+                converter = Py_None;
+            }
+        } else {
+            converter = Py_None;
+        }
+
+        if (converter != Py_None) {
+            nbytes = sqlite3_column_bytes(self->statement->st, i);
+            val_str = (const char*)sqlite3_column_blob(self->statement->st, i);
+            if (!val_str) {
+                Py_INCREF(Py_None);
+                converted = Py_None;
+            } else {
+                item = PyString_FromStringAndSize(val_str, nbytes);
+                if (!item) {
+                    return NULL;
+                }
+                converted = PyObject_CallFunction(converter, "O", item);
+                Py_DECREF(item);
+                if (!converted) {
+                    break;
+                }
+            }
+        } else {
+            Py_BEGIN_ALLOW_THREADS
+            coltype = sqlite3_column_type(self->statement->st, i);
+            Py_END_ALLOW_THREADS
+            if (coltype == SQLITE_NULL) {
+                Py_INCREF(Py_None);
+                converted = Py_None;
+            } else if (coltype == SQLITE_INTEGER) {
+                intval = sqlite3_column_int64(self->statement->st, i);
+                if (intval < INT32_MIN || intval > INT32_MAX) {
+                    converted = PyLong_FromLongLong(intval);
+                } else {
+                    converted = PyInt_FromLong((long)intval);
+                }
+            } else if (coltype == SQLITE_FLOAT) {
+                converted = PyFloat_FromDouble(sqlite3_column_double(self->statement->st, i));
+            } else if (coltype == SQLITE_TEXT) {
+                val_str = (const char*)sqlite3_column_text(self->statement->st, i);
+                if ((self->connection->text_factory == (PyObject*)&PyUnicode_Type)
+                    || (self->connection->text_factory == pysqlite_OptimizedUnicode)) {
+
+                    converted = pysqlite_unicode_from_string(val_str,
+                        self->connection->text_factory == pysqlite_OptimizedUnicode ? 1 : 0);
+
+                    if (!converted) {
+                        colname = sqlite3_column_name(self->statement->st, i);
+                        if (!colname) {
+                            colname = "<unknown column name>";
+                        }
+                        PyOS_snprintf(buf, sizeof(buf) - 1, "Could not decode to UTF-8 column '%s' with text '%s'",
+                                     colname , val_str);
+                        PyErr_SetString(pysqlite_OperationalError, buf);
+                    }
+                } else if (self->connection->text_factory == (PyObject*)&PyString_Type) {
+                    converted = PyString_FromString(val_str);
+                } else {
+                    converted = PyObject_CallFunction(self->connection->text_factory, "s", val_str);
+                }
+            } else {
+                /* coltype == SQLITE_BLOB */
+                nbytes = sqlite3_column_bytes(self->statement->st, i);
+                buffer = PyBuffer_New(nbytes);
+                if (!buffer) {
+                    break;
+                }
+                if (PyObject_AsWriteBuffer(buffer, &raw_buffer, &nbytes)) {
+                    break;
+                }
+                memcpy(raw_buffer, sqlite3_column_blob(self->statement->st, i), nbytes);
+                converted = buffer;
+            }
+        }
+
+        if (converted) {
+            PyTuple_SetItem(row, i, converted);
+        } else {
+            Py_INCREF(Py_None);
+            PyTuple_SetItem(row, i, Py_None);
+        }
+    }
+
+    if (PyErr_Occurred()) {
+        Py_DECREF(row);
+        row = NULL;
+    }
+
+    return row;
+}
+
+/*
+ * Checks if a cursor object is usable.
+ *
+ * 0 => error; 1 => ok
+ */
+static int check_cursor(pysqlite_Cursor* cur)
+{
+    if (!cur->initialized) {
+        PyErr_SetString(pysqlite_ProgrammingError, "Base Cursor.__init__ not called.");
+        return 0;
+    }
+
+    if (cur->closed) {
+        PyErr_SetString(pysqlite_ProgrammingError, "Cannot operate on a closed cursor.");
+        return 0;
+    } else {
+        return pysqlite_check_thread(cur->connection) && pysqlite_check_connection(cur->connection);
+    }
+}
+
+PyObject* _pysqlite_query_execute(pysqlite_Cursor* self, int multiple, PyObject* args)
+{
+    PyObject* operation;
+    PyObject* operation_bytestr = NULL;
+    char* operation_cstr;
+    PyObject* parameters_list = NULL;
+    PyObject* parameters_iter = NULL;
+    PyObject* parameters = NULL;
+    int i;
+    int rc;
+    PyObject* func_args;
+    PyObject* result;
+    int numcols;
+    PY_LONG_LONG lastrowid;
+    int statement_type;
+    PyObject* descriptor;
+    PyObject* second_argument = NULL;
+    int allow_8bit_chars;
+
+    if (!check_cursor(self)) {
+        return NULL;
+    }
+
+    self->reset = 0;
+
+    /* Make shooting yourself in the foot with not utf-8 decodable 8-bit-strings harder */
+    allow_8bit_chars = ((self->connection->text_factory != (PyObject*)&PyUnicode_Type) &&
+        (self->connection->text_factory != pysqlite_OptimizedUnicode));
+
+    Py_XDECREF(self->next_row);
+    self->next_row = NULL;
+
+    if (multiple) {
+        /* executemany() */
+        if (!PyArg_ParseTuple(args, "OO", &operation, &second_argument)) {
+            return NULL;
+        }
+
+        if (!PyString_Check(operation) && !PyUnicode_Check(operation)) {
+            PyErr_SetString(PyExc_ValueError, "operation parameter must be str or unicode");
+            return NULL;
+        }
+
+        if (PyIter_Check(second_argument)) {
+            /* iterator */
+            Py_INCREF(second_argument);
+            parameters_iter = second_argument;
+        } else {
+            /* sequence */
+            parameters_iter = PyObject_GetIter(second_argument);
+            if (!parameters_iter) {
+                return NULL;
+            }
+        }
+    } else {
+        /* execute() */
+        if (!PyArg_ParseTuple(args, "O|O", &operation, &second_argument)) {
+            return NULL;
+        }
+
+        if (!PyString_Check(operation) && !PyUnicode_Check(operation)) {
+            PyErr_SetString(PyExc_ValueError, "operation parameter must be str or unicode");
+            return NULL;
+        }
+
+        parameters_list = PyList_New(0);
+        if (!parameters_list) {
+            return NULL;
+        }
+
+        if (second_argument == NULL) {
+            second_argument = PyTuple_New(0);
+            if (!second_argument) {
+                goto error;
+            }
+        } else {
+            Py_INCREF(second_argument);
+        }
+        if (PyList_Append(parameters_list, second_argument) != 0) {
+            Py_DECREF(second_argument);
+            goto error;
+        }
+        Py_DECREF(second_argument);
+
+        parameters_iter = PyObject_GetIter(parameters_list);
+        if (!parameters_iter) {
+            goto error;
+        }
+    }
+
+    if (self->statement != NULL) {
+        /* There is an active statement */
+        rc = pysqlite_statement_reset(self->statement);
+    }
+
+    if (PyString_Check(operation)) {
+        operation_cstr = PyString_AsString(operation);
+    } else {
+        operation_bytestr = PyUnicode_AsUTF8String(operation);
+        if (!operation_bytestr) {
+            goto error;
+        }
+
+        operation_cstr = PyString_AsString(operation_bytestr);
+    }
+
+    /* reset description and rowcount */
+    Py_DECREF(self->description);
+    Py_INCREF(Py_None);
+    self->description = Py_None;
+    self->rowcount = -1L;
+
+    func_args = PyTuple_New(1);
+    if (!func_args) {
+        goto error;
+    }
+    Py_INCREF(operation);
+    if (PyTuple_SetItem(func_args, 0, operation) != 0) {
+        goto error;
+    }
+
+    if (self->statement) {
+        (void)pysqlite_statement_reset(self->statement);
+        Py_DECREF(self->statement);
+    }
+
+    self->statement = (pysqlite_Statement*)pysqlite_cache_get(self->connection->statement_cache, func_args);
+    Py_DECREF(func_args);
+
+    if (!self->statement) {
+        goto error;
+    }
+
+    if (self->statement->in_use) {
+        Py_DECREF(self->statement);
+        self->statement = PyObject_New(pysqlite_Statement, &pysqlite_StatementType);
+        if (!self->statement) {
+            goto error;
+        }
+        rc = pysqlite_statement_create(self->statement, self->connection, operation);
+        if (rc != SQLITE_OK) {
+            Py_CLEAR(self->statement);
+            goto error;
+        }
+    }
+
+    pysqlite_statement_reset(self->statement);
+    pysqlite_statement_mark_dirty(self->statement);
+
+    statement_type = detect_statement_type(operation_cstr);
+    if (self->connection->begin_statement) {
+        switch (statement_type) {
+            case STATEMENT_UPDATE:
+            case STATEMENT_DELETE:
+            case STATEMENT_INSERT:
+            case STATEMENT_REPLACE:
+                if (!self->connection->inTransaction) {
+                    result = _pysqlite_connection_begin(self->connection);
+                    if (!result) {
+                        goto error;
+                    }
+                    Py_DECREF(result);
+                }
+                break;
+            case STATEMENT_OTHER:
+                /* it's a DDL statement or something similar
+                   - we better COMMIT first so it works for all cases */
+                if (self->connection->inTransaction) {
+                    result = pysqlite_connection_commit(self->connection, NULL);
+                    if (!result) {
+                        goto error;
+                    }
+                    Py_DECREF(result);
+                }
+                break;
+            case STATEMENT_SELECT:
+                if (multiple) {
+                    PyErr_SetString(pysqlite_ProgrammingError,
+                                "You cannot execute SELECT statements in executemany().");
+                    goto error;
+                }
+                break;
+        }
+    }
+
+    while (1) {
+        parameters = PyIter_Next(parameters_iter);
+        if (!parameters) {
+            break;
+        }
+
+        pysqlite_statement_mark_dirty(self->statement);
+
+        pysqlite_statement_bind_parameters(self->statement, parameters, allow_8bit_chars);
+        if (PyErr_Occurred()) {
+            goto error;
+        }
+
+        /* Keep trying the SQL statement until the schema stops changing. */
+        while (1) {
+            /* Actually execute the SQL statement. */
+            rc = pysqlite_step(self->statement->st, self->connection);
+            if (rc == SQLITE_DONE ||  rc == SQLITE_ROW) {
+                /* If it worked, let's get out of the loop */
+                break;
+            }
+            /* Something went wrong.  Re-set the statement and try again. */
+            rc = pysqlite_statement_reset(self->statement);
+            if (rc == SQLITE_SCHEMA) {
+                /* If this was a result of the schema changing, let's try
+                   again. */
+                rc = pysqlite_statement_recompile(self->statement, parameters);
+                if (rc == SQLITE_OK) {
+                    continue;
+                } else {
+                    /* If the database gave us an error, promote it to Python. */
+                    (void)pysqlite_statement_reset(self->statement);
+                    _pysqlite_seterror(self->connection->db, NULL);
+                    goto error;
+                }
+            } else {
+                if (PyErr_Occurred()) {
+                    /* there was an error that occurred in a user-defined callback */
+                    if (_enable_callback_tracebacks) {
+                        PyErr_Print();
+                    } else {
+                        PyErr_Clear();
+                    }
+                }
+                (void)pysqlite_statement_reset(self->statement);
+                _pysqlite_seterror(self->connection->db, NULL);
+                goto error;
+            }
+        }
+
+        if (pysqlite_build_row_cast_map(self) != 0) {
+            PyErr_SetString(pysqlite_OperationalError, "Error while building row_cast_map");
+            goto error;
+        }
+
+        if (rc == SQLITE_ROW || (rc == SQLITE_DONE && statement_type == STATEMENT_SELECT)) {
+            if (self->description == Py_None) {
+                Py_BEGIN_ALLOW_THREADS
+                numcols = sqlite3_column_count(self->statement->st);
+                Py_END_ALLOW_THREADS
+
+                Py_DECREF(self->description);
+                self->description = PyTuple_New(numcols);
+                if (!self->description) {
+                    goto error;
+                }
+                for (i = 0; i < numcols; i++) {
+                    descriptor = PyTuple_New(7);
+                    if (!descriptor) {
+                        goto error;
+                    }
+                    PyTuple_SetItem(descriptor, 0, _pysqlite_build_column_name(sqlite3_column_name(self->statement->st, i)));
+                    Py_INCREF(Py_None); PyTuple_SetItem(descriptor, 1, Py_None);
+                    Py_INCREF(Py_None); PyTuple_SetItem(descriptor, 2, Py_None);
+                    Py_INCREF(Py_None); PyTuple_SetItem(descriptor, 3, Py_None);
+                    Py_INCREF(Py_None); PyTuple_SetItem(descriptor, 4, Py_None);
+                    Py_INCREF(Py_None); PyTuple_SetItem(descriptor, 5, Py_None);
+                    Py_INCREF(Py_None); PyTuple_SetItem(descriptor, 6, Py_None);
+                    PyTuple_SetItem(self->description, i, descriptor);
+                }
+            }
+        }
+
+        if (rc == SQLITE_ROW) {
+            if (multiple) {
+                PyErr_SetString(pysqlite_ProgrammingError, "executemany() can only execute DML statements.");
+                goto error;
+            }
+
+            self->next_row = _pysqlite_fetch_one_row(self);
+        } else if (rc == SQLITE_DONE && !multiple) {
+            pysqlite_statement_reset(self->statement);
+            Py_CLEAR(self->statement);
+        }
+
+        switch (statement_type) {
+            case STATEMENT_UPDATE:
+            case STATEMENT_DELETE:
+            case STATEMENT_INSERT:
+            case STATEMENT_REPLACE:
+                if (self->rowcount == -1L) {
+                    self->rowcount = 0L;
+                }
+                self->rowcount += (long)sqlite3_changes(self->connection->db);
+        }
+
+        Py_DECREF(self->lastrowid);
+        if (!multiple && statement_type == STATEMENT_INSERT) {
+            Py_BEGIN_ALLOW_THREADS
+            lastrowid = sqlite3_last_insert_rowid(self->connection->db);
+            Py_END_ALLOW_THREADS
+            self->lastrowid = PyInt_FromLong((long)lastrowid);
+        } else {
+            Py_INCREF(Py_None);
+            self->lastrowid = Py_None;
+        }
+
+        if (multiple) {
+            rc = pysqlite_statement_reset(self->statement);
+        }
+        Py_XDECREF(parameters);
+    }
+
+error:
+    /* just to be sure (implicit ROLLBACKs with ON CONFLICT ROLLBACK/OR
+     * ROLLBACK could have happened */
+    #ifdef SQLITE_VERSION_NUMBER
+    #if SQLITE_VERSION_NUMBER >= 3002002
+    self->connection->inTransaction = !sqlite3_get_autocommit(self->connection->db);
+    #endif
+    #endif
+
+    Py_XDECREF(operation_bytestr);
+    Py_XDECREF(parameters);
+    Py_XDECREF(parameters_iter);
+    Py_XDECREF(parameters_list);
+
+    if (PyErr_Occurred()) {
+        self->rowcount = -1L;
+        return NULL;
+    } else {
+        Py_INCREF(self);
+        return (PyObject*)self;
+    }
+}
+
+PyObject* pysqlite_cursor_execute(pysqlite_Cursor* self, PyObject* args)
+{
+    return _pysqlite_query_execute(self, 0, args);
+}
+
+PyObject* pysqlite_cursor_executemany(pysqlite_Cursor* self, PyObject* args)
+{
+    return _pysqlite_query_execute(self, 1, args);
+}
+
+PyObject* pysqlite_cursor_executescript(pysqlite_Cursor* self, PyObject* args)
+{
+    PyObject* script_obj;
+    PyObject* script_str = NULL;
+    const char* script_cstr;
+    sqlite3_stmt* statement;
+    int rc;
+    PyObject* result;
+
+    if (!PyArg_ParseTuple(args, "O", &script_obj)) {
+        return NULL;
+    }
+
+    if (!check_cursor(self)) {
+        return NULL;
+    }
+
+    self->reset = 0;
+
+    if (PyString_Check(script_obj)) {
+        script_cstr = PyString_AsString(script_obj);
+    } else if (PyUnicode_Check(script_obj)) {
+        script_str = PyUnicode_AsUTF8String(script_obj);
+        if (!script_str) {
+            return NULL;
+        }
+
+        script_cstr = PyString_AsString(script_str);
+    } else {
+        PyErr_SetString(PyExc_ValueError, "script argument must be unicode or string.");
+        return NULL;
+    }
+
+    /* commit first */
+    result = pysqlite_connection_commit(self->connection, NULL);
+    if (!result) {
+        goto error;
+    }
+    Py_DECREF(result);
+
+    while (1) {
+        Py_BEGIN_ALLOW_THREADS
+        rc = sqlite3_prepare(self->connection->db,
+                             script_cstr,
+                             -1,
+                             &statement,
+                             &script_cstr);
+        Py_END_ALLOW_THREADS
+        if (rc != SQLITE_OK) {
+            _pysqlite_seterror(self->connection->db, NULL);
+            goto error;
+        }
+
+        /* execute statement, and ignore results of SELECT statements */
+        rc = SQLITE_ROW;
+        while (rc == SQLITE_ROW) {
+            rc = pysqlite_step(statement, self->connection);
+            /* TODO: we probably need more error handling here */
+        }
+
+        if (rc != SQLITE_DONE) {
+            (void)sqlite3_finalize(statement);
+            _pysqlite_seterror(self->connection->db, NULL);
+            goto error;
+        }
+
+        rc = sqlite3_finalize(statement);
+        if (rc != SQLITE_OK) {
+            _pysqlite_seterror(self->connection->db, NULL);
+            goto error;
+        }
+
+        if (*script_cstr == (char)0) {
+            break;
+        }
+    }
+
+error:
+    Py_XDECREF(script_str);
+
+    if (PyErr_Occurred()) {
+        return NULL;
+    } else {
+        Py_INCREF(self);
+        return (PyObject*)self;
+    }
+}
+
+PyObject* pysqlite_cursor_getiter(pysqlite_Cursor *self)
+{
+    Py_INCREF(self);
+    return (PyObject*)self;
+}
+
+PyObject* pysqlite_cursor_iternext(pysqlite_Cursor *self)
+{
+    PyObject* next_row_tuple;
+    PyObject* next_row;
+    int rc;
+
+    if (!check_cursor(self)) {
+        return NULL;
+    }
+
+    if (self->reset) {
+        PyErr_SetString(pysqlite_InterfaceError, errmsg_fetch_across_rollback);
+        return NULL;
+    }
+
+    if (!self->next_row) {
+         if (self->statement) {
+            (void)pysqlite_statement_reset(self->statement);
+            Py_DECREF(self->statement);
+            self->statement = NULL;
+        }
+        return NULL;
+    }
+
+    next_row_tuple = self->next_row;
+    self->next_row = NULL;
+
+    if (self->row_factory != Py_None) {
+        next_row = PyObject_CallFunction(self->row_factory, "OO", self, next_row_tuple);
+        Py_DECREF(next_row_tuple);
+    } else {
+        next_row = next_row_tuple;
+    }
+
+    if (self->statement) {
+        rc = pysqlite_step(self->statement->st, self->connection);
+        if (rc != SQLITE_DONE && rc != SQLITE_ROW) {
+            (void)pysqlite_statement_reset(self->statement);
+            Py_DECREF(next_row);
+            _pysqlite_seterror(self->connection->db, NULL);
+            return NULL;
+        }
+
+        if (rc == SQLITE_ROW) {
+            self->next_row = _pysqlite_fetch_one_row(self);
+        }
+    }
+
+    return next_row;
+}
+
+PyObject* pysqlite_cursor_fetchone(pysqlite_Cursor* self, PyObject* args)
+{
+    PyObject* row;
+
+    row = pysqlite_cursor_iternext(self);
+    if (!row && !PyErr_Occurred()) {
+        Py_INCREF(Py_None);
+        return Py_None;
+    }
+
+    return row;
+}
+
+PyObject* pysqlite_cursor_fetchmany(pysqlite_Cursor* self, PyObject* args, PyObject* kwargs)
+{
+    static char *kwlist[] = {"size", NULL, NULL};
+
+    PyObject* row;
+    PyObject* list;
+    int maxrows = self->arraysize;
+    int counter = 0;
+
+    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|i:fetchmany", kwlist, &maxrows)) {
+        return NULL;
+    }
+
+    list = PyList_New(0);
+    if (!list) {
+        return NULL;
+    }
+
+    /* just make sure we enter the loop */
+    row = Py_None;
+
+    while (row) {
+        row = pysqlite_cursor_iternext(self);
+        if (row) {
+            PyList_Append(list, row);
+            Py_DECREF(row);
+        } else {
+            break;
+        }
+
+        if (++counter == maxrows) {
+            break;
+        }
+    }
+
+    if (PyErr_Occurred()) {
+        Py_DECREF(list);
+        return NULL;
+    } else {
+        return list;
+    }
+}
+
+PyObject* pysqlite_cursor_fetchall(pysqlite_Cursor* self, PyObject* args)
+{
+    PyObject* row;
+    PyObject* list;
+
+    list = PyList_New(0);
+    if (!list) {
+        return NULL;
+    }
+
+    /* just make sure we enter the loop */
+    row = (PyObject*)Py_None;
+
+    while (row) {
+        row = pysqlite_cursor_iternext(self);
+        if (row) {
+            PyList_Append(list, row);
+            Py_DECREF(row);
+        }
+    }
+
+    if (PyErr_Occurred()) {
+        Py_DECREF(list);
+        return NULL;
+    } else {
+        return list;
+    }
+}
+
+PyObject* pysqlite_noop(pysqlite_Connection* self, PyObject* args)
+{
+    /* don't care, return None */
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+PyObject* pysqlite_cursor_close(pysqlite_Cursor* self, PyObject* args)
+{
+    if (!pysqlite_check_thread(self->connection) || !pysqlite_check_connection(self->connection)) {
+        return NULL;
+    }
+
+    if (self->statement) {
+        (void)pysqlite_statement_reset(self->statement);
+        Py_CLEAR(self->statement);
+    }
+
+    self->closed = 1;
+
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+static PyMethodDef cursor_methods[] = {
+    {"execute", (PyCFunction)pysqlite_cursor_execute, METH_VARARGS,
+        PyDoc_STR("Executes a SQL statement.")},
+    {"executemany", (PyCFunction)pysqlite_cursor_executemany, METH_VARARGS,
+        PyDoc_STR("Repeatedly executes a SQL statement.")},
+    {"executescript", (PyCFunction)pysqlite_cursor_executescript, METH_VARARGS,
+        PyDoc_STR("Executes a multiple SQL statements at once. Non-standard.")},
+    {"fetchone", (PyCFunction)pysqlite_cursor_fetchone, METH_NOARGS,
+        PyDoc_STR("Fetches one row from the resultset.")},
+    {"fetchmany", (PyCFunction)pysqlite_cursor_fetchmany, METH_VARARGS|METH_KEYWORDS,
+        PyDoc_STR("Fetches several rows from the resultset.")},
+    {"fetchall", (PyCFunction)pysqlite_cursor_fetchall, METH_NOARGS,
+        PyDoc_STR("Fetches all rows from the resultset.")},
+    {"close", (PyCFunction)pysqlite_cursor_close, METH_NOARGS,
+        PyDoc_STR("Closes the cursor.")},
+    {"setinputsizes", (PyCFunction)pysqlite_noop, METH_VARARGS,
+        PyDoc_STR("Required by DB-API. Does nothing in pysqlite.")},
+    {"setoutputsize", (PyCFunction)pysqlite_noop, METH_VARARGS,
+        PyDoc_STR("Required by DB-API. Does nothing in pysqlite.")},
+    {NULL, NULL}
+};
+
+static struct PyMemberDef cursor_members[] =
+{
+    {"connection", T_OBJECT, offsetof(pysqlite_Cursor, connection), RO},
+    {"description", T_OBJECT, offsetof(pysqlite_Cursor, description), RO},
+    {"arraysize", T_INT, offsetof(pysqlite_Cursor, arraysize), 0},
+&nb