Imported Upstream version 2.5.5 upstream/2.5.5
authorJoel Rosdahl <joel@debian.org>
Mon, 11 Jan 2010 20:47:03 +0000 (21:47 +0100)
committerJoel Rosdahl <joel@debian.org>
Mon, 11 Jan 2010 20:47:03 +0000 (21:47 +0100)
20 files changed:
MANIFEST.in
PKG-INFO
doc/includes/sqlite3/load_extension.py
doc/install-source-win32.txt [deleted file]
doc/install-source.txt
doc/sphinx/sqlite3.rst
extended_setup.py [deleted file]
ez_setup.py [deleted file]
pysqlite2/test/dbapi.py
pysqlite2/test/dump.py
pysqlite2/test/regression.py
pysqlite2/test/transactions.py
pysqlite2/test/types.py
setup.cfg
setup.py
src/connection.c
src/connection.h
src/cursor.c
src/cursor.h
src/module.h

index 3ddb093..d909e8e 100644 (file)
@@ -1,7 +1,5 @@
 include MANIFEST.in
 include LICENSE
-include ez_setup.py
-include extended_setup.py
 include cross_bdist_wininst.py
 include doc/*.txt
 include doc/includes/sqlite3/*.py
index 6a6d1bf..237b4b9 100644 (file)
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,12 +1,12 @@
 Metadata-Version: 1.0
 Name: pysqlite
-Version: 2.5.0
+Version: 2.5.5
 Summary: DB-API 2.0 interface for SQLite 3.x
 Home-page: http://oss.itsystementwicklung.de/trac/pysqlite
 Author: Gerhard Haering
 Author-email: gh@ghaering.de
 License: zlib/libpng license
-Download-URL: http://oss.itsystementwicklung.de/download/pysqlite/2.5/2.5.0/
+Download-URL: http://oss.itsystementwicklung.de/download/pysqlite/2.5/2.5.5/
 Description: Python interface to SQLite 3
         
         pysqlite is an interface to the SQLite 3.x embedded relational database engine.
index 8b6b4e5..d8df90f 100644 (file)
@@ -2,9 +2,16 @@ from pysqlite2 import dbapi2 as sqlite3
 
 con = sqlite3.connect(":memory:")
 
-# Load the fulltext search extension
+# 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
diff --git a/doc/install-source-win32.txt b/doc/install-source-win32.txt
deleted file mode 100644 (file)
index ed9260b..0000000
+++ /dev/null
@@ -1,168 +0,0 @@
----------------------------------------------------------------
-pysqlite installation guide - installing from source on Windows
----------------------------------------------------------------
-
-\(c\) 2005 Gerhard Häring
-
-Steps:
-
-  - `Step 1: Download and install required tools`_
-  - `Step 2: Download and compile SQLite`_
-  - `Step 3: Compile pysqlite`_
-  - `Step 4: Install pysqlite or create an installer`_
-  - `Step 5: Test your pysqlite installation`_
-
-Step 1: Download and install required tools
-===========================================
-
-This guide explains how to build SQLite and pysqlite with free and commercial
-Microsoft compilers for Python 2.4 and later. Other compilers might work, too,
-and MINGW is known to work well for Python 2.3, but in order to keep this
-document to the point, only one obvious way to do it is documented.
-
-First we need to have a working compiler. These two work fine:
-
-    * Microsoft Visual Studio .NET 2003
-    * MS Toolkit Compiler (http://www.vrplumber.com/programming/mstoolkit/)
-
-Then, we need GNU make. Download a Windows version from
-http://mingw.sourceforge.net/download.shtml and extract it.
-
-Make sure your PATH is set up correctly, so that the make.exe you just
-downloaded is in the PATH and that your compiler's environment is set up
-correctly. For MS Visual Studio .NET 2003, just execute
-Start/Programs/Microsoft Visual Studio .NET/Visual Studio .NET Tools/Visual
-Studio .NET 2003 Command Prompt. For the MS Toolkit Compiler write yourself a
-batch file that sets up the environment as neccessary.
-
-Step 2: Download and compile SQLite
-===================================
-
-From now on, let's suppose you do all your development work in $SRCDIR.
-
-Now download the latest versions of the SQLite 3.x Windows sources from http://sqlite.org/download.html.
-
-Pick the download that looks like this:
-
-sqlite-source-3_{major}_{minor}.zip
-
-Extract this file in $SRCDIR/sqlite
-
-Now copy this file into your $SRCDIR/sqlite directory:
-http://initd.org/svn/pysqlite/pysqlite/trunk/misc/Makefile
-
-Then on a Windows command prompt, change into the $SRCDIR/sqlite directory and
-issue a "make". This should build a statically linked SQLite library now::
-
-    Setting environment for using Microsoft Visual Studio .NET 2003 tools.
-    (If you have another version of Visual Studio or Visual C++ installed and wish
-    to use its tools from the command line, run vcvars32.bat for that version.)
-
-    C:\Documents and Settings\Gerhard>set path=c:\msys\1.0\bin;%PATH%
-
-    C:\Documents and Settings\Gerhard>d:
-
-    D:\>cd src\sqlite
-
-    D:\src\sqlite>make
-    cl -c -O2 -Og -G7 -Foalter.o alter.c
-    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 13.10.3077 for 80x86
-    Copyright (C) Microsoft Corporation 1984-2002. All rights reserved.
-
-    alter.c
-    cl -c -O2 -Og -G7 -Foanalyze.o analyze.c
-    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 13.10.3077 for 80x86
-    Copyright (C) Microsoft Corporation 1984-2002. All rights reserved.
-
-    [...]
-
-    utf.c
-    cl -c -O2 -Og -G7 -Folegacy.o legacy.c
-    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 13.10.3077 for 80x86
-    Copyright (C) Microsoft Corporation 1984-2002. All rights reserved.
-
-    legacy.c
-    link -LIB -OUT:sqlite3.lib alter.o analyze.o attach.o auth.o btree.o build.o cal
-    lback.o complete.o date.o delete.o expr.o func.o hash.o insert.o main.o opcodes.
-    o os_unix.o os_win.o pager.o parse.o pragma.o prepare.o printf.o random.o select
-    .o table.o tokenize.o trigger.o update.o util.o vacuum.o vdbe.o vdbeapi.o vdbeau
-    x.o vdbefifo.o vdbemem.o where.o utf.o legacy.o
-    Microsoft (R) Library Manager Version 7.10.3077
-    Copyright (C) Microsoft Corporation.  All rights reserved.
-
-    done
-
-    D:\src\sqlite>
-
-
-Step 3: Compile pysqlite
-========================
-
-Unpack the latest pysqlite sources in $SRCDIR/pysqlite
-
-Now change the include_dirs and library_dirs settings in ``setup.cfg`` like
-this::
-
-    [build_ext]
-    define=
-    include_dirs=../sqlite
-    library_dirs=../sqlite
-    libraries=sqlite3
-
-Now open a command prompt, change to the directory where you decompressed the
-pysqlite source distribution, and type::
-
-  python setup.py build
-
-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 4: Install pysqlite or create an installer
-===============================================
-
-To install pysqlite now, invoke::
-
-    python setup.py install
-
-from the commandline. In order to create an installer executable, invoke::
-
-    python setup.py bdist_wininst
-
-from the commandline.
-
-Step 5: 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 151 tests in 1.059s
-
-    OK
-    >>>
-
-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/
-.. _pysqlite bug tracker: http://initd.org/tracker/pysqlite
-.. _pysqlite mailing list: http://lists.initd.org/mailman/listinfo/pysqlite
index 090209c..a1162c6 100644 (file)
@@ -73,7 +73,7 @@ Otherwise you will have to customize the build process. That's what the file
 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* apprporiately, try invoking ``python
+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
@@ -143,5 +143,5 @@ 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://initd.org/tracker/pysqlite
-.. _pysqlite mailing list: http://lists.initd.org/mailman/listinfo/pysqlite
+.. _pysqlite bug tracker: http://pysqlite.org/
+.. _pysqlite mailing list: http://itsystementwicklung.de/cgi-bin/mailman/listinfo/list-pysqlite
index ca0b13a..b71dbd9 100644 (file)
@@ -220,7 +220,7 @@ A :class:`Connection` instance has the following attributes and methods:
 .. attribute:: Connection.isolation_level
 
    Get or set the current isolation level. None for autocommit mode or one of
-   "DEFERRED", "IMMEDIATE" or "EXLUSIVE". See section
+   "DEFERRED", "IMMEDIATE" or "EXCLUSIVE". See section
    :ref:`sqlite3-controlling-transactions` for a more detailed explanation.
 
 
@@ -369,6 +369,11 @@ A :class:`Connection` instance has the following attributes and methods:
 
    .. 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
 
diff --git a/extended_setup.py b/extended_setup.py
deleted file mode 100644 (file)
index c155695..0000000
+++ /dev/null
@@ -1,87 +0,0 @@
-#-*- coding: ISO-8859-1 -*-
-# ext_setup.py: setuptools extensions for the distutils script
-#
-# 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 glob, os, sys
-import cross_bdist_wininst
-
-from ez_setup import use_setuptools
-from distutils.command.build import build
-from distutils.command.build_ext import build_ext
-use_setuptools()
-
-import setuptools
-import urllib
-import zipfile
-
-from setup import get_setup_args, DocBuilder
-
-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."
-    urllib.urlretrieve("http://sqlite.org/sqlite-amalgamation-3_6_1.zip", "tmp.zip")
-    zf = zipfile.ZipFile("tmp.zip")
-    files = ["sqlite3.c", "sqlite3.h"]
-    for fn in files:
-        print "Extracting", fn
-        outf = open(AMALGAMATION_ROOT + os.sep + fn, "wb")
-        outf.write(zf.read(fn))
-        outf.close()
-    zf.close()
-    os.unlink("tmp.zip")
-
-class AmalgamationBuilder(build):
-    description = "Build a statically built pysqlite using the amalgamtion."
-
-    def __init__(self, *args, **kwargs):
-        MyBuildExt.amalgamation = True
-        build.__init__(self, *args, **kwargs)
-
-class MyBuildExt(build_ext):
-    amalgamation = False
-
-    def build_extension(self, ext):
-        get_amalgamation()
-        if self.amalgamation:
-            ext.sources.append(os.path.join(AMALGAMATION_ROOT, "sqlite3.c"))
-            build_ext.build_extension(self, ext)
-
-    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 main():
-    setup_args = get_setup_args()
-    setup_args["test_suite"] = "pysqlite2.test.suite"
-    setup_args["cmdclass"].update({"build_docs": DocBuilder, "build_ext": MyBuildExt, "build_static": AmalgamationBuilder, "cross_bdist_wininst": cross_bdist_wininst.bdist_wininst})
-    setuptools.setup(**setup_args)
-
-if __name__ == "__main__":
-    main()
diff --git a/ez_setup.py b/ez_setup.py
deleted file mode 100644 (file)
index bfda8cd..0000000
+++ /dev/null
@@ -1,231 +0,0 @@
-#!python
-"""Bootstrap setuptools installation
-
-If you want to use setuptools in your package's setup.py, just include this
-file in the same directory with it, and add this to the top of your setup.py::
-
-    from ez_setup import use_setuptools
-    use_setuptools()
-
-If you want to require a specific version of setuptools, set a download
-mirror, or use an alternate download directory, you can do so by supplying
-the appropriate options to ``use_setuptools()``.
-
-This file can also be run as a script to install or upgrade setuptools.
-"""
-import sys
-DEFAULT_VERSION = "0.6c6"
-DEFAULT_URL     = "http://cheeseshop.python.org/packages/%s/s/setuptools/" % sys.version[:3]
-
-md5_data = {
-    'setuptools-0.6b1-py2.3.egg': '8822caf901250d848b996b7f25c6e6ca',
-    'setuptools-0.6b1-py2.4.egg': 'b79a8a403e4502fbb85ee3f1941735cb',
-    'setuptools-0.6b2-py2.3.egg': '5657759d8a6d8fc44070a9d07272d99b',
-    'setuptools-0.6b2-py2.4.egg': '4996a8d169d2be661fa32a6e52e4f82a',
-    'setuptools-0.6b3-py2.3.egg': 'bb31c0fc7399a63579975cad9f5a0618',
-    'setuptools-0.6b3-py2.4.egg': '38a8c6b3d6ecd22247f179f7da669fac',
-    'setuptools-0.6b4-py2.3.egg': '62045a24ed4e1ebc77fe039aa4e6f7e5',
-    'setuptools-0.6b4-py2.4.egg': '4cb2a185d228dacffb2d17f103b3b1c4',
-    'setuptools-0.6c1-py2.3.egg': 'b3f2b5539d65cb7f74ad79127f1a908c',
-    'setuptools-0.6c1-py2.4.egg': 'b45adeda0667d2d2ffe14009364f2a4b',
-    'setuptools-0.6c2-py2.3.egg': 'f0064bf6aa2b7d0f3ba0b43f20817c27',
-    'setuptools-0.6c2-py2.4.egg': '616192eec35f47e8ea16cd6a122b7277',
-    'setuptools-0.6c3-py2.3.egg': 'f181fa125dfe85a259c9cd6f1d7b78fa',
-    'setuptools-0.6c3-py2.4.egg': 'e0ed74682c998bfb73bf803a50e7b71e',
-    'setuptools-0.6c3-py2.5.egg': 'abef16fdd61955514841c7c6bd98965e',
-    'setuptools-0.6c4-py2.3.egg': 'b0b9131acab32022bfac7f44c5d7971f',
-    'setuptools-0.6c4-py2.4.egg': '2a1f9656d4fbf3c97bf946c0a124e6e2',
-    'setuptools-0.6c4-py2.5.egg': '8f5a052e32cdb9c72bcf4b5526f28afc',
-    'setuptools-0.6c5-py2.3.egg': 'ee9fd80965da04f2f3e6b3576e9d8167',
-    'setuptools-0.6c5-py2.4.egg': 'afe2adf1c01701ee841761f5bcd8aa64',
-    'setuptools-0.6c5-py2.5.egg': 'a8d3f61494ccaa8714dfed37bccd3d5d',
-    'setuptools-0.6c6-py2.3.egg': '35686b78116a668847237b69d549ec20',
-    'setuptools-0.6c6-py2.4.egg': '3c56af57be3225019260a644430065ab',
-    'setuptools-0.6c6-py2.5.egg': 'b2f8a7520709a5b34f80946de5f02f53',
-}
-
-import sys, os
-
-def _validate_md5(egg_name, data):
-    if egg_name in md5_data:
-        from md5 import md5
-        digest = md5(data).hexdigest()
-        if digest != md5_data[egg_name]:
-            print >>sys.stderr, (
-                "md5 validation of %s failed!  (Possible download problem?)"
-                % egg_name
-            )
-            sys.exit(2)
-    return data
-
-
-def use_setuptools(
-    version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir,
-    download_delay=15
-):
-    """Automatically find/download setuptools and make it available on sys.path
-
-    `version` should be a valid setuptools version number that is available
-    as an egg for download under the `download_base` URL (which should end with
-    a '/').  `to_dir` is the directory where setuptools will be downloaded, if
-    it is not already available.  If `download_delay` is specified, it should
-    be the number of seconds that will be paused before initiating a download,
-    should one be required.  If an older version of setuptools is installed,
-    this routine will print a message to ``sys.stderr`` and raise SystemExit in
-    an attempt to abort the calling script.
-    """
-    try:
-        import setuptools
-        if setuptools.__version__ == '0.0.1':
-            print >>sys.stderr, (
-            "You have an obsolete version of setuptools installed.  Please\n"
-            "remove it from your system entirely before rerunning this script."
-            )
-            sys.exit(2)
-    except ImportError:
-        egg = download_setuptools(version, download_base, to_dir, download_delay)
-        sys.path.insert(0, egg)
-        import setuptools; setuptools.bootstrap_install_from = egg
-
-    import pkg_resources
-    try:
-        pkg_resources.require("setuptools>="+version)
-
-    except pkg_resources.VersionConflict, e:
-        # XXX could we install in a subprocess here?
-        print >>sys.stderr, (
-            "The required version of setuptools (>=%s) is not available, and\n"
-            "can't be installed while this script is running. Please install\n"
-            " a more recent version first.\n\n(Currently using %r)"
-        ) % (version, e.args[0])
-        sys.exit(2)
-
-def download_setuptools(
-    version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir,
-    delay = 15
-):
-    """Download setuptools from a specified location and return its filename
-
-    `version` should be a valid setuptools version number that is available
-    as an egg for download under the `download_base` URL (which should end
-    with a '/'). `to_dir` is the directory where the egg will be downloaded.
-    `delay` is the number of seconds to pause before an actual download attempt.
-    """
-    import urllib2, shutil
-    egg_name = "setuptools-%s-py%s.egg" % (version,sys.version[:3])
-    url = download_base + egg_name
-    saveto = os.path.join(to_dir, egg_name)
-    src = dst = None
-    if not os.path.exists(saveto):  # Avoid repeated downloads
-        try:
-            from distutils import log
-            if delay:
-                log.warn("""
----------------------------------------------------------------------------
-This script requires setuptools version %s to run (even to display
-help).  I will attempt to download it for you (from
-%s), but
-you may need to enable firewall access for this script first.
-I will start the download in %d seconds.
-
-(Note: if this machine does not have network access, please obtain the file
-
-   %s
-
-and place it in this directory before rerunning this script.)
----------------------------------------------------------------------------""",
-                    version, download_base, delay, url
-                ); from time import sleep; sleep(delay)
-            log.warn("Downloading %s", url)
-            src = urllib2.urlopen(url)
-            # Read/write all in one block, so we don't create a corrupt file
-            # if the download is interrupted.
-            data = _validate_md5(egg_name, src.read())
-            dst = open(saveto,"wb"); dst.write(data)
-        finally:
-            if src: src.close()
-            if dst: dst.close()
-    return os.path.realpath(saveto)
-
-def main(argv, version=DEFAULT_VERSION):
-    """Install or upgrade setuptools and EasyInstall"""
-
-    try:
-        import setuptools
-    except ImportError:
-        egg = None
-        try:
-            egg = download_setuptools(version, delay=0)
-            sys.path.insert(0,egg)
-            from setuptools.command.easy_install import main
-            return main(list(argv)+[egg])   # we're done here
-        finally:
-            if egg and os.path.exists(egg):
-                os.unlink(egg)
-    else:
-        if setuptools.__version__ == '0.0.1':
-            # tell the user to uninstall obsolete version
-            use_setuptools(version)
-
-    req = "setuptools>="+version
-    import pkg_resources
-    try:
-        pkg_resources.require(req)
-    except pkg_resources.VersionConflict:
-        try:
-            from setuptools.command.easy_install import main
-        except ImportError:
-            from easy_install import main
-        main(list(argv)+[download_setuptools(delay=0)])
-        sys.exit(0) # try to force an exit
-    else:
-        if argv:
-            from setuptools.command.easy_install import main
-            main(argv)
-        else:
-            print "Setuptools version",version,"or greater has been installed."
-            print '(Run "ez_setup.py -U setuptools" to reinstall or upgrade.)'
-
-
-
-def update_md5(filenames):
-    """Update our built-in md5 registry"""
-
-    import re
-    from md5 import md5
-
-    for name in filenames:
-        base = os.path.basename(name)
-        f = open(name,'rb')
-        md5_data[base] = md5(f.read()).hexdigest()
-        f.close()
-
-    data = ["    %r: %r,\n" % it for it in md5_data.items()]
-    data.sort()
-    repl = "".join(data)
-
-    import inspect
-    srcfile = inspect.getsourcefile(sys.modules[__name__])
-    f = open(srcfile, 'rb'); src = f.read(); f.close()
-
-    match = re.search("\nmd5_data = {\n([^}]+)}", src)
-    if not match:
-        print >>sys.stderr, "Internal error!"
-        sys.exit(2)
-
-    src = src[:match.start(1)] + repl + src[match.end(1):]
-    f = open(srcfile,'w')
-    f.write(src)
-    f.close()
-
-
-if __name__=='__main__':
-    if len(sys.argv)>2 and sys.argv[1]=='--md5update':
-        update_md5(sys.argv[2:])
-    else:
-        main(sys.argv[1:])
-
-
-
-
-
index 5e0ad7d..03bcd57 100644 (file)
@@ -1,7 +1,7 @@
 #-*- coding: ISO-8859-1 -*-
 # pysqlite2/test/dbapi.py: tests for DB-API compliance
 #
-# Copyright (C) 2004-2007 Gerhard Häring <gh@ghaering.de>
+# Copyright (C) 2004-2009 Gerhard Häring <gh@ghaering.de>
 #
 # This file is part of pysqlite.
 #
@@ -672,13 +672,13 @@ class ExtensionTests(unittest.TestCase):
         res = cur.fetchone()[0]
         self.failUnlessEqual(res, 6)
 
-    def CheckScriptErrorIncomplete(self):
+    def CheckScriptSyntaxError(self):
         con = sqlite.connect(":memory:")
         cur = con.cursor()
         raised = False
         try:
-            cur.executescript("create table test(sadfsadfdsa")
-        except sqlite.ProgrammingError:
+            cur.executescript("create table test(x); asdf; create table test2(x)")
+        except sqlite.OperationalError:
             raised = True
         self.failUnlessEqual(raised, True, "should have raised an exception")
 
@@ -711,7 +711,7 @@ class ExtensionTests(unittest.TestCase):
         result = con.execute("select foo from test").fetchone()[0]
         self.failUnlessEqual(result, 5, "Basic test of Connection.executescript")
 
-class ClosedTests(unittest.TestCase):
+class ClosedConTests(unittest.TestCase):
     def setUp(self):
         pass
 
@@ -763,6 +763,36 @@ class ClosedTests(unittest.TestCase):
         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")
@@ -770,8 +800,9 @@ def suite():
     thread_suite = unittest.makeSuite(ThreadTests, "Check")
     constructor_suite = unittest.makeSuite(ConstructorTests, "Check")
     ext_suite = unittest.makeSuite(ExtensionTests, "Check")
-    closed_suite = unittest.makeSuite(ClosedTests, "Check")
-    return unittest.TestSuite((module_suite, connection_suite, cursor_suite, thread_suite, constructor_suite, ext_suite, closed_suite))
+    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()
index c4b3e13..d93ca02 100644 (file)
@@ -42,7 +42,7 @@ class DumpTests(unittest.TestCase):
             for i in xrange(len(expected_sqls))]
 
 def suite():
-    return unittest.TestSuite(unittest.makeSuite(DumpTests, "Check"))
+    return unittest.TestSuite([unittest.makeSuite(DumpTests, "Check")])
 
 def test():
     runner = unittest.TextTestRunner()
index 80fec5f..7d463ae 100644 (file)
@@ -1,7 +1,7 @@
 #-*- coding: ISO-8859-1 -*-
 # pysqlite2/test/regression.py: pysqlite regression tests
 #
-# Copyright (C) 2006-2007 Gerhard Häring <gh@ghaering.de>
+# Copyright (C) 2006-2009 Gerhard Häring <gh@ghaering.de>
 #
 # This file is part of pysqlite.
 #
@@ -70,16 +70,6 @@ class RegressionTests(unittest.TestCase):
         cur.execute('select 1 as "foo baz"')
         self.failUnlessEqual(cur.description[0][0], "foo baz")
 
-    def CheckStatementAvailable(self):
-        # pysqlite up to 2.3.2 crashed on this, because the active statement handle was not checked
-        # before trying to fetch data from it. close() destroys the active statement ...
-        con = sqlite.connect(":memory:", detect_types=sqlite.PARSE_DECLTYPES)
-        cur = con.cursor()
-        cur.execute("select 4 union select 5")
-        cur.close()
-        cur.fetchone()
-        cur.fetchone()
-
     def CheckStatementFinalizationOnCloseDb(self):
         # pysqlite versions <= 2.3.3 only finalized statements in the statement
         # cache when closing the database. statements that were still
@@ -167,6 +157,88 @@ class RegressionTests(unittest.TestCase):
         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 suite():
     regression_suite = unittest.makeSuite(RegressionTests, "Check")
index 9011b24..ca6e71c 100644 (file)
@@ -1,7 +1,7 @@
 #-*- coding: ISO-8859-1 -*-
 # pysqlite2/test/transactions.py: tests transactions
 #
-# Copyright (C) 2005-2007 Gerhard Häring <gh@ghaering.de>
+# Copyright (C) 2005-2009 Gerhard Häring <gh@ghaering.de>
 #
 # This file is part of pysqlite.
 #
@@ -148,6 +148,26 @@ class TransactionTests(unittest.TestCase):
         # 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:")
index 7a0b76c..f15f1f0 100644 (file)
@@ -73,6 +73,33 @@ class SqliteTypeTests(unittest.TestCase):
         row = self.cur.fetchone()
         self.failUnlessEqual(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):
index bb37822..607fda4 100644 (file)
--- a/setup.cfg
+++ b/setup.cfg
@@ -3,3 +3,4 @@
 #include_dirs=/usr/local/include
 #library_dirs=/usr/local/lib
 libraries=sqlite3
+define=SQLITE_OMIT_LOAD_EXTENSION
index f6de2b3..70cf76d 100644 (file)
--- a/setup.py
+++ b/setup.py
 # 3. This notice may not be removed or altered from any source distribution.
 
 import glob, os, re, sys
+import urllib
+import zipfile
 
 from distutils.core import setup, Extension, Command
+from distutils.command.build import build
+from distutils.command.build_ext import build_ext
+
+import cross_bdist_wininst
 
 # If you need to change anything, it should be enough to change setup.cfg.
 
@@ -69,9 +75,64 @@ class DocBuilder(Command):
         except OSError:
             pass
         os.makedirs("build/doc")
-        os.system("sphinx-build doc/sphinx 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."
+
+    # find out what's current amalgamation ZIP file
+    download_page = urllib.urlopen("http://sqlite.org/download.html").read()
+    pattern = re.compile("(sqlite-amalgamation.*?\.zip)")
+    download_file = pattern.findall(download_page)[0]
+    amalgamation_url = "http://sqlite.org/" + download_file
+
+    # and download it
+    urllib.urlretrieve(amalgamation_url, "tmp.zip")
+
+    zf = zipfile.ZipFile("tmp.zip")
+    files = ["sqlite3.c", "sqlite3.h"]
+    for fn in files:
+        print "Extracting", fn
+        outf = open(AMALGAMATION_ROOT + os.sep + fn, "wb")
+        outf.write(zf.read(fn))
+        outf.close()
+    zf.close()
+    os.unlink("tmp.zip")
+
+class AmalgamationBuilder(build):
+    description = "Build a statically built pysqlite using the amalgamtion."
+
+    def __init__(self, *args, **kwargs):
+        MyBuildExt.amalgamation = True
+        build.__init__(self, *args, **kwargs)
+
+class MyBuildExt(build_ext):
+    amalgamation = False
+
+    def build_extension(self, ext):
+        if self.amalgamation:
+            get_amalgamation()
+            ext.define_macros.append(("SQLITE_ENABLE_FTS3", "1"))   # build with fulltext search enabled
+            ext.sources.append(os.path.join(AMALGAMATION_ROOT, "sqlite3.c"))
+            ext.include_dirs.append(AMALGAMATION_ROOT)
+        build_ext.build_extension(self, ext)
+
+    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 "(.*)"')
@@ -138,6 +199,8 @@ def get_setup_args():
             "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():
index 7dd0ffc..09d5ddb 100644 (file)
@@ -1,6 +1,6 @@
 /* connection.c - the connection type
  *
- * Copyright (C) 2004-2007 Gerhard Häring <gh@ghaering.de>
+ * Copyright (C) 2004-2009 Gerhard Häring <gh@ghaering.de>
  *
  * This file is part of pysqlite.
  * 
 #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)
@@ -77,10 +80,13 @@ int pysqlite_connection_init(pysqlite_Connection* self, PyObject* args, PyObject
         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;
@@ -154,11 +160,15 @@ int pysqlite_connection_init(pysqlite_Connection* self, PyObject* args, PyObject
         return -1;
     }
 
+    self->created_statements = 0;
+    self->created_cursors = 0;
+
+    /* Create lists of weak references to statements/cursors */
     self->statements = PyList_New(0);
