summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKali Kaneko <kali@futeisha.org>2013-02-04 19:20:12 +0900
committerKali Kaneko <kali@futeisha.org>2013-02-04 19:20:12 +0900
commit4189e53a881e52de0945375a72b8748143c5bd13 (patch)
tree23ed8e0600dc3c62da7a95e50f778c79a9be1e75
initial commit
-rw-r--r--.gitignore2
-rw-r--r--LICENSE19
-rw-r--r--MANIFEST.in8
-rw-r--r--PKG-INFO25
-rw-r--r--README.rst11
-rw-r--r--cross_bdist_wininst.py340
-rw-r--r--cross_bdist_wininst.pycbin0 -> 10283 bytes
-rw-r--r--doc/includes/sqlite3/adapter_datetime.py14
-rw-r--r--doc/includes/sqlite3/adapter_point_1.py16
-rw-r--r--doc/includes/sqlite3/adapter_point_2.py17
-rw-r--r--doc/includes/sqlite3/apsw_example.py12
-rw-r--r--doc/includes/sqlite3/authorizer.py26
-rw-r--r--doc/includes/sqlite3/collation_reverse.py15
-rw-r--r--doc/includes/sqlite3/complete_statement.py30
-rw-r--r--doc/includes/sqlite3/connect_db_1.py3
-rw-r--r--doc/includes/sqlite3/connect_db_2.py3
-rw-r--r--doc/includes/sqlite3/converter_point.py47
-rw-r--r--doc/includes/sqlite3/countcursors.py15
-rw-r--r--doc/includes/sqlite3/createdb.py28
-rw-r--r--doc/includes/sqlite3/ctx_manager.py19
-rw-r--r--doc/includes/sqlite3/execsql_fetchonerow.py17
-rw-r--r--doc/includes/sqlite3/execsql_printall_1.py13
-rw-r--r--doc/includes/sqlite3/execute_1.py11
-rw-r--r--doc/includes/sqlite3/execute_2.py12
-rw-r--r--doc/includes/sqlite3/execute_3.py12
-rw-r--r--doc/includes/sqlite3/executemany_1.py24
-rw-r--r--doc/includes/sqlite3/executemany_2.py15
-rw-r--r--doc/includes/sqlite3/executescript.py24
-rw-r--r--doc/includes/sqlite3/insert_more_people.py16
-rw-r--r--doc/includes/sqlite3/load_extension.py28
-rw-r--r--doc/includes/sqlite3/md5func.py11
-rw-r--r--doc/includes/sqlite3/mysumaggr.py20
-rw-r--r--doc/includes/sqlite3/parse_colnames.py8
-rw-r--r--doc/includes/sqlite3/progress.py29
-rw-r--r--doc/includes/sqlite3/pysqlite_datetime.py20
-rw-r--r--doc/includes/sqlite3/row_factory.py13
-rw-r--r--doc/includes/sqlite3/rowclass.py12
-rw-r--r--doc/includes/sqlite3/shared_cache.py6
-rw-r--r--doc/includes/sqlite3/shortcut_methods.py21
-rw-r--r--doc/includes/sqlite3/simple_tableprinter.py26
-rw-r--r--doc/includes/sqlite3/text_factory.py42
-rw-r--r--doc/install-source.txt147
-rw-r--r--doc/sphinx/.static/.keepme0
-rw-r--r--doc/sphinx/Makefile66
-rw-r--r--doc/sphinx/conf.py132
-rw-r--r--doc/sphinx/index.rst19
-rw-r--r--doc/sphinx/sqlite3.rst886
-rw-r--r--lib/__init__.py22
-rw-r--r--lib/dbapi2.py92
-rw-r--r--lib/dump.py63
-rw-r--r--lib/test/__init__.py50
-rw-r--r--lib/test/dbapi.py878
-rw-r--r--lib/test/dump.py52
-rw-r--r--lib/test/factory.py205
-rw-r--r--lib/test/hooks.py188
-rw-r--r--lib/test/py25/__init__.py1
-rw-r--r--lib/test/py25/py25tests.py80
-rw-r--r--lib/test/regression.py271
-rw-r--r--lib/test/transactions.py205
-rw-r--r--lib/test/types.py414
-rw-r--r--lib/test/userfunctions.py413
-rw-r--r--setup.cfg6
-rw-r--r--setup.py358
-rw-r--r--src/backup.h43
-rw-r--r--src/cache.c375
-rw-r--r--src/cache.h73
-rw-r--r--src/connection.c1704
-rw-r--r--src/connection.h138
-rw-r--r--src/cursor.c1126
-rw-r--r--src/cursor.h74
-rw-r--r--src/microprotocols.c142
-rw-r--r--src/microprotocols.h55
-rw-r--r--src/module.c464
-rw-r--r--src/module.h58
-rw-r--r--src/prepare_protocol.c84
-rw-r--r--src/prepare_protocol.h41
-rw-r--r--src/row.c256
-rw-r--r--src/row.h39
-rw-r--r--src/sqlitecompat.h63
-rw-r--r--src/statement.c543
-rw-r--r--src/statement.h59
-rw-r--r--src/util.c106
-rw-r--r--src/util.h38
83 files changed, 11059 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ba66091
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+amalgamation
+build
diff --git a/LICENSE b/LICENSE
new file mode 100644
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
index 0000000..d909e8e
--- /dev/null
+++ b/MANIFEST.in
@@ -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
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
index 0000000..2b9590d
--- /dev/null
+++ b/README.rst
@@ -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
index 0000000..0b40921
--- /dev/null
+++ b/cross_bdist_wininst.py
@@ -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
index 0000000..f9fb0af
--- /dev/null
+++ b/cross_bdist_wininst.pyc
Binary files differ
diff --git a/doc/includes/sqlite3/adapter_datetime.py b/doc/includes/sqlite3/adapter_datetime.py
new file mode 100644
index 0000000..5a43b02
--- /dev/null
+++ b/doc/includes/sqlite3/adapter_datetime.py
@@ -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
index 0000000..d9acb8d
--- /dev/null
+++ b/doc/includes/sqlite3/adapter_point_1.py
@@ -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
index 0000000..6ec58a8
--- /dev/null
+++ b/doc/includes/sqlite3/adapter_point_2.py
@@ -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
index 0000000..bdca0c9
--- /dev/null
+++ b/doc/includes/sqlite3/apsw_example.py
@@ -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
index 0000000..0176c6c
--- /dev/null
+++ b/doc/includes/sqlite3/authorizer.py
@@ -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
index 0000000..100fac9
--- /dev/null
+++ b/doc/includes/sqlite3/collation_reverse.py
@@ -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
index 0000000..2bb49d4
--- /dev/null
+++ b/doc/includes/sqlite3/complete_statement.py
@@ -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
index 0000000..360bf21
--- /dev/null
+++ b/doc/includes/sqlite3/connect_db_1.py
@@ -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
index 0000000..6899843
--- /dev/null
+++ b/doc/includes/sqlite3/connect_db_2.py
@@ -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
index 0000000..4ba0df5
--- /dev/null
+++ b/doc/includes/sqlite3/converter_point.py
@@ -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
index 0000000..9ba7614
--- /dev/null
+++ b/doc/includes/sqlite3/countcursors.py
@@ -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
index 0000000..28e9514
--- /dev/null
+++ b/doc/includes/sqlite3/createdb.py
@@ -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
index 0000000..2821e8f
--- /dev/null
+++ b/doc/includes/sqlite3/ctx_manager.py
@@ -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
index 0000000..e3aa578
--- /dev/null
+++ b/doc/includes/sqlite3/execsql_fetchonerow.py
@@ -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
index 0000000..62e48bd
--- /dev/null
+++ b/doc/includes/sqlite3/execsql_printall_1.py
@@ -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
index 0000000..70967ea
--- /dev/null
+++ b/doc/includes/sqlite3/execute_1.py
@@ -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
index 0000000..416b116
--- /dev/null
+++ b/doc/includes/sqlite3/execute_2.py
@@ -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
index 0000000..868be99
--- /dev/null
+++ b/doc/includes/sqlite3/execute_3.py
@@ -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
index 0000000..b076389
--- /dev/null
+++ b/doc/includes/sqlite3/executemany_1.py
@@ -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
index 0000000..9913909
--- /dev/null
+++ b/doc/includes/sqlite3/executemany_2.py
@@ -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
index 0000000..57c2613
--- /dev/null
+++ b/doc/includes/sqlite3/executescript.py
@@ -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
index 0000000..40600dc
--- /dev/null
+++ b/doc/includes/sqlite3/insert_more_people.py
@@ -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
index 0000000..d8df90f
--- /dev/null
+++ b/doc/includes/sqlite3/load_extension.py
@@ -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
index 0000000..5b8b983
--- /dev/null
+++ b/doc/includes/sqlite3/md5func.py
@@ -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
index 0000000..4fbcad5
--- /dev/null
+++ b/doc/includes/sqlite3/mysumaggr.py
@@ -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
index 0000000..702fa8d
--- /dev/null
+++ b/doc/includes/sqlite3/parse_colnames.py
@@ -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
index 0000000..b30941d
--- /dev/null
+++ b/doc/includes/sqlite3/progress.py
@@ -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
index 0000000..9075b46
--- /dev/null
+++ b/doc/includes/sqlite3/pysqlite_datetime.py
@@ -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
index 0000000..bfbc64d
--- /dev/null
+++ b/doc/includes/sqlite3/row_factory.py
@@ -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
index 0000000..e210ef2
--- /dev/null
+++ b/doc/includes/sqlite3/rowclass.py
@@ -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
index 0000000..98adf78
--- /dev/null
+++ b/doc/includes/sqlite3/shared_cache.py
@@ -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
index 0000000..fcfc631
--- /dev/null
+++ b/doc/includes/sqlite3/shortcut_methods.py
@@ -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
index 0000000..2237dc5
--- /dev/null
+++ b/doc/includes/sqlite3/simple_tableprinter.py
@@ -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
index 0000000..cb38d52
--- /dev/null
+++ b/doc/includes/sqlite3/text_factory.py
@@ -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
index 0000000..90a3ce7
--- /dev/null
+++ b/doc/install-source.txt
@@ -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
index 0000000..e69de29
--- /dev/null
+++ b/doc/sphinx/.static/.keepme
diff --git a/doc/sphinx/Makefile b/doc/sphinx/Makefile
new file mode 100644
index 0000000..bccfdaf
--- /dev/null
+++ b/doc/sphinx/Makefile
@@ -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
index 0000000..6528513
--- /dev/null
+++ b/doc/sphinx/conf.py
@@ -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
index 0000000..522f986
--- /dev/null
+++ b/doc/sphinx/index.rst
@@ -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
index 0000000..a0d5de8
--- /dev/null
+++ b/doc/sphinx/sqlite3.rst
@@ -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
index 0000000..3920a2b
--- /dev/null
+++ b/lib/__init__.py
@@ -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
index 0000000..78f340e
--- /dev/null
+++ b/lib/dbapi2.py
@@ -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
index 0000000..409a405
--- /dev/null
+++ b/lib/dump.py
@@ -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
index 0000000..1c760bb
--- /dev/null
+++ b/lib/test/__init__.py
@@ -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
index 0000000..05dfd4b
--- /dev/null
+++ b/lib/test/dbapi.py
@@ -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
index 0000000..b2f2d26
--- /dev/null
+++ b/lib/test/dump.py
@@ -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
index 0000000..a314d99
--- /dev/null
+++ b/lib/test/factory.py
@@ -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
index 0000000..5a38060
--- /dev/null
+++ b/lib/test/hooks.py
@@ -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
index 0000000..8b13789
--- /dev/null
+++ b/lib/test/py25/__init__.py
@@ -0,0 +1 @@
+
diff --git a/lib/test/py25/py25tests.py b/lib/test/py25/py25tests.py
new file mode 100644
index 0000000..ede1279
--- /dev/null
+++ b/lib/test/py25/py25tests.py
@@ -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
index 0000000..4003742
--- /dev/null
+++ b/lib/test/regression.py
@@ -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
index 0000000..4e3aa08
--- /dev/null
+++ b/lib/test/transactions.py
@@ -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
index 0000000..fc757be
--- /dev/null
+++ b/lib/test/types.py
@@ -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
index 0000000..6b60f60
--- /dev/null
+++ b/lib/test/userfunctions.py
@@ -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
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
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
index 0000000..10720f0
--- /dev/null
+++ b/src/backup.h
@@ -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
index 0000000..0653367
--- /dev/null
+++ b/src/cache.c
@@ -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
index 0000000..b09517c
--- /dev/null
+++ b/src/cache.h
@@ -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
index 0000000..481b9b5
--- /dev/null
+++ b/src/connection.c
@@ -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
index 0000000..3fc5a70
--- /dev/null
+++ b/src/connection.h
@@ -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
index 0000000..26e3307
--- /dev/null
+++ b/src/cursor.c
@@ -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},
+ {"lastrowid", T_OBJECT, offsetof(pysqlite_Cursor, lastrowid), RO},
+ {"rowcount", T_LONG, offsetof(pysqlite_Cursor, rowcount), RO},
+ {"row_factory", T_OBJECT, offsetof(pysqlite_Cursor, row_factory), 0},
+ {NULL}
+};
+
+static char cursor_doc[] =
+PyDoc_STR("SQLite database cursor class.");
+
+PyTypeObject pysqlite_CursorType = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ MODULE_NAME ".Cursor", /* tp_name */
+ sizeof(pysqlite_Cursor), /* tp_basicsize */
+ 0, /* tp_itemsize */
+ (destructor)pysqlite_cursor_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_HAVE_ITER|Py_TPFLAGS_BASETYPE|Py_TPFLAGS_HAVE_WEAKREFS, /* tp_flags */
+ cursor_doc, /* tp_doc */
+ 0, /* tp_traverse */
+ 0, /* tp_clear */
+ 0, /* tp_richcompare */
+ offsetof(pysqlite_Cursor, in_weakreflist), /* tp_weaklistoffset */
+ (getiterfunc)pysqlite_cursor_getiter, /* tp_iter */
+ (iternextfunc)pysqlite_cursor_iternext, /* tp_iternext */
+ cursor_methods, /* tp_methods */
+ cursor_members, /* tp_members */
+ 0, /* tp_getset */
+ 0, /* tp_base */
+ 0, /* tp_dict */
+ 0, /* tp_descr_get */
+ 0, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ (initproc)pysqlite_cursor_init, /* tp_init */
+ 0, /* tp_alloc */
+ 0, /* tp_new */
+ 0 /* tp_free */
+};
+
+extern int pysqlite_cursor_setup_types(void)
+{
+ pysqlite_CursorType.tp_new = PyType_GenericNew;
+ return PyType_Ready(&pysqlite_CursorType);
+}
diff --git a/src/cursor.h b/src/cursor.h
new file mode 100644
index 0000000..82f5972
--- /dev/null
+++ b/src/cursor.h
@@ -0,0 +1,74 @@
+/* cursor.h - definitions for 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.
+ */
+
+#ifndef PYSQLITE_CURSOR_H
+#define PYSQLITE_CURSOR_H
+#include "Python.h"
+
+#include "statement.h"
+#include "connection.h"
+#include "module.h"
+
+typedef struct
+{
+ PyObject_HEAD
+ pysqlite_Connection* connection;
+ PyObject* description;
+ PyObject* row_cast_map;
+ int arraysize;
+ PyObject* lastrowid;
+ long rowcount;
+ PyObject* row_factory;
+ pysqlite_Statement* statement;
+ int closed;
+ int reset;
+ int initialized;
+
+ /* the next row to be returned, NULL if no next row available */
+ PyObject* next_row;
+
+ PyObject* in_weakreflist; /* List of weak references */
+} pysqlite_Cursor;
+
+typedef enum {
+ STATEMENT_INVALID, STATEMENT_INSERT, STATEMENT_DELETE,
+ STATEMENT_UPDATE, STATEMENT_REPLACE, STATEMENT_SELECT,
+ STATEMENT_OTHER
+} pysqlite_StatementKind;
+
+extern PyTypeObject pysqlite_CursorType;
+
+PyObject* pysqlite_cursor_execute(pysqlite_Cursor* self, PyObject* args);
+PyObject* pysqlite_cursor_executemany(pysqlite_Cursor* self, PyObject* args);
+PyObject* pysqlite_cursor_getiter(pysqlite_Cursor *self);
+PyObject* pysqlite_cursor_iternext(pysqlite_Cursor *self);
+PyObject* pysqlite_cursor_fetchone(pysqlite_Cursor* self, PyObject* args);
+PyObject* pysqlite_cursor_fetchmany(pysqlite_Cursor* self, PyObject* args, PyObject* kwargs);
+PyObject* pysqlite_cursor_fetchall(pysqlite_Cursor* self, PyObject* args);
+PyObject* pysqlite_noop(pysqlite_Connection* self, PyObject* args);
+PyObject* pysqlite_cursor_close(pysqlite_Cursor* self, PyObject* args);
+
+int pysqlite_cursor_setup_types(void);
+
+#define UNKNOWN (-1)
+#endif
diff --git a/src/microprotocols.c b/src/microprotocols.c
new file mode 100644
index 0000000..c730afa
--- /dev/null
+++ b/src/microprotocols.c
@@ -0,0 +1,142 @@
+/* microprotocols.c - minimalist and non-validating protocols implementation
+ *
+ * Copyright (C) 2003-2004 Federico Di Gregorio <fog@debian.org>
+ *
+ * This file is part of psycopg and was adapted for pysqlite. Federico Di
+ * Gregorio gave the permission to use it within pysqlite under the following
+ * license:
+ *
+ * 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 <Python.h>
+#include <structmember.h>
+
+#include "cursor.h"
+#include "microprotocols.h"
+#include "prepare_protocol.h"
+
+
+/** the adapters registry **/
+
+PyObject *psyco_adapters;
+
+/* pysqlite_microprotocols_init - initialize the adapters dictionary */
+
+int
+pysqlite_microprotocols_init(PyObject *dict)
+{
+ /* create adapters dictionary and put it in module namespace */
+ if ((psyco_adapters = PyDict_New()) == NULL) {
+ return -1;
+ }
+
+ return PyDict_SetItemString(dict, "adapters", psyco_adapters);
+}
+
+
+/* pysqlite_microprotocols_add - add a reverse type-caster to the dictionary */
+
+int
+pysqlite_microprotocols_add(PyTypeObject *type, PyObject *proto, PyObject *cast)
+{
+ PyObject* key;
+ int rc;
+
+ if (proto == NULL) proto = (PyObject*)&pysqlite_PrepareProtocolType;
+
+ key = Py_BuildValue("(OO)", (PyObject*)type, proto);
+ if (!key) {
+ return -1;
+ }
+
+ rc = PyDict_SetItem(psyco_adapters, key, cast);
+ Py_DECREF(key);
+
+ return rc;
+}
+
+/* pysqlite_microprotocols_adapt - adapt an object to the built-in protocol */
+
+PyObject *
+pysqlite_microprotocols_adapt(PyObject *obj, PyObject *proto, PyObject *alt)
+{
+ PyObject *adapter, *key;
+
+ /* we don't check for exact type conformance as specified in PEP 246
+ because the pysqlite_PrepareProtocolType type is abstract and there is no
+ way to get a quotable object to be its instance */
+
+ /* look for an adapter in the registry */
+ key = Py_BuildValue("(OO)", (PyObject*)obj->ob_type, proto);
+ if (!key) {
+ return NULL;
+ }
+ adapter = PyDict_GetItem(psyco_adapters, key);
+ Py_DECREF(key);
+ if (adapter) {
+ PyObject *adapted = PyObject_CallFunctionObjArgs(adapter, obj, NULL);
+ return adapted;
+ }
+
+ /* try to have the protocol adapt this object*/
+ if (PyObject_HasAttrString(proto, "__adapt__")) {
+ PyObject *adapted = PyObject_CallMethod(proto, "__adapt__", "O", obj);
+ if (adapted) {
+ if (adapted != Py_None) {
+ return adapted;
+ } else {
+ Py_DECREF(adapted);
+ }
+ }
+
+ if (PyErr_Occurred() && !PyErr_ExceptionMatches(PyExc_TypeError))
+ return NULL;
+ }
+
+ /* and finally try to have the object adapt itself */
+ if (PyObject_HasAttrString(obj, "__conform__")) {
+ PyObject *adapted = PyObject_CallMethod(obj, "__conform__","O", proto);
+ if (adapted) {
+ if (adapted != Py_None) {
+ return adapted;
+ } else {
+ Py_DECREF(adapted);
+ }
+ }
+
+ if (PyErr_Occurred() && !PyErr_ExceptionMatches(PyExc_TypeError)) {
+ return NULL;
+ }
+ }
+
+ /* else set the right exception and return NULL */
+ PyErr_SetString(pysqlite_ProgrammingError, "can't adapt");
+ return NULL;
+}
+
+/** module-level functions **/
+
+PyObject *
+pysqlite_adapt(pysqlite_Cursor *self, PyObject *args)
+{
+ PyObject *obj, *alt = NULL;
+ PyObject *proto = (PyObject*)&pysqlite_PrepareProtocolType;
+
+ if (!PyArg_ParseTuple(args, "O|OO", &obj, &proto, &alt)) return NULL;
+ return pysqlite_microprotocols_adapt(obj, proto, alt);
+}
diff --git a/src/microprotocols.h b/src/microprotocols.h
new file mode 100644
index 0000000..3a9944f
--- /dev/null
+++ b/src/microprotocols.h
@@ -0,0 +1,55 @@
+/* microprotocols.c - definitions for minimalist and non-validating protocols
+ *
+ * Copyright (C) 2003-2004 Federico Di Gregorio <fog@debian.org>
+ *
+ * This file is part of psycopg and was adapted for pysqlite. Federico Di
+ * Gregorio gave the permission to use it within pysqlite under the following
+ * license:
+ *
+ * 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 PSYCOPG_MICROPROTOCOLS_H
+#define PSYCOPG_MICROPROTOCOLS_H 1
+
+#include <Python.h>
+
+/** adapters registry **/
+
+extern PyObject *psyco_adapters;
+
+/** the names of the three mandatory methods **/
+
+#define MICROPROTOCOLS_GETQUOTED_NAME "getquoted"
+#define MICROPROTOCOLS_GETSTRING_NAME "getstring"
+#define MICROPROTOCOLS_GETBINARY_NAME "getbinary"
+
+/** exported functions **/
+
+/* used by module.c to init the microprotocols system */
+extern int pysqlite_microprotocols_init(PyObject *dict);
+extern int pysqlite_microprotocols_add(
+ PyTypeObject *type, PyObject *proto, PyObject *cast);
+extern PyObject *pysqlite_microprotocols_adapt(
+ PyObject *obj, PyObject *proto, PyObject *alt);
+
+extern PyObject *
+ pysqlite_adapt(pysqlite_Cursor* self, PyObject *args);
+#define pysqlite_adapt_doc \
+ "adapt(obj, protocol, alternate) -> adapt obj to given protocol. Non-standard."
+
+#endif /* !defined(PSYCOPG_MICROPROTOCOLS_H) */
diff --git a/src/module.c b/src/module.c
new file mode 100644
index 0000000..dbc015c
--- /dev/null
+++ b/src/module.c
@@ -0,0 +1,464 @@
+/* module.c - the module itself
+ *
+ * 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 "connection.h"
+#include "statement.h"
+#include "cursor.h"
+#include "cache.h"
+#include "prepare_protocol.h"
+#include "microprotocols.h"
+#include "row.h"
+
+#ifdef PYSQLITE_EXPERIMENTAL
+#include "backup.h"
+#endif
+
+#if SQLITE_VERSION_NUMBER >= 3003003
+#define HAVE_SHARED_CACHE
+#endif
+
+/* static objects at module-level */
+
+PyObject* pysqlite_Error, *pysqlite_Warning, *pysqlite_InterfaceError, *pysqlite_DatabaseError,
+ *pysqlite_InternalError, *pysqlite_OperationalError, *pysqlite_ProgrammingError,
+ *pysqlite_IntegrityError, *pysqlite_DataError, *pysqlite_NotSupportedError, *pysqlite_OptimizedUnicode;
+
+PyObject* converters;
+int _enable_callback_tracebacks;
+int pysqlite_BaseTypeAdapted;
+
+static PyObject* module_connect(PyObject* self, PyObject* args, PyObject*
+ kwargs)
+{
+ /* Python seems to have no way of extracting a single keyword-arg at
+ * C-level, so this code is redundant with the one in connection_init in
+ * connection.c and must always be copied from there ... */
+
+ 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;
+ PyObject* factory = NULL;
+ int check_same_thread = 1;
+ int cached_statements;
+ double timeout = 5.0;
+
+ PyObject* result;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|diOiOi", kwlist,
+ &database, &timeout, &detect_types, &isolation_level, &check_same_thread, &factory, &cached_statements))
+ {
+ return NULL;
+ }
+
+ if (factory == NULL) {
+ factory = (PyObject*)&pysqlite_ConnectionType;
+ }
+
+ result = PyObject_Call(factory, args, kwargs);
+
+ return result;
+}
+
+PyDoc_STRVAR(module_connect_doc,
+"connect(database[, timeout, isolation_level, detect_types, factory])\n\
+\n\
+Opens a connection to the SQLite database file *database*. You can use\n\
+\":memory:\" to open a database connection to a database that resides in\n\
+RAM instead of on disk.");
+
+static PyObject* module_complete(PyObject* self, PyObject* args, PyObject*
+ kwargs)
+{
+ static char *kwlist[] = {"statement", NULL, NULL};
+ char* statement;
+
+ PyObject* result;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s", kwlist, &statement))
+ {
+ return NULL;
+ }
+
+ if (sqlite3_complete(statement)) {
+ result = Py_True;
+ } else {
+ result = Py_False;
+ }
+
+ Py_INCREF(result);
+
+ return result;
+}
+
+PyDoc_STRVAR(module_complete_doc,
+"complete_statement(sql)\n\
+\n\
+Checks if a string contains a complete SQL statement. Non-standard.");
+
+#ifdef HAVE_SHARED_CACHE
+static PyObject* module_enable_shared_cache(PyObject* self, PyObject* args, PyObject*
+ kwargs)
+{
+ static char *kwlist[] = {"do_enable", NULL, NULL};
+ int do_enable;
+ int rc;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "i", kwlist, &do_enable))
+ {
+ return NULL;
+ }
+
+ rc = sqlite3_enable_shared_cache(do_enable);
+
+ if (rc != SQLITE_OK) {
+ PyErr_SetString(pysqlite_OperationalError, "Changing the shared_cache flag failed");
+ return NULL;
+ } else {
+ Py_INCREF(Py_None);
+ return Py_None;
+ }
+}
+
+PyDoc_STRVAR(module_enable_shared_cache_doc,
+"enable_shared_cache(do_enable)\n\
+\n\
+Enable or disable shared cache mode for the calling thread.\n\
+Experimental/Non-standard.");
+#endif /* HAVE_SHARED_CACHE */
+
+static PyObject* module_register_adapter(PyObject* self, PyObject* args)
+{
+ PyTypeObject* type;
+ PyObject* caster;
+ int rc;
+
+ if (!PyArg_ParseTuple(args, "OO", &type, &caster)) {
+ return NULL;
+ }
+
+ /* a basic type is adapted; there's a performance optimization if that's not the case
+ * (99 % of all usages) */
+ if (type == &PyInt_Type || type == &PyLong_Type || type == &PyFloat_Type
+ || type == &PyString_Type || type == &PyUnicode_Type || type == &PyBuffer_Type) {
+ pysqlite_BaseTypeAdapted = 1;
+ }
+
+ rc = pysqlite_microprotocols_add(type, (PyObject*)&pysqlite_PrepareProtocolType, caster);
+ if (rc == -1)
+ return NULL;
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+PyDoc_STRVAR(module_register_adapter_doc,
+"register_adapter(type, callable)\n\
+\n\
+Registers an adapter with pysqlite's adapter registry. Non-standard.");
+
+static PyObject* module_register_converter(PyObject* self, PyObject* args)
+{
+ PyObject* orig_name;
+ PyObject* name = NULL;
+ PyObject* callable;
+ PyObject* retval = NULL;
+
+ if (!PyArg_ParseTuple(args, "SO", &orig_name, &callable)) {
+ return NULL;
+ }
+
+ /* convert the name to upper case */
+ name = PyObject_CallMethod(orig_name, "upper", "");
+ if (!name) {
+ goto error;
+ }
+
+ if (PyDict_SetItem(converters, name, callable) != 0) {
+ goto error;
+ }
+
+ Py_INCREF(Py_None);
+ retval = Py_None;
+error:
+ Py_XDECREF(name);
+ return retval;
+}
+
+PyDoc_STRVAR(module_register_converter_doc,
+"register_converter(typename, callable)\n\
+\n\
+Registers a converter with pysqlite. Non-standard.");
+
+static PyObject* enable_callback_tracebacks(PyObject* self, PyObject* args)
+{
+ if (!PyArg_ParseTuple(args, "i", &_enable_callback_tracebacks)) {
+ return NULL;
+ }
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+PyDoc_STRVAR(enable_callback_tracebacks_doc,
+"enable_callback_tracebacks(flag)\n\
+\n\
+Enable or disable callback functions throwing errors to stderr.");
+
+static void converters_init(PyObject* dict)
+{
+ converters = PyDict_New();
+ if (!converters) {
+ return;
+ }
+
+ PyDict_SetItemString(dict, "converters", converters);
+}
+
+static PyMethodDef module_methods[] = {
+ {"connect", (PyCFunction)module_connect,
+ METH_VARARGS | METH_KEYWORDS, module_connect_doc},
+ {"complete_statement", (PyCFunction)module_complete,
+ METH_VARARGS | METH_KEYWORDS, module_complete_doc},
+#ifdef HAVE_SHARED_CACHE
+ {"enable_shared_cache", (PyCFunction)module_enable_shared_cache,
+ METH_VARARGS | METH_KEYWORDS, module_enable_shared_cache_doc},
+#endif
+ {"register_adapter", (PyCFunction)module_register_adapter,
+ METH_VARARGS, module_register_adapter_doc},
+ {"register_converter", (PyCFunction)module_register_converter,
+ METH_VARARGS, module_register_converter_doc},
+ {"adapt", (PyCFunction)pysqlite_adapt, METH_VARARGS,
+ pysqlite_adapt_doc},
+ {"enable_callback_tracebacks", (PyCFunction)enable_callback_tracebacks,
+ METH_VARARGS, enable_callback_tracebacks_doc},
+ {NULL, NULL}
+};
+
+struct _IntConstantPair {
+ char* constant_name;
+ int constant_value;
+};
+
+typedef struct _IntConstantPair IntConstantPair;
+
+static IntConstantPair _int_constants[] = {
+ {"PARSE_DECLTYPES", PARSE_DECLTYPES},
+ {"PARSE_COLNAMES", PARSE_COLNAMES},
+
+ {"SQLITE_OK", SQLITE_OK},
+ {"SQLITE_DENY", SQLITE_DENY},
+ {"SQLITE_IGNORE", SQLITE_IGNORE},
+ {"SQLITE_CREATE_INDEX", SQLITE_CREATE_INDEX},
+ {"SQLITE_CREATE_TABLE", SQLITE_CREATE_TABLE},
+ {"SQLITE_CREATE_TEMP_INDEX", SQLITE_CREATE_TEMP_INDEX},
+ {"SQLITE_CREATE_TEMP_TABLE", SQLITE_CREATE_TEMP_TABLE},
+ {"SQLITE_CREATE_TEMP_TRIGGER", SQLITE_CREATE_TEMP_TRIGGER},
+ {"SQLITE_CREATE_TEMP_VIEW", SQLITE_CREATE_TEMP_VIEW},
+ {"SQLITE_CREATE_TRIGGER", SQLITE_CREATE_TRIGGER},
+ {"SQLITE_CREATE_VIEW", SQLITE_CREATE_VIEW},
+ {"SQLITE_DELETE", SQLITE_DELETE},
+ {"SQLITE_DROP_INDEX", SQLITE_DROP_INDEX},
+ {"SQLITE_DROP_TABLE", SQLITE_DROP_TABLE},
+ {"SQLITE_DROP_TEMP_INDEX", SQLITE_DROP_TEMP_INDEX},
+ {"SQLITE_DROP_TEMP_TABLE", SQLITE_DROP_TEMP_TABLE},
+ {"SQLITE_DROP_TEMP_TRIGGER", SQLITE_DROP_TEMP_TRIGGER},
+ {"SQLITE_DROP_TEMP_VIEW", SQLITE_DROP_TEMP_VIEW},
+ {"SQLITE_DROP_TRIGGER", SQLITE_DROP_TRIGGER},
+ {"SQLITE_DROP_VIEW", SQLITE_DROP_VIEW},
+ {"SQLITE_INSERT", SQLITE_INSERT},
+ {"SQLITE_PRAGMA", SQLITE_PRAGMA},
+ {"SQLITE_READ", SQLITE_READ},
+ {"SQLITE_SELECT", SQLITE_SELECT},
+ {"SQLITE_TRANSACTION", SQLITE_TRANSACTION},
+ {"SQLITE_UPDATE", SQLITE_UPDATE},
+ {"SQLITE_ATTACH", SQLITE_ATTACH},
+ {"SQLITE_DETACH", SQLITE_DETACH},
+#if SQLITE_VERSION_NUMBER >= 3002001
+ {"SQLITE_ALTER_TABLE", SQLITE_ALTER_TABLE},
+ {"SQLITE_REINDEX", SQLITE_REINDEX},
+#endif
+#if SQLITE_VERSION_NUMBER >= 3003000
+ {"SQLITE_ANALYZE", SQLITE_ANALYZE},
+#endif
+ {(char*)NULL, 0}
+};
+
+PyMODINIT_FUNC init_sqlite(void)
+{
+ PyObject *module, *dict;
+ PyObject *tmp_obj;
+ int i;
+
+ module = Py_InitModule("pysqlcipher._sqlite", module_methods);
+
+ if (!module ||
+ (pysqlite_row_setup_types() < 0) ||
+ (pysqlite_cursor_setup_types() < 0) ||
+ (pysqlite_connection_setup_types() < 0) ||
+ (pysqlite_cache_setup_types() < 0) ||
+ (pysqlite_statement_setup_types() < 0) ||
+ #ifdef PYSQLITE_EXPERIMENTAL
+ (pysqlite_backup_setup_types() < 0) ||
+ #endif
+ (pysqlite_prepare_protocol_setup_types() < 0)
+ ) {
+ return;
+ }
+
+ Py_INCREF(&pysqlite_ConnectionType);
+ PyModule_AddObject(module, "Connection", (PyObject*) &pysqlite_ConnectionType);
+ Py_INCREF(&pysqlite_CursorType);
+ PyModule_AddObject(module, "Cursor", (PyObject*) &pysqlite_CursorType);
+ Py_INCREF(&pysqlite_CacheType);
+ PyModule_AddObject(module, "Statement", (PyObject*)&pysqlite_StatementType);
+ Py_INCREF(&pysqlite_StatementType);
+ PyModule_AddObject(module, "Cache", (PyObject*) &pysqlite_CacheType);
+ Py_INCREF(&pysqlite_PrepareProtocolType);
+ PyModule_AddObject(module, "PrepareProtocol", (PyObject*) &pysqlite_PrepareProtocolType);
+ Py_INCREF(&pysqlite_RowType);
+ PyModule_AddObject(module, "Row", (PyObject*) &pysqlite_RowType);
+
+ if (!(dict = PyModule_GetDict(module))) {
+ goto error;
+ }
+
+ /*** Create DB-API Exception hierarchy */
+
+ if (!(pysqlite_Error = PyErr_NewException(MODULE_NAME ".Error", PyExc_StandardError, NULL))) {
+ goto error;
+ }
+ PyDict_SetItemString(dict, "Error", pysqlite_Error);
+
+ if (!(pysqlite_Warning = PyErr_NewException(MODULE_NAME ".Warning", PyExc_StandardError, NULL))) {
+ goto error;
+ }
+ PyDict_SetItemString(dict, "Warning", pysqlite_Warning);
+
+ /* Error subclasses */
+
+ if (!(pysqlite_InterfaceError = PyErr_NewException(MODULE_NAME ".InterfaceError", pysqlite_Error, NULL))) {
+ goto error;
+ }
+ PyDict_SetItemString(dict, "InterfaceError", pysqlite_InterfaceError);
+
+ if (!(pysqlite_DatabaseError = PyErr_NewException(MODULE_NAME ".DatabaseError", pysqlite_Error, NULL))) {
+ goto error;
+ }
+ PyDict_SetItemString(dict, "DatabaseError", pysqlite_DatabaseError);
+
+ /* pysqlite_DatabaseError subclasses */
+
+ if (!(pysqlite_InternalError = PyErr_NewException(MODULE_NAME ".InternalError", pysqlite_DatabaseError, NULL))) {
+ goto error;
+ }
+ PyDict_SetItemString(dict, "InternalError", pysqlite_InternalError);
+
+ if (!(pysqlite_OperationalError = PyErr_NewException(MODULE_NAME ".OperationalError", pysqlite_DatabaseError, NULL))) {
+ goto error;
+ }
+ PyDict_SetItemString(dict, "OperationalError", pysqlite_OperationalError);
+
+ if (!(pysqlite_ProgrammingError = PyErr_NewException(MODULE_NAME ".ProgrammingError", pysqlite_DatabaseError, NULL))) {
+ goto error;
+ }
+ PyDict_SetItemString(dict, "ProgrammingError", pysqlite_ProgrammingError);
+
+ if (!(pysqlite_IntegrityError = PyErr_NewException(MODULE_NAME ".IntegrityError", pysqlite_DatabaseError,NULL))) {
+ goto error;
+ }
+ PyDict_SetItemString(dict, "IntegrityError", pysqlite_IntegrityError);
+
+ if (!(pysqlite_DataError = PyErr_NewException(MODULE_NAME ".DataError", pysqlite_DatabaseError, NULL))) {
+ goto error;
+ }
+ PyDict_SetItemString(dict, "DataError", pysqlite_DataError);
+
+ if (!(pysqlite_NotSupportedError = PyErr_NewException(MODULE_NAME ".NotSupportedError", pysqlite_DatabaseError, NULL))) {
+ goto error;
+ }
+ PyDict_SetItemString(dict, "NotSupportedError", pysqlite_NotSupportedError);
+
+ /* We just need "something" unique for pysqlite_OptimizedUnicode. It does not really
+ * need to be a string subclass. Just anything that can act as a special
+ * marker for us. So I pulled PyCell_Type out of my magic hat.
+ */
+ Py_INCREF((PyObject*)&PyCell_Type);
+ pysqlite_OptimizedUnicode = (PyObject*)&PyCell_Type;
+ PyDict_SetItemString(dict, "OptimizedUnicode", pysqlite_OptimizedUnicode);
+
+ /* Set integer constants */
+ for (i = 0; _int_constants[i].constant_name != 0; i++) {
+ tmp_obj = PyInt_FromLong(_int_constants[i].constant_value);
+ if (!tmp_obj) {
+ goto error;
+ }
+ PyDict_SetItemString(dict, _int_constants[i].constant_name, tmp_obj);
+ Py_DECREF(tmp_obj);
+ }
+
+ if (!(tmp_obj = PyString_FromString(PYSQLITE_VERSION))) {
+ goto error;
+ }
+ PyDict_SetItemString(dict, "version", tmp_obj);
+ Py_DECREF(tmp_obj);
+
+ if (!(tmp_obj = PyString_FromString(sqlite3_libversion()))) {
+ goto error;
+ }
+ PyDict_SetItemString(dict, "sqlite_version", tmp_obj);
+ Py_DECREF(tmp_obj);
+
+ /* initialize microprotocols layer */
+ pysqlite_microprotocols_init(dict);
+
+ /* initialize the default converters */
+ converters_init(dict);
+
+ _enable_callback_tracebacks = 0;
+
+ pysqlite_BaseTypeAdapted = 0;
+
+ /* Original comment from _bsddb.c in the Python core. This is also still
+ * needed nowadays for Python 2.3/2.4.
+ *
+ * PyEval_InitThreads is called here due to a quirk in python 1.5
+ * - 2.2.1 (at least) according to Russell Williamson <merel@wt.net>:
+ * The global interpreter lock is not initialized until the first
+ * thread is created using thread.start_new_thread() or fork() is
+ * called. that would cause the ALLOW_THREADS here to segfault due
+ * to a null pointer reference if no threads or child processes
+ * have been created. This works around that and is a no-op if
+ * threads have already been initialized.
+ * (see pybsddb-users mailing list post on 2002-08-07)
+ */
+#ifdef WITH_THREAD
+ PyEval_InitThreads();
+#endif
+
+error:
+ if (PyErr_Occurred())
+ {
+ PyErr_SetString(PyExc_ImportError, "pysqlcipher._sqlite: init failed");
+ }
+}
diff --git a/src/module.h b/src/module.h
new file mode 100644
index 0000000..0b1dab3
--- /dev/null
+++ b/src/module.h
@@ -0,0 +1,58 @@
+/* module.h - definitions for the module
+ *
+ * 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_MODULE_H
+#define PYSQLITE_MODULE_H
+#include "Python.h"
+
+#define PYSQLITE_VERSION "2.6.3"
+
+extern PyObject* pysqlite_Error;
+extern PyObject* pysqlite_Warning;
+extern PyObject* pysqlite_InterfaceError;
+extern PyObject* pysqlite_DatabaseError;
+extern PyObject* pysqlite_InternalError;
+extern PyObject* pysqlite_OperationalError;
+extern PyObject* pysqlite_ProgrammingError;
+extern PyObject* pysqlite_IntegrityError;
+extern PyObject* pysqlite_DataError;
+extern PyObject* pysqlite_NotSupportedError;
+
+extern PyObject* pysqlite_OptimizedUnicode;
+
+/* the functions time.time() and time.sleep() */
+extern PyObject* time_time;
+extern PyObject* time_sleep;
+
+/* A dictionary, mapping colum types (INTEGER, VARCHAR, etc.) to converter
+ * functions, that convert the SQL value to the appropriate Python value.
+ * The key is uppercase.
+ */
+extern PyObject* converters;
+
+extern int _enable_callback_tracebacks;
+extern int pysqlite_BaseTypeAdapted;
+
+#define PARSE_DECLTYPES 1
+#define PARSE_COLNAMES 2
+#endif
diff --git a/src/prepare_protocol.c b/src/prepare_protocol.c
new file mode 100644
index 0000000..9ed25e1
--- /dev/null
+++ b/src/prepare_protocol.c
@@ -0,0 +1,84 @@
+/* prepare_protocol.c - the protocol for preparing values for SQLite
+ *
+ * Copyright (C) 2005-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 "prepare_protocol.h"
+
+int pysqlite_prepare_protocol_init(pysqlite_PrepareProtocol* self, PyObject* args, PyObject* kwargs)
+{
+ return 0;
+}
+
+void pysqlite_prepare_protocol_dealloc(pysqlite_PrepareProtocol* self)
+{
+ Py_TYPE(self)->tp_free((PyObject*)self);
+}
+
+PyTypeObject pysqlite_PrepareProtocolType= {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ MODULE_NAME ".PrepareProtocol", /* tp_name */
+ sizeof(pysqlite_PrepareProtocol), /* tp_basicsize */
+ 0, /* tp_itemsize */
+ (destructor)pysqlite_prepare_protocol_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, /* 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)pysqlite_prepare_protocol_init, /* tp_init */
+ 0, /* tp_alloc */
+ 0, /* tp_new */
+ 0 /* tp_free */
+};
+
+extern int pysqlite_prepare_protocol_setup_types(void)
+{
+ pysqlite_PrepareProtocolType.tp_new = PyType_GenericNew;
+ Py_TYPE(&pysqlite_PrepareProtocolType)= &PyType_Type;
+ return PyType_Ready(&pysqlite_PrepareProtocolType);
+}
diff --git a/src/prepare_protocol.h b/src/prepare_protocol.h
new file mode 100644
index 0000000..1cdf708
--- /dev/null
+++ b/src/prepare_protocol.h
@@ -0,0 +1,41 @@
+/* prepare_protocol.h - the protocol for preparing values for SQLite
+ *
+ * Copyright (C) 2005-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_PREPARE_PROTOCOL_H
+#define PYSQLITE_PREPARE_PROTOCOL_H
+#include "Python.h"
+
+typedef struct
+{
+ PyObject_HEAD
+} pysqlite_PrepareProtocol;
+
+extern PyTypeObject pysqlite_PrepareProtocolType;
+
+int pysqlite_prepare_protocol_init(pysqlite_PrepareProtocol* self, PyObject* args, PyObject* kwargs);
+void pysqlite_prepare_protocol_dealloc(pysqlite_PrepareProtocol* self);
+
+int pysqlite_prepare_protocol_setup_types(void);
+
+#define UNKNOWN (-1)
+#endif
diff --git a/src/row.c b/src/row.c
new file mode 100644
index 0000000..480b482
--- /dev/null
+++ b/src/row.c
@@ -0,0 +1,256 @@
+/* row.c - an enhanced tuple for database rows
+ *
+ * Copyright (C) 2005-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 "row.h"
+#include "cursor.h"
+#include "sqlitecompat.h"
+
+void pysqlite_row_dealloc(pysqlite_Row* self)
+{
+ Py_XDECREF(self->data);
+ Py_XDECREF(self->description);
+
+ Py_TYPE(self)->tp_free((PyObject*)self);
+}
+
+int pysqlite_row_init(pysqlite_Row* self, PyObject* args, PyObject* kwargs)
+{
+ PyObject* data;
+ pysqlite_Cursor* cursor;
+
+ self->data = 0;
+ self->description = 0;
+
+ if (!PyArg_ParseTuple(args, "OO", &cursor, &data)) {
+ return -1;
+ }
+
+ if (!PyObject_IsInstance((PyObject*)cursor, (PyObject*)&pysqlite_CursorType)) {
+ PyErr_SetString(PyExc_TypeError, "instance of cursor required for first argument");
+ return -1;
+ }
+
+ if (!PyTuple_Check(data)) {
+ PyErr_SetString(PyExc_TypeError, "tuple required for second argument");
+ return -1;
+ }
+
+ Py_INCREF(data);
+ self->data = data;
+
+ Py_INCREF(cursor->description);
+ self->description = cursor->description;
+
+ return 0;
+}
+
+PyObject* pysqlite_row_subscript(pysqlite_Row* self, PyObject* idx)
+{
+ long _idx;
+ char* key;
+ int nitems, i;
+ char* compare_key;
+
+ char* p1;
+ char* p2;
+
+ PyObject* item;
+
+ if (PyInt_Check(idx)) {
+ _idx = PyInt_AsLong(idx);
+ item = PyTuple_GetItem(self->data, _idx);
+ Py_XINCREF(item);
+ return item;
+ } else if (PyLong_Check(idx)) {
+ _idx = PyLong_AsLong(idx);
+ item = PyTuple_GetItem(self->data, _idx);
+ Py_XINCREF(item);
+ return item;
+ } else if (PyString_Check(idx)) {
+ key = PyString_AsString(idx);
+
+ nitems = PyTuple_Size(self->description);
+
+ for (i = 0; i < nitems; i++) {
+ compare_key = PyString_AsString(PyTuple_GET_ITEM(PyTuple_GET_ITEM(self->description, i), 0));
+ if (!compare_key) {
+ return NULL;
+ }
+
+ p1 = key;
+ p2 = compare_key;
+
+ while (1) {
+ if ((*p1 == (char)0) || (*p2 == (char)0)) {
+ break;
+ }
+
+ if ((*p1 | 0x20) != (*p2 | 0x20)) {
+ break;
+ }
+
+ p1++;
+ p2++;
+ }
+
+ if ((*p1 == (char)0) && (*p2 == (char)0)) {
+ /* found item */
+ item = PyTuple_GetItem(self->data, i);
+ Py_INCREF(item);
+ return item;
+ }
+
+ }
+
+ PyErr_SetString(PyExc_IndexError, "No item with that key");
+ return NULL;
+ } else if (PySlice_Check(idx)) {
+ PyErr_SetString(PyExc_ValueError, "slices not implemented, yet");
+ return NULL;
+ } else {
+ PyErr_SetString(PyExc_IndexError, "Index must be int or string");
+ return NULL;
+ }
+}
+
+Py_ssize_t pysqlite_row_length(pysqlite_Row* self, PyObject* args, PyObject* kwargs)
+{
+ return PyTuple_GET_SIZE(self->data);
+}
+
+PyObject* pysqlite_row_keys(pysqlite_Row* self, PyObject* args, PyObject* kwargs)
+{
+ PyObject* list;
+ int nitems, i;
+
+ list = PyList_New(0);
+ if (!list) {
+ return NULL;
+ }
+ nitems = PyTuple_Size(self->description);
+
+ for (i = 0; i < nitems; i++) {
+ if (PyList_Append(list, PyTuple_GET_ITEM(PyTuple_GET_ITEM(self->description, i), 0)) != 0) {
+ Py_DECREF(list);
+ return NULL;
+ }
+ }
+
+ return list;
+}
+
+static int pysqlite_row_print(pysqlite_Row* self, FILE *fp, int flags)
+{
+ return (&PyTuple_Type)->tp_print(self->data, fp, flags);
+}
+
+static PyObject* pysqlite_iter(pysqlite_Row* self)
+{
+ return PyObject_GetIter(self->data);
+}
+
+static long pysqlite_row_hash(pysqlite_Row *self)
+{
+ return PyObject_Hash(self->description) ^ PyObject_Hash(self->data);
+}
+
+static PyObject* pysqlite_row_richcompare(pysqlite_Row *self, PyObject *_other, int opid)
+{
+ if (opid != Py_EQ && opid != Py_NE) {
+ Py_INCREF(Py_NotImplemented);
+ return Py_NotImplemented;
+ }
+ if (PyType_IsSubtype(Py_TYPE(_other), &pysqlite_RowType)) {
+ pysqlite_Row *other = (pysqlite_Row *)_other;
+ PyObject *res = PyObject_RichCompare(self->description, other->description, opid);
+ if ((opid == Py_EQ && res == Py_True)
+ || (opid == Py_NE && res == Py_False)) {
+ Py_DECREF(res);
+ return PyObject_RichCompare(self->data, other->data, opid);
+ }
+ }
+ Py_INCREF(Py_NotImplemented);
+ return Py_NotImplemented;
+}
+
+PyMappingMethods pysqlite_row_as_mapping = {
+ /* mp_length */ (lenfunc)pysqlite_row_length,
+ /* mp_subscript */ (binaryfunc)pysqlite_row_subscript,
+ /* mp_ass_subscript */ (objobjargproc)0,
+};
+
+static PyMethodDef pysqlite_row_methods[] = {
+ {"keys", (PyCFunction)pysqlite_row_keys, METH_NOARGS,
+ PyDoc_STR("Returns the keys of the row.")},
+ {NULL, NULL}
+};
+
+
+PyTypeObject pysqlite_RowType = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ MODULE_NAME ".Row", /* tp_name */
+ sizeof(pysqlite_Row), /* tp_basicsize */
+ 0, /* tp_itemsize */
+ (destructor)pysqlite_row_dealloc, /* tp_dealloc */
+ (printfunc)pysqlite_row_print, /* 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 */
+ (hashfunc)pysqlite_row_hash, /* 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 */
+ (traverseproc)0, /* tp_traverse */
+ 0, /* tp_clear */
+ (richcmpfunc)pysqlite_row_richcompare, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ (getiterfunc)pysqlite_iter, /* tp_iter */
+ 0, /* tp_iternext */
+ pysqlite_row_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_row_init, /* tp_init */
+ 0, /* tp_alloc */
+ 0, /* tp_new */
+ 0 /* tp_free */
+};
+
+extern int pysqlite_row_setup_types(void)
+{
+ pysqlite_RowType.tp_new = PyType_GenericNew;
+ pysqlite_RowType.tp_as_mapping = &pysqlite_row_as_mapping;
+ return PyType_Ready(&pysqlite_RowType);
+}
diff --git a/src/row.h b/src/row.h
new file mode 100644
index 0000000..dd9b0c3
--- /dev/null
+++ b/src/row.h
@@ -0,0 +1,39 @@
+/* row.h - an enhanced tuple for database rows
+ *
+ * Copyright (C) 2005-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_ROW_H
+#define PYSQLITE_ROW_H
+#include "Python.h"
+
+typedef struct _Row
+{
+ PyObject_HEAD
+ PyObject* data;
+ PyObject* description;
+} pysqlite_Row;
+
+extern PyTypeObject pysqlite_RowType;
+
+int pysqlite_row_setup_types(void);
+
+#endif
diff --git a/src/sqlitecompat.h b/src/sqlitecompat.h
new file mode 100644
index 0000000..3408fc2
--- /dev/null
+++ b/src/sqlitecompat.h
@@ -0,0 +1,63 @@
+/* sqlitecompat.h - compatibility macros
+ *
+ * Copyright (C) 2006-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 "Python.h"
+
+#ifndef PYSQLITE_COMPAT_H
+#define PYSQLITE_COMPAT_H
+
+/* define Py_ssize_t for pre-2.5 versions of Python */
+
+#if PY_VERSION_HEX < 0x02050000
+typedef int Py_ssize_t;
+typedef int (*lenfunc)(PyObject*);
+#endif
+
+
+/* define PyDict_CheckExact for pre-2.4 versions of Python */
+#ifndef PyDict_CheckExact
+#define PyDict_CheckExact(op) ((op)->ob_type == &PyDict_Type)
+#endif
+
+/* define Py_CLEAR for pre-2.4 versions of Python */
+#ifndef Py_CLEAR
+#define Py_CLEAR(op) \
+ do { \
+ if (op) { \
+ PyObject *tmp = (PyObject *)(op); \
+ (op) = NULL; \
+ Py_DECREF(tmp); \
+ } \
+ } while (0)
+#endif
+
+#ifndef PyVarObject_HEAD_INIT
+#define PyVarObject_HEAD_INIT(type, size) \
+ PyObject_HEAD_INIT(type) size,
+#endif
+
+#ifndef Py_TYPE
+#define Py_TYPE(ob) ((ob)->ob_type)
+#endif
+
+#endif
diff --git a/src/statement.c b/src/statement.c
new file mode 100644
index 0000000..9d08f2b
--- /dev/null
+++ b/src/statement.c
@@ -0,0 +1,543 @@
+/* statement.c - the statement type
+ *
+ * Copyright (C) 2005-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 "statement.h"
+#include "cursor.h"
+#include "connection.h"
+#include "microprotocols.h"
+#include "prepare_protocol.h"
+#include "sqlitecompat.h"
+
+/* prototypes */
+static int pysqlite_check_remaining_sql(const char* tail);
+
+typedef enum {
+ LINECOMMENT_1,
+ IN_LINECOMMENT,
+ COMMENTSTART_1,
+ IN_COMMENT,
+ COMMENTEND_1,
+ NORMAL
+} parse_remaining_sql_state;
+
+typedef enum {
+ TYPE_INT,
+ TYPE_LONG,
+ TYPE_FLOAT,
+ TYPE_STRING,
+ TYPE_UNICODE,
+ TYPE_BUFFER,
+ TYPE_UNKNOWN
+} parameter_type;
+
+int pysqlite_statement_create(pysqlite_Statement* self, pysqlite_Connection* connection, PyObject* sql)
+{
+ const char* tail;
+ int rc;
+ PyObject* sql_str;
+ char* sql_cstr;
+
+ self->st = NULL;
+ self->in_use = 0;
+
+ if (PyString_Check(sql)) {
+ sql_str = sql;
+ Py_INCREF(sql_str);
+ } else if (PyUnicode_Check(sql)) {
+ sql_str = PyUnicode_AsUTF8String(sql);
+ if (!sql_str) {
+ rc = PYSQLITE_SQL_WRONG_TYPE;
+ return rc;
+ }
+ } else {
+ rc = PYSQLITE_SQL_WRONG_TYPE;
+ return rc;
+ }
+
+ self->in_weakreflist = NULL;
+ self->sql = sql_str;
+
+ sql_cstr = PyString_AsString(sql_str);
+
+ Py_BEGIN_ALLOW_THREADS
+ rc = sqlite3_prepare(connection->db,
+ sql_cstr,
+ -1,
+ &self->st,
+ &tail);
+ Py_END_ALLOW_THREADS
+
+ self->db = connection->db;
+
+ if (rc == SQLITE_OK && pysqlite_check_remaining_sql(tail)) {
+ (void)sqlite3_finalize(self->st);
+ self->st = NULL;
+ rc = PYSQLITE_TOO_MUCH_SQL;
+ }
+
+ return rc;
+}
+
+int pysqlite_statement_bind_parameter(pysqlite_Statement* self, int pos, PyObject* parameter, int allow_8bit_chars)
+{
+ int rc = SQLITE_OK;
+ long longval;
+ PY_LONG_LONG longlongval;
+ const char* buffer;
+ char* string;
+ Py_ssize_t buflen;
+ PyObject* stringval;
+ parameter_type paramtype;
+ char* c;
+
+ if (parameter == Py_None) {
+ rc = sqlite3_bind_null(self->st, pos);
+ goto final;
+ }
+
+ if (PyInt_CheckExact(parameter)) {
+ paramtype = TYPE_INT;
+ } else if (PyLong_CheckExact(parameter)) {
+ paramtype = TYPE_LONG;
+ } else if (PyFloat_CheckExact(parameter)) {
+ paramtype = TYPE_FLOAT;
+ } else if (PyString_CheckExact(parameter)) {
+ paramtype = TYPE_STRING;
+ } else if (PyUnicode_CheckExact(parameter)) {
+ paramtype = TYPE_UNICODE;
+ } else if (PyBuffer_Check(parameter)) {
+ paramtype = TYPE_BUFFER;
+ } else if (PyInt_Check(parameter)) {
+ paramtype = TYPE_INT;
+ } else if (PyLong_Check(parameter)) {
+ paramtype = TYPE_LONG;
+ } else if (PyFloat_Check(parameter)) {
+ paramtype = TYPE_FLOAT;
+ } else if (PyString_Check(parameter)) {
+ paramtype = TYPE_STRING;
+ } else if (PyUnicode_Check(parameter)) {
+ paramtype = TYPE_UNICODE;
+ } else {
+ paramtype = TYPE_UNKNOWN;
+ }
+
+ if (paramtype == TYPE_STRING && !allow_8bit_chars) {
+ string = PyString_AS_STRING(parameter);
+ for (c = string; *c != 0; c++) {
+ if (*c & 0x80) {
+ PyErr_SetString(pysqlite_ProgrammingError, "You must not use 8-bit bytestrings unless you use a text_factory that can interpret 8-bit bytestrings (like text_factory = str). It is highly recommended that you instead just switch your application to Unicode strings.");
+ rc = -1;
+ goto final;
+ }
+ }
+ }
+
+ switch (paramtype) {
+ case TYPE_INT:
+ longval = PyInt_AsLong(parameter);
+ rc = sqlite3_bind_int64(self->st, pos, (sqlite_int64)longval);
+ break;
+ case TYPE_LONG:
+ longlongval = PyLong_AsLongLong(parameter);
+ /* in the overflow error case, longlongval is -1, and an exception is set */
+ rc = sqlite3_bind_int64(self->st, pos, (sqlite_int64)longlongval);
+ break;
+ case TYPE_FLOAT:
+ rc = sqlite3_bind_double(self->st, pos, PyFloat_AsDouble(parameter));
+ break;
+ case TYPE_STRING:
+ string = PyString_AS_STRING(parameter);
+ rc = sqlite3_bind_text(self->st, pos, string, -1, SQLITE_TRANSIENT);
+ break;
+ case TYPE_UNICODE:
+ stringval = PyUnicode_AsUTF8String(parameter);
+ string = PyString_AsString(stringval);
+ rc = sqlite3_bind_text(self->st, pos, string, -1, SQLITE_TRANSIENT);
+ Py_DECREF(stringval);
+ break;
+ case TYPE_BUFFER:
+ if (PyObject_AsCharBuffer(parameter, &buffer, &buflen) == 0) {
+ rc = sqlite3_bind_blob(self->st, pos, buffer, buflen, SQLITE_TRANSIENT);
+ } else {
+ PyErr_SetString(PyExc_ValueError, "could not convert BLOB to buffer");
+ rc = -1;
+ }
+ break;
+ case TYPE_UNKNOWN:
+ rc = -1;
+ }
+
+final:
+ return rc;
+}
+
+/* returns 0 if the object is one of Python's internal ones that don't need to be adapted */
+static int _need_adapt(PyObject* obj)
+{
+ if (pysqlite_BaseTypeAdapted) {
+ return 1;
+ }
+
+ if (PyInt_CheckExact(obj) || PyLong_CheckExact(obj)
+ || PyFloat_CheckExact(obj) || PyString_CheckExact(obj)
+ || PyUnicode_CheckExact(obj) || PyBuffer_Check(obj)) {
+ return 0;
+ } else {
+ return 1;
+ }
+}
+
+void pysqlite_statement_bind_parameters(pysqlite_Statement* self, PyObject* parameters, int allow_8bit_chars)
+{
+ PyObject* current_param;
+ PyObject* adapted;
+ const char* binding_name;
+ int i;
+ int rc;
+ int num_params_needed;
+ int num_params;
+
+ Py_BEGIN_ALLOW_THREADS
+ num_params_needed = sqlite3_bind_parameter_count(self->st);
+ Py_END_ALLOW_THREADS
+
+ if (PyTuple_CheckExact(parameters) || PyList_CheckExact(parameters) || (!PyDict_Check(parameters) && PySequence_Check(parameters))) {
+ /* parameters passed as sequence */
+ if (PyTuple_CheckExact(parameters)) {
+ num_params = PyTuple_GET_SIZE(parameters);
+ } else if (PyList_CheckExact(parameters)) {
+ num_params = PyList_GET_SIZE(parameters);
+ } else {
+ num_params = PySequence_Size(parameters);
+ }
+ if (num_params != num_params_needed) {
+ PyErr_Format(pysqlite_ProgrammingError, "Incorrect number of bindings supplied. The current statement uses %d, and there are %d supplied.",
+ num_params_needed, num_params);
+ return;
+ }
+ for (i = 0; i < num_params; i++) {
+ if (PyTuple_CheckExact(parameters)) {
+ current_param = PyTuple_GET_ITEM(parameters, i);
+ Py_XINCREF(current_param);
+ } else if (PyList_CheckExact(parameters)) {
+ current_param = PyList_GET_ITEM(parameters, i);
+ Py_XINCREF(current_param);
+ } else {
+ current_param = PySequence_GetItem(parameters, i);
+ }
+ if (!current_param) {
+ return;
+ }
+
+ if (!_need_adapt(current_param)) {
+ adapted = current_param;
+ } else {
+ adapted = pysqlite_microprotocols_adapt(current_param, (PyObject*)&pysqlite_PrepareProtocolType, NULL);
+ if (adapted) {
+ Py_DECREF(current_param);
+ } else {
+ PyErr_Clear();
+ adapted = current_param;
+ }
+ }
+
+ rc = pysqlite_statement_bind_parameter(self, i + 1, adapted, allow_8bit_chars);
+ Py_DECREF(adapted);
+
+ if (rc != SQLITE_OK) {
+ if (!PyErr_Occurred()) {
+ PyErr_Format(pysqlite_InterfaceError, "Error binding parameter %d - probably unsupported type.", i);
+ }
+ return;
+ }
+ }
+ } else if (PyDict_Check(parameters)) {
+ /* parameters passed as dictionary */
+ for (i = 1; i <= num_params_needed; i++) {
+ Py_BEGIN_ALLOW_THREADS
+ binding_name = sqlite3_bind_parameter_name(self->st, i);
+ Py_END_ALLOW_THREADS
+ if (!binding_name) {
+ PyErr_Format(pysqlite_ProgrammingError, "Binding %d has no name, but you supplied a dictionary (which has only names).", i);
+ return;
+ }
+
+ binding_name++; /* skip first char (the colon) */
+ if (PyDict_CheckExact(parameters)) {
+ current_param = PyDict_GetItemString(parameters, binding_name);
+ Py_XINCREF(current_param);
+ } else {
+ current_param = PyMapping_GetItemString(parameters, (char*)binding_name);
+ }
+ if (!current_param) {
+ PyErr_Format(pysqlite_ProgrammingError, "You did not supply a value for binding %d.", i);
+ return;
+ }
+
+ if (!_need_adapt(current_param)) {
+ adapted = current_param;
+ } else {
+ adapted = pysqlite_microprotocols_adapt(current_param, (PyObject*)&pysqlite_PrepareProtocolType, NULL);
+ if (adapted) {
+ Py_DECREF(current_param);
+ } else {
+ PyErr_Clear();
+ adapted = current_param;
+ }
+ }
+
+ rc = pysqlite_statement_bind_parameter(self, i, adapted, allow_8bit_chars);
+ Py_DECREF(adapted);
+
+ if (rc != SQLITE_OK) {
+ if (!PyErr_Occurred()) {
+ PyErr_Format(pysqlite_InterfaceError, "Error binding parameter :%s - probably unsupported type.", binding_name);
+ }
+ return;
+ }
+ }
+ } else {
+ PyErr_SetString(PyExc_ValueError, "parameters are of unsupported type");
+ }
+}
+
+int pysqlite_statement_recompile(pysqlite_Statement* self, PyObject* params)
+{
+ const char* tail;
+ int rc;
+ char* sql_cstr;
+ sqlite3_stmt* new_st;
+
+ sql_cstr = PyString_AsString(self->sql);
+
+ Py_BEGIN_ALLOW_THREADS
+ rc = sqlite3_prepare(self->db,
+ sql_cstr,
+ -1,
+ &new_st,
+ &tail);
+ Py_END_ALLOW_THREADS
+
+ if (rc == SQLITE_OK) {
+ /* The efficient sqlite3_transfer_bindings is only available in SQLite
+ * version 3.2.2 or later. For older SQLite releases, that might not
+ * even define SQLITE_VERSION_NUMBER, we do it the manual way.
+ */
+ #ifdef SQLITE_VERSION_NUMBER
+ #if SQLITE_VERSION_NUMBER >= 3002002
+ /* The check for the number of parameters is necessary to not trigger a
+ * bug in certain SQLite versions (experienced in 3.2.8 and 3.3.4). */
+ if (sqlite3_bind_parameter_count(self->st) > 0) {
+ (void)sqlite3_transfer_bindings(self->st, new_st);
+ }
+ #endif
+ #else
+ statement_bind_parameters(self, params);
+ #endif
+
+ (void)sqlite3_finalize(self->st);
+ self->st = new_st;
+ }
+
+ return rc;
+}
+
+int pysqlite_statement_finalize(pysqlite_Statement* self)
+{
+ int rc;
+
+ rc = SQLITE_OK;
+ if (self->st) {
+ Py_BEGIN_ALLOW_THREADS
+ rc = sqlite3_finalize(self->st);
+ Py_END_ALLOW_THREADS
+ self->st = NULL;
+ }
+
+ self->in_use = 0;
+
+ return rc;
+}
+
+int pysqlite_statement_reset(pysqlite_Statement* self)
+{
+ int rc;
+
+ rc = SQLITE_OK;
+
+ if (self->in_use && self->st) {
+ Py_BEGIN_ALLOW_THREADS
+ rc = sqlite3_reset(self->st);
+ Py_END_ALLOW_THREADS
+
+ if (rc == SQLITE_OK) {
+ self->in_use = 0;
+ }
+ }
+
+ return rc;
+}
+
+void pysqlite_statement_mark_dirty(pysqlite_Statement* self)
+{
+ self->in_use = 1;
+}
+
+void pysqlite_statement_dealloc(pysqlite_Statement* self)
+{
+ int rc;
+
+ if (self->st) {
+ Py_BEGIN_ALLOW_THREADS
+ rc = sqlite3_finalize(self->st);
+ Py_END_ALLOW_THREADS
+ }
+
+ self->st = NULL;
+
+ Py_XDECREF(self->sql);
+
+ if (self->in_weakreflist != NULL) {
+ PyObject_ClearWeakRefs((PyObject*)self);
+ }
+
+ Py_TYPE(self)->tp_free((PyObject*)self);
+}
+
+/*
+ * Checks if there is anything left in an SQL string after SQLite compiled it.
+ * This is used to check if somebody tried to execute more than one SQL command
+ * with one execute()/executemany() command, which the DB-API and we don't
+ * allow.
+ *
+ * Returns 1 if there is more left than should be. 0 if ok.
+ */
+static int pysqlite_check_remaining_sql(const char* tail)
+{
+ const char* pos = tail;
+
+ parse_remaining_sql_state state = NORMAL;
+
+ for (;;) {
+ switch (*pos) {
+ case 0:
+ return 0;
+ case '-':
+ if (state == NORMAL) {
+ state = LINECOMMENT_1;
+ } else if (state == LINECOMMENT_1) {
+ state = IN_LINECOMMENT;
+ }
+ break;
+ case ' ':
+ case '\t':
+ break;
+ case '\n':
+ case 13:
+ if (state == IN_LINECOMMENT) {
+ state = NORMAL;
+ }
+ break;
+ case '/':
+ if (state == NORMAL) {
+ state = COMMENTSTART_1;
+ } else if (state == COMMENTEND_1) {
+ state = NORMAL;
+ } else if (state == COMMENTSTART_1) {
+ return 1;
+ }
+ break;
+ case '*':
+ if (state == NORMAL) {
+ return 1;
+ } else if (state == LINECOMMENT_1) {
+ return 1;
+ } else if (state == COMMENTSTART_1) {
+ state = IN_COMMENT;
+ } else if (state == IN_COMMENT) {
+ state = COMMENTEND_1;
+ }
+ break;
+ default:
+ if (state == COMMENTEND_1) {
+ state = IN_COMMENT;
+ } else if (state == IN_LINECOMMENT) {
+ } else if (state == IN_COMMENT) {
+ } else {
+ return 1;
+ }
+ }
+
+ pos++;
+ }
+
+ return 0;
+}
+
+PyTypeObject pysqlite_StatementType = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ MODULE_NAME ".Statement", /* tp_name */
+ sizeof(pysqlite_Statement), /* tp_basicsize */
+ 0, /* tp_itemsize */
+ (destructor)pysqlite_statement_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_HAVE_WEAKREFS, /* tp_flags */
+ 0, /* tp_doc */
+ 0, /* tp_traverse */
+ 0, /* tp_clear */
+ 0, /* tp_richcompare */
+ offsetof(pysqlite_Statement, in_weakreflist), /* 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 */
+};
+
+extern int pysqlite_statement_setup_types(void)
+{
+ pysqlite_StatementType.tp_new = PyType_GenericNew;
+ return PyType_Ready(&pysqlite_StatementType);
+}
diff --git a/src/statement.h b/src/statement.h
new file mode 100644
index 0000000..05fd5ff
--- /dev/null
+++ b/src/statement.h
@@ -0,0 +1,59 @@
+/* statement.h - definitions for the statement type
+ *
+ * Copyright (C) 2005-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_STATEMENT_H
+#define PYSQLITE_STATEMENT_H
+#include "Python.h"
+
+#include "connection.h"
+#include "sqlite3.h"
+
+#define PYSQLITE_TOO_MUCH_SQL (-100)
+#define PYSQLITE_SQL_WRONG_TYPE (-101)
+
+typedef struct
+{
+ PyObject_HEAD
+ sqlite3* db;
+ sqlite3_stmt* st;
+ PyObject* sql;
+ int in_use;
+ PyObject* in_weakreflist; /* List of weak references */
+} pysqlite_Statement;
+
+extern PyTypeObject pysqlite_StatementType;
+
+int pysqlite_statement_create(pysqlite_Statement* self, pysqlite_Connection* connection, PyObject* sql);
+void pysqlite_statement_dealloc(pysqlite_Statement* self);
+
+int pysqlite_statement_bind_parameter(pysqlite_Statement* self, int pos, PyObject* parameter, int allow_8bit_chars);
+void pysqlite_statement_bind_parameters(pysqlite_Statement* self, PyObject* parameters, int allow_8bit_chars);
+
+int pysqlite_statement_recompile(pysqlite_Statement* self, PyObject* parameters);
+int pysqlite_statement_finalize(pysqlite_Statement* self);
+int pysqlite_statement_reset(pysqlite_Statement* self);
+void pysqlite_statement_mark_dirty(pysqlite_Statement* self);
+
+int pysqlite_statement_setup_types(void);
+
+#endif
diff --git a/src/util.c b/src/util.c
new file mode 100644
index 0000000..6b57b76
--- /dev/null
+++ b/src/util.c
@@ -0,0 +1,106 @@
+/* util.c - various utility functions
+ *
+ * Copyright (C) 2005-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 "module.h"
+#include "connection.h"
+
+int pysqlite_step(sqlite3_stmt* statement, pysqlite_Connection* connection)
+{
+ int rc;
+
+ if (statement == NULL) {
+ /* this is a workaround for SQLite 3.5 and later. it now apparently
+ * returns NULL for "no-operation" statements */
+ rc = SQLITE_OK;
+ } else {
+ Py_BEGIN_ALLOW_THREADS
+ rc = sqlite3_step(statement);
+ Py_END_ALLOW_THREADS
+ }
+
+ return rc;
+}
+
+/**
+ * Checks the SQLite error code and sets the appropriate DB-API exception.
+ * Returns the error code (0 means no error occurred).
+ */
+int _pysqlite_seterror(sqlite3* db, sqlite3_stmt* st)
+{
+ int errorcode;
+
+ /* SQLite often doesn't report anything useful, unless you reset the statement first */
+ if (st != NULL) {
+ (void)sqlite3_reset(st);
+ }
+
+ errorcode = sqlite3_errcode(db);
+
+ switch (errorcode)
+ {
+ case SQLITE_OK:
+ PyErr_Clear();
+ break;
+ case SQLITE_INTERNAL:
+ case SQLITE_NOTFOUND:
+ PyErr_SetString(pysqlite_InternalError, sqlite3_errmsg(db));
+ break;
+ case SQLITE_NOMEM:
+ (void)PyErr_NoMemory();
+ break;
+ case SQLITE_ERROR:
+ case SQLITE_PERM:
+ case SQLITE_ABORT:
+ case SQLITE_BUSY:
+ case SQLITE_LOCKED:
+ case SQLITE_READONLY:
+ case SQLITE_INTERRUPT:
+ case SQLITE_IOERR:
+ case SQLITE_FULL:
+ case SQLITE_CANTOPEN:
+ case SQLITE_PROTOCOL:
+ case SQLITE_EMPTY:
+ case SQLITE_SCHEMA:
+ PyErr_SetString(pysqlite_OperationalError, sqlite3_errmsg(db));
+ break;
+ case SQLITE_CORRUPT:
+ PyErr_SetString(pysqlite_DatabaseError, sqlite3_errmsg(db));
+ break;
+ case SQLITE_TOOBIG:
+ PyErr_SetString(pysqlite_DataError, sqlite3_errmsg(db));
+ break;
+ case SQLITE_CONSTRAINT:
+ case SQLITE_MISMATCH:
+ PyErr_SetString(pysqlite_IntegrityError, sqlite3_errmsg(db));
+ break;
+ case SQLITE_MISUSE:
+ PyErr_SetString(pysqlite_ProgrammingError, sqlite3_errmsg(db));
+ break;
+ default:
+ PyErr_SetString(pysqlite_DatabaseError, sqlite3_errmsg(db));
+ break;
+ }
+
+ return errorcode;
+}
+
diff --git a/src/util.h b/src/util.h
new file mode 100644
index 0000000..4269003
--- /dev/null
+++ b/src/util.h
@@ -0,0 +1,38 @@
+/* util.h - various utility functions
+ *
+ * Copyright (C) 2005-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_UTIL_H
+#define PYSQLITE_UTIL_H
+#include "Python.h"
+#include "pythread.h"
+#include "sqlite3.h"
+#include "connection.h"
+
+int pysqlite_step(sqlite3_stmt* statement, pysqlite_Connection* connection);
+
+/**
+ * Checks the SQLite error code and sets the appropriate DB-API exception.
+ * Returns the error code (0 means no error occurred).
+ */
+int _pysqlite_seterror(sqlite3* db, sqlite3_stmt* st);
+#endif