-    if (!self->statements) {
+    self->cursors = PyList_New(0);
+    if (!self->statements || !self->cursors) {
         return -1;
     }
-    self->created_statements = 0;
 
     /* By default, the Cache class INCREFs the factory in its initializer, and
      * decrefs it in its deallocator method. Since this would create a circular
@@ -221,11 +231,12 @@ void pysqlite_flush_statement_cache(pysqlite_Connection* self)
 }
 
 /* action in (ACTION_RESET, ACTION_FINALIZE) */
-void pysqlite_do_all_statements(pysqlite_Connection* self, int action)
+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);
@@ -238,6 +249,16 @@ void pysqlite_do_all_statements(pysqlite_Connection* self, int action)
             }
         }
     }
+
+    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)
@@ -266,17 +287,43 @@ void pysqlite_connection_dealloc(pysqlite_Connection* self)
     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;
@@ -292,6 +339,8 @@ PyObject* pysqlite_connection_cursor(pysqlite_Connection* self, PyObject* args,
 
     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);
@@ -310,7 +359,7 @@ PyObject* pysqlite_connection_close(pysqlite_Connection* self, PyObject* args)
         return NULL;
     }
 
-    pysqlite_do_all_statements(self, ACTION_FINALIZE);
+    pysqlite_do_all_statements(self, ACTION_FINALIZE, 1);
 
     if (self->db) {
         if (self->apsw_connection) {
@@ -338,12 +387,17 @@ PyObject* pysqlite_connection_close(pysqlite_Connection* self, PyObject* args)
 }
 
 /*
- * Checks if a connection object is usable (i. e. not closed).
+ * Checks if a connection object is usable.
  *
  * 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;
@@ -402,6 +456,8 @@ PyObject* pysqlite_connection_commit(pysqlite_Connection* self, PyObject* args)
     }
 
     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
@@ -446,7 +502,7 @@ PyObject* pysqlite_connection_rollback(pysqlite_Connection* self, PyObject* args
     }
 
     if (self->inTransaction) {
-        pysqlite_do_all_statements(self, ACTION_RESET);
+        pysqlite_do_all_statements(self, ACTION_RESET, 1);
 
         Py_BEGIN_ALLOW_THREADS
         rc = sqlite3_prepare(self->db, "ROLLBACK", -1, &statement, &tail);
@@ -715,7 +771,7 @@ error:
     PyGILState_Release(threadstate);
 }
 
-void _pysqlite_drop_unused_statement_references(pysqlite_Connection* self)
+static void _pysqlite_drop_unused_statement_references(pysqlite_Connection* self)
 {
     PyObject* new_list;
     PyObject* weakref;
@@ -747,6 +803,38 @@ void _pysqlite_drop_unused_statement_references(pysqlite_Connection* self)
     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};
@@ -860,7 +948,7 @@ static int _progress_handler(void* user_arg)
     return rc;
 }
 
-PyObject* pysqlite_connection_set_authorizer(pysqlite_Connection* self, PyObject* args, PyObject* kwargs)
+static PyObject* pysqlite_connection_set_authorizer(pysqlite_Connection* self, PyObject* args, PyObject* kwargs)
 {
     PyObject* authorizer_cb;
 
@@ -885,7 +973,7 @@ PyObject* pysqlite_connection_set_authorizer(pysqlite_Connection* self, PyObject
     }
 }
 
-PyObject* pysqlite_connection_set_progress_handler(pysqlite_Connection* self, PyObject* args, PyObject* kwargs)
+static PyObject* pysqlite_connection_set_progress_handler(pysqlite_Connection* self, PyObject* args, PyObject* kwargs)
 {
     PyObject* progress_handler;
     int n;
@@ -910,11 +998,15 @@ PyObject* pysqlite_connection_set_progress_handler(pysqlite_Connection* self, Py
 }
 
 #ifdef HAVE_LOAD_EXTENSION
-PyObject* pysqlite_enable_load_extension(pysqlite_Connection* self, PyObject* args)
+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;
     }
@@ -929,6 +1021,30 @@ PyObject* pysqlite_enable_load_extension(pysqlite_Connection* self, PyObject* ar
         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)
@@ -1405,6 +1521,8 @@ static PyMethodDef connection_methods[] = {
     #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.")},
index 3b1c632..a62989b 100644 (file)
@@ -1,6 +1,6 @@
 /* connection.h - definitions for the connection type
  *
- * Copyright (C) 2004-2007 Gerhard Häring <gh@ghaering.de>
+ * Copyright (C) 2004-2009 Gerhard Häring <gh@ghaering.de>
  *
  * This file is part of pysqlite.
  *
@@ -63,17 +63,21 @@ typedef struct
      * 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;
 
-    /* A list of weak references to statements used within this connection */
+    /* Lists of weak references to statements and cursors used within this connection */
     PyObject* statements;
+    PyObject* cursors;
 
-    /* a counter for how many statements were created in the connection. May be
+    /* 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;
 
@@ -125,6 +129,7 @@ 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);
 
index 2dc613e..fee6afb 100644 (file)
@@ -1,6 +1,6 @@
 /* cursor.c - the cursor type
  *
- * Copyright (C) 2004-2007 Gerhard Häring <gh@ghaering.de>
+ * Copyright (C) 2004-2009 Gerhard Häring <gh@ghaering.de>
  *
  * This file is part of pysqlite.
  *
@@ -36,6 +36,8 @@
 
 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];
@@ -74,7 +76,7 @@ static pysqlite_StatementKind detect_statement_type(char* statement)
     }
 }
 
-int pysqlite_cursor_init(pysqlite_Cursor* self, PyObject* args, PyObject* kwargs)
+static int pysqlite_cursor_init(pysqlite_Cursor* self, PyObject* args, PyObject* kwargs)
 {
     pysqlite_Connection* connection;
 
@@ -87,6 +89,7 @@ int pysqlite_cursor_init(pysqlite_Cursor* self, PyObject* args, PyObject* kwargs
     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) {
@@ -100,6 +103,8 @@ int pysqlite_cursor_init(pysqlite_Cursor* self, PyObject* args, PyObject* kwargs
     self->lastrowid= Py_None;
 
     self->arraysize = 1;
+    self->closed = 0;
+    self->reset = 0;
 
     self->rowcount = -1L;
 
@@ -110,10 +115,16 @@ int pysqlite_cursor_init(pysqlite_Cursor* self, PyObject* args, PyObject* kwargs
         return -1;
     }
 
+    if (!pysqlite_connection_register_cursor(connection, (PyObject*)self)) {
+        return -1;
+    }
+
+    self->initialized = 1;
+
     return 0;
 }
 
-void pysqlite_cursor_dealloc(pysqlite_Cursor* self)
+static void pysqlite_cursor_dealloc(pysqlite_Cursor* self)
 {
     int rc;
 
@@ -130,6 +141,10 @@ void pysqlite_cursor_dealloc(pysqlite_Cursor* self)
     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);
 }
 
@@ -301,6 +316,11 @@ PyObject* _pysqlite_fetch_one_row(pysqlite_Cursor* self)
     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
@@ -406,6 +426,26 @@ PyObject* _pysqlite_fetch_one_row(pysqlite_Cursor* self)
     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;
@@ -425,13 +465,15 @@ PyObject* _pysqlite_query_execute(pysqlite_Cursor* self, int multiple, PyObject*
     PyObject* second_argument = NULL;
     int allow_8bit_chars;
 
-    if (!pysqlite_check_thread(self->connection) || !pysqlite_check_connection(self->connection)) {
+    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 != (PyObject*)&PyUnicode_Type && pysqlite_OptimizedUnicode));
+        (self->connection->text_factory != pysqlite_OptimizedUnicode));
 
     Py_XDECREF(self->next_row);
     self->next_row = NULL;
@@ -753,16 +795,17 @@ PyObject* pysqlite_cursor_executescript(pysqlite_Cursor* self, PyObject* args)
     sqlite3_stmt* statement;
     int rc;
     PyObject* result;
-    int statement_completed = 0;
 
     if (!PyArg_ParseTuple(args, "O", &script_obj)) {
         return NULL;
     }
 
-    if (!pysqlite_check_thread(self->connection) || !pysqlite_check_connection(self->connection)) {
+    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)) {
@@ -785,11 +828,6 @@ PyObject* pysqlite_cursor_executescript(pysqlite_Cursor* self, PyObject* args)
     Py_DECREF(result);
 
     while (1) {
-        if (!sqlite3_complete(script_cstr)) {
-            break;
-        }
-        statement_completed = 1;
-
         Py_BEGIN_ALLOW_THREADS
         rc = sqlite3_prepare(self->connection->db,
                              script_cstr,
@@ -820,15 +858,15 @@ PyObject* pysqlite_cursor_executescript(pysqlite_Cursor* self, PyObject* args)
             _pysqlite_seterror(self->connection->db, NULL);
             goto error;
         }
+
+        if (*script_cstr == (char)0) {
+            break;
+        }
     }
 
 error:
     Py_XDECREF(script_str);
 
-    if (!statement_completed) {
-        PyErr_SetString(pysqlite_ProgrammingError, "you did not provide a complete SQL statement");
-    }
-
     if (PyErr_Occurred()) {
         return NULL;
     } else {
@@ -849,7 +887,12 @@ PyObject* pysqlite_cursor_iternext(pysqlite_Cursor *self)
     PyObject* next_row;
     int rc;
 
-    if (!pysqlite_check_thread(self->connection) || !pysqlite_check_connection(self->connection)) {
+    if (!check_cursor(self)) {
+        return NULL;
+    }
+
+    if (self->reset) {
+        PyErr_SetString(pysqlite_InterfaceError, errmsg_fetch_across_rollback);
         return NULL;
     }
 
@@ -992,6 +1035,8 @@ PyObject* pysqlite_cursor_close(pysqlite_Cursor* self, PyObject* args)
         Py_CLEAR(self->statement);
     }
 
+    self->closed = 1;
+
     Py_INCREF(Py_None);
     return Py_None;
 }
@@ -1053,12 +1098,12 @@ PyTypeObject pysqlite_CursorType = {
         0,                                              /* tp_getattro */
         0,                                              /* tp_setattro */
         0,                                              /* tp_as_buffer */
-        Py_TPFLAGS_DEFAULT|Py_TPFLAGS_HAVE_ITER|Py_TPFLAGS_BASETYPE, /* tp_flags */
+        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 */
-        0,                                              /* tp_weaklistoffset */
+        offsetof(pysqlite_Cursor, in_weakreflist),      /* tp_weaklistoffset */
         (getiterfunc)pysqlite_cursor_getiter,           /* tp_iter */
         (iternextfunc)pysqlite_cursor_iternext,         /* tp_iternext */
         cursor_methods,                                 /* tp_methods */
index 54d816d..1988ac3 100644 (file)
@@ -1,6 +1,6 @@
 /* cursor.h - definitions for the cursor type
  *
- * Copyright (C) 2004-2007 Gerhard Häring <gh@ghaering.de>
+ * Copyright (C) 2004-2009 Gerhard Häring <gh@ghaering.de>
  *
  * This file is part of pysqlite.
  *
@@ -40,9 +40,14 @@ typedef struct
     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 {
@@ -53,8 +58,6 @@ typedef enum {
 
 extern PyTypeObject pysqlite_CursorType;
 
-int pysqlite_cursor_init(pysqlite_Cursor* self, PyObject* args, PyObject* kwargs);
-void pysqlite_cursor_dealloc(pysqlite_Cursor* self);
 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);
index 05c7b14..3a7a316 100644 (file)
@@ -25,7 +25,7 @@
 #define PYSQLITE_MODULE_H
 #include "Python.h"
 
-#define PYSQLITE_VERSION "2.5.0"
+#define PYSQLITE_VERSION "2.5.5"
 
 extern PyObject* pysqlite_Error;
 extern PyObject* pysqlite_Warning;