Imported Upstream version 2.3.0 upstream/2.3.0
authorJoel Rosdahl <joel@debian.org>
Mon, 11 Jan 2010 20:37:33 +0000 (21:37 +0100)
committerJoel Rosdahl <joel@debian.org>
Mon, 11 Jan 2010 20:37:33 +0000 (21:37 +0100)
17 files changed:
PKG-INFO
doc/code/authorizer.py [new file with mode: 0644]
doc/usage-guide.html
doc/usage-guide.txt
pysqlite2/test/__init__.py
pysqlite2/test/regression.py
pysqlite2/test/types.py
pysqlite2/test/userfunctions.py
setup.cfg
src/compat.h [deleted file]
src/connection.c
src/cursor.c
src/module.c
src/module.h
src/row.c
src/sqlitecompat.h [new file with mode: 0644]
src/statement.c

index 272e496..1182817 100644 (file)
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.0
 Name: pysqlite
-Version: 2.2.2
+Version: 2.3.0
 Summary: DB-API 2.0 interface for SQLite 3.x
 Home-page: http://pysqlite.org/
 Author: Gerhard Haering
diff --git a/doc/code/authorizer.py b/doc/code/authorizer.py
new file mode 100644 (file)
index 0000000..5f89716
--- /dev/null
@@ -0,0 +1,26 @@
+from pysqlite2 import dbapi2 as sqlite
+
+def authorizer_callback(action, arg1, arg2, dbname, source):
+    if action != sqlite.SQLITE_SELECT:
+        return sqlite.SQLITE_DENY
+    if arg1 == "private_table":
+        return sqlite.SQLITE_DENY
+    return sqlite.SQLITE_OK
+
+con = sqlite.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 sqlite.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 sqlite.DatabaseError, e:
+    print "DML command =>", e.args[0]     # access ... prohibited
+
index 8ca7ba0..78d59f6 100644 (file)
@@ -27,7 +27,7 @@ font-size: 10pt;
 <div class="line">(c) 2004-2005 David Rushby</div>
 <div class="line">(c) 2005-2006 Gerhard Häring</div>
 </div>
-<p>Last updated for pysqlite 2.2.1</p>
+<p>Last updated for pysqlite 2.3.0</p>
 <div class="section">
 <h1><a id="table-of-contents" name="table-of-contents">Table Of Contents</a></h1>
 <div class="line-block">
@@ -51,6 +51,7 @@ font-size: 10pt;
 <div class="line"><a class="reference" href="#creating-and-using-collations">3.3 Creating and using collations</a></div>
 <div class="line"><a class="reference" href="#checking-for-complete-statements">3.4 Checking for complete statements</a></div>
 <div class="line"><a class="reference" href="#enabling-sqlite-s-shared-cache">3.5 Enabling SQLite's shared cache</a></div>
+<div class="line"><a class="reference" href="#setting-an-authorizer-callback">3.6 Setting an authorizer callback</a></div>
 </div>
 <div class="line"><a class="reference" href="#sqlite-and-python-types">4. SQLite and Python types</a></div>
 <div class="line-block">
@@ -203,8 +204,7 @@ details.</p>
 type for each column it returns. It will parse out the first word of the
 declared type, i. e. for &quot;integer primary key&quot;, it will parse out
 &quot;integer&quot;. Then for that column, it will look into pysqlite's converters
-dictionary and use the converter function registered for that type there.
-Converter names are case-sensitive!</p>
+dictionary and use the converter function registered for that type there.</p>
 </li>
 <li><p class="first"><strong>sqlite.PARSE_COLNAMES</strong> - This makes pysqlite parse the column name
 for each column it returns. It will look for a string formed
@@ -218,7 +218,7 @@ then pysqlite will parse out everything until the first blank for
 the column name: the column name would simply be &quot;x&quot;.</p>
 <p>The following example uses the column name <em>timestamp</em>, which is already
 registered by default in the converters dictionary with an appropriate
-converter! Note that converter names are case-sensitive!</p>
+converter!</p>
 <p>Example:</p>
 <div class="code-block">
 <span class="p_word">from</span><span class="p_default">&nbsp;</span><span class="p_identifier">pysqlite2</span><span class="p_default">&nbsp;</span><span class="p_word">import</span><span class="p_default">&nbsp;</span><span class="p_identifier">dbapi2</span><span class="p_default">&nbsp;</span><span class="p_word">as</span><span class="p_default">&nbsp;</span><span class="p_identifier">sqlite</span><span class="p_default"><br/>
@@ -277,8 +277,7 @@ The currently implemented default is to cache 100 statements.</p>
 registers a callable to convert a bytestring from the database into a custom
 Python type. The converter will be invoked for all database values that are
 of the type <tt class="docutils literal"><span class="pre">typename</span></tt>. Confer the parameter <strong>detect_types</strong> of the
-<strong>connect</strong> method for how the type detection works. Note that the case
-<tt class="docutils literal"><span class="pre">typename</span></tt> and the name of the type in your query must match!</p>
+<strong>connect</strong> method for how the type detection works.</p>
 </li>
 <li><p class="first"><strong>register_adapter</strong> function - <tt class="docutils literal"><span class="pre">register_adapter(type,</span> <span class="pre">callable)</span></tt>
 registers a callable to convert the custom Python <strong>type</strong> into one of
@@ -286,6 +285,10 @@ SQLite's supported types. The 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.</p>
 </li>
+<li><p class="first"><strong>enable_callback_tracebacks</strong> function - <tt class="docutils literal"><span class="pre">enable_callback_tracebacks(flag)</span></tt>
+Can be used to enable displaying tracebacks of exceptions in user-defined functions, aggregates and other callbacks being printed to stderr.
+methods should never raise any exception. This feature is off by default.</p>
+</li>
 <li><p class="first"><strong>Connection</strong> class</p>
 <ul>
 <li><p class="first"><strong>isolation_level</strong> attribute (read-write)</p>
@@ -533,10 +536,7 @@ using a generator:</p>
 execute more than one statement with it, it will raise a Warning. Use
 <em>executescript</em> if want to execute multiple SQL statements with one call.</p>
 </li>
-</ul>
-</li>
 <li><p class="first"><strong>executescript</strong> method</p>
-<blockquote>
 <div class="code-block">
 <span class="p_operator">.</span><span class="p_identifier">executemany</span><span class="p_operator">(</span><span class="p_identifier">sqlscript</span><span class="p_operator">)</span>
 </div>
@@ -571,13 +571,17 @@ SQL script it gets as a parameter.</p>
 &nbsp;&nbsp;&nbsp;&nbsp;);<br/>
 &nbsp;&nbsp;&nbsp;&nbsp;"""</span><span class="p_operator">)</span>
 </div>
-</blockquote>
-<ul>
+</li>
+<li><p class="first"><strong>interrupt</strong> method</p>
+<p>This method has no arguments. You can call it from a different thread to
+abort any queries that are currently executing on the connection. This can
+be used to let the user abort runaway queries, for example.</p>
+</li>
 <li><p class="first"><strong>rowcount</strong> attribute</p>
 <p>Although pysqlite's Cursors implement this attribute, the database
 engine's own support for the determination of &quot;rows affected&quot;/&quot;rows
 selected&quot; is quirky.</p>
-<p>For <tt class="docutils literal"><span class="pre">SELECT</span></tt> statements, <em>rowcount</em> is always None because pysqlite
+<p>For <tt class="docutils literal"><span class="pre">SELECT</span></tt> statements, <em>rowcount</em> is always -1 because pysqlite
 cannot determine the number of rows a query produced until all rows
 were fetched.</p>
 <p>For <tt class="docutils literal"><span class="pre">DELETE</span></tt> statements, SQLite reports <em>rowcount</em> as 0 if you make a
@@ -800,8 +804,8 @@ number of parameters</dd>
 <dd>the Python function</dd>
 </dl>
 <p>The function can return any of pysqlite's supported SQLite types: unicode,
-str, int, long, float, buffer and None.  The function should never raise an
-exception.</p>
+str, int, long, float, buffer and None.  Any exception in the user-defined
+function leads to the SQL statement executed being aborted.</p>
 <p>Example:</p>
 <div class="code-block">
 <span class="p_word">from</span><span class="p_default">&nbsp;</span><span class="p_identifier">pysqlite2</span><span class="p_default">&nbsp;</span><span class="p_word">import</span><span class="p_default">&nbsp;</span><span class="p_identifier">dbapi2</span><span class="p_default">&nbsp;</span><span class="p_word">as</span><span class="p_default">&nbsp;</span><span class="p_identifier">sqlite</span><span class="p_default"><br/>
@@ -830,8 +834,9 @@ create new aggregate functions with the connection's <em>create_aggregate</em> m
 number of parameters defined in <em>create_aggregate</em>, and a <em>finalize</em>
 method which will return the final result of the aggregate.</p>
 <p>The <em>finalize</em> method can return any of pysqlite's supported SQLite types:
-unicode, str, int, long, float, buffer and None. The aggregate class's
-methods should never raise any exception.</p>
+unicode, str, int, long, float, buffer and None. Any exception in the
+aggregate's <em>__init__</em>, <em>step</em> or <em>finalize</em> methods lead to the SQL
+statement executed being aborted.</p>
 <p>Example:</p>
 <div class="code-block">
 <span class="p_word">from</span><span class="p_default">&nbsp;</span><span class="p_identifier">pysqlite2</span><span class="p_default">&nbsp;</span><span class="p_word">import</span><span class="p_default">&nbsp;</span><span class="p_identifier">dbapi2</span><span class="p_default">&nbsp;</span><span class="p_word">as</span><span class="p_default">&nbsp;</span><span class="p_identifier">sqlite</span><span class="p_default"><br/>
@@ -957,6 +962,48 @@ statement!</p>
 </div>
 </blockquote>
 </div>
+<div class="section">
+<h2><a id="setting-an-authorizer-callback" name="setting-an-authorizer-callback">3.6 Setting an authorizer callback</a></h2>
+<p>You can set an authorizer callback if you want to restrict what your users can
+do with the database. This is mostly useful if you accept arbitrary SQL from
+users and want to execute it safely. See the relevant section in the SQL
+documentation for details:
+<a class="reference" href="http://sqlite.org/capi3ref.html#sqlite3_set_authorizer">http://sqlite.org/capi3ref.html#sqlite3_set_authorizer</a></p>
+<p>All necessary constants like SQLITE_OK, SQLITE_DENY, SQLITE_IGNORE,
+SQLITE_SELECT, SQLITE_CREATE_INDEX and all other authorizer-related constants
+are available through the dbapi2 module.</p>
+<p>Here's an example that demonstrates the usage of this function:</p>
+<blockquote>
+<div class="code-block">
+<span class="p_word">from</span><span class="p_default">&nbsp;</span><span class="p_identifier">pysqlite2</span><span class="p_default">&nbsp;</span><span class="p_word">import</span><span class="p_default">&nbsp;</span><span class="p_identifier">dbapi2</span><span class="p_default">&nbsp;</span><span class="p_word">as</span><span class="p_default">&nbsp;</span><span class="p_identifier">sqlite</span><span class="p_default"><br/>
+<br/>
+</span><span class="p_word">def</span><span class="p_default">&nbsp;</span><span class="p_defname">authorizer_callback</span><span class="p_operator">(</span><span class="p_identifier">action</span><span class="p_operator">,</span><span class="p_default">&nbsp;</span><span class="p_identifier">arg1</span><span class="p_operator">,</span><span class="p_default">&nbsp;</span><span class="p_identifier">arg2</span><span class="p_operator">,</span><span class="p_default">&nbsp;</span><span class="p_identifier">dbname</span><span class="p_operator">,</span><span class="p_default">&nbsp;</span><span class="p_identifier">source</span><span class="p_operator">):</span><span class="p_default"><br/>
+&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="p_word">if</span><span class="p_default">&nbsp;</span><span class="p_identifier">action</span><span class="p_default">&nbsp;</span><span class="p_operator">!=</span><span class="p_default">&nbsp;</span><span class="p_identifier">sqlite</span><span class="p_operator">.</span><span class="p_identifier">SQLITE_SELECT</span><span class="p_operator">:</span><span class="p_default"><br/>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="p_word">return</span><span class="p_default">&nbsp;</span><span class="p_identifier">sqlite</span><span class="p_operator">.</span><span class="p_identifier">SQLITE_DENY</span><span class="p_default"><br/>
+&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="p_word">if</span><span class="p_default">&nbsp;</span><span class="p_identifier">arg1</span><span class="p_default">&nbsp;</span><span class="p_operator">==</span><span class="p_default">&nbsp;</span><span class="p_string">"private_table"</span><span class="p_operator">:</span><span class="p_default"><br/>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="p_word">return</span><span class="p_default">&nbsp;</span><span class="p_identifier">sqlite</span><span class="p_operator">.</span><span class="p_identifier">SQLITE_DENY</span><span class="p_default"><br/>
+&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="p_word">return</span><span class="p_default">&nbsp;</span><span class="p_identifier">sqlite</span><span class="p_operator">.</span><span class="p_identifier">SQLITE_OK</span><span class="p_default"><br/>
+<br/>
+</span><span class="p_identifier">con</span><span class="p_default">&nbsp;</span><span class="p_operator">=</span><span class="p_default">&nbsp;</span><span class="p_identifier">sqlite</span><span class="p_operator">.</span><span class="p_identifier">connect</span><span class="p_operator">(</span><span class="p_string">":memory:"</span><span class="p_operator">)</span><span class="p_default"><br/>
+</span><span class="p_identifier">con</span><span class="p_operator">.</span><span class="p_identifier">executescript</span><span class="p_operator">(</span><span class="p_tripledouble">"""<br/>
+&nbsp;&nbsp;&nbsp;&nbsp;create&nbsp;table&nbsp;public_table(c1,&nbsp;c2);<br/>
+&nbsp;&nbsp;&nbsp;&nbsp;create&nbsp;table&nbsp;private_table(c1,&nbsp;c2);<br/>
+&nbsp;&nbsp;&nbsp;&nbsp;"""</span><span class="p_operator">)</span><span class="p_default"><br/>
+</span><span class="p_identifier">con</span><span class="p_operator">.</span><span class="p_identifier">set_authorizer</span><span class="p_operator">(</span><span class="p_identifier">authorizer_callback</span><span class="p_operator">)</span><span class="p_default"><br/>
+<br/>
+</span><span class="p_word">try</span><span class="p_operator">:</span><span class="p_default"><br/>
+&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="p_identifier">con</span><span class="p_operator">.</span><span class="p_identifier">execute</span><span class="p_operator">(</span><span class="p_string">"select&nbsp;*&nbsp;from&nbsp;private_table"</span><span class="p_operator">)</span><span class="p_default"><br/>
+</span><span class="p_word">except</span><span class="p_default">&nbsp;</span><span class="p_identifier">sqlite</span><span class="p_operator">.</span><span class="p_identifier">DatabaseError</span><span class="p_operator">,</span><span class="p_default">&nbsp;</span><span class="p_identifier">e</span><span class="p_operator">:</span><span class="p_default"><br/>
+&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="p_word">print</span><span class="p_default">&nbsp;</span><span class="p_string">"SELECT&nbsp;FROM&nbsp;private_table&nbsp;=&gt;"</span><span class="p_operator">,</span><span class="p_default">&nbsp;</span><span class="p_identifier">e</span><span class="p_operator">.</span><span class="p_identifier">args</span><span class="p_operator">[</span><span class="p_number">0</span><span class="p_operator">]</span><span class="p_default">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="p_commentline">#&nbsp;access&nbsp;...&nbsp;prohibited</span><span class="p_default"><br/>
+<br/>
+</span><span class="p_word">try</span><span class="p_operator">:</span><span class="p_default"><br/>
+&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="p_identifier">con</span><span class="p_operator">.</span><span class="p_identifier">execute</span><span class="p_operator">(</span><span class="p_string">"insert&nbsp;into&nbsp;public_table(c1,&nbsp;c2)&nbsp;values&nbsp;(1,&nbsp;2)"</span><span class="p_operator">)</span><span class="p_default"><br/>
+</span><span class="p_word">except</span><span class="p_default">&nbsp;</span><span class="p_identifier">sqlite</span><span class="p_operator">.</span><span class="p_identifier">DatabaseError</span><span class="p_operator">,</span><span class="p_default">&nbsp;</span><span class="p_identifier">e</span><span class="p_operator">:</span><span class="p_default"><br/>
+&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="p_word">print</span><span class="p_default">&nbsp;</span><span class="p_string">"DML&nbsp;command&nbsp;=&gt;"</span><span class="p_operator">,</span><span class="p_default">&nbsp;</span><span class="p_identifier">e</span><span class="p_operator">.</span><span class="p_identifier">args</span><span class="p_operator">[</span><span class="p_number">0</span><span class="p_operator">]</span><span class="p_default">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="p_commentline">#&nbsp;access&nbsp;...&nbsp;prohibited</span><span class="p_default"><br/>
+</span>
+</div>
+</blockquote>
+</div>
 </div>
 <div class="section">
 <h1><a id="sqlite-and-python-types" name="sqlite-and-python-types">4. SQLite and Python types</a></h1>
@@ -1138,7 +1185,6 @@ via semicolons as strings in SQLite.</p>
 <p>Let's first define a converter function that accepts the string as a parameter and constructs a Point object from it.</p>
 <p>!!! Note that converter functions <em>always</em> get called with a string, no matter
 under which data type you sent the value to SQLite !!!</p>
-<p>!!! Also note that converter names are looked up in a case-sensitive manner !!!</p>
 <blockquote>
 <div class="code-block">
 <span class="p_word">def</span><span class="p_default">&nbsp;</span><span class="p_defname">convert_point</span><span class="p_operator">(</span><span class="p_identifier">s</span><span class="p_operator">):</span><span class="p_default"><br/>
index 135cf4c..0a74245 100644 (file)
@@ -5,7 +5,7 @@ pysqlite usage guide
 | (c) 2004-2005 David Rushby
 | (c) 2005-2006 Gerhard Häring
 
-Last updated for pysqlite 2.2.1
+Last updated for pysqlite 2.3.0
 
 Table Of Contents
 =================
@@ -25,6 +25,7 @@ Table Of Contents
 |   `3.3 Creating and using collations`_
 |   `3.4 Checking for complete statements`_
 |   `3.5 Enabling SQLite's shared cache`_
+|   `3.6 Setting an authorizer callback`_
 | `4. SQLite and Python types`_
 |   `4.1 Introduction`_
 |   `4.2 Using adapters to store additional Python types in SQLite databases`_
@@ -173,7 +174,6 @@ Python DB API.
       declared type, i. e. for "integer primary key", it will parse out
       "integer". Then for that column, it will look into pysqlite's converters
       dictionary and use the converter function registered for that type there.
-      Converter names are case-sensitive!
 
     * **sqlite.PARSE_COLNAMES** - This makes pysqlite parse the column name
       for each column it returns. It will look for a string formed
@@ -188,7 +188,7 @@ Python DB API.
 
       The following example uses the column name *timestamp*, which is already
       registered by default in the converters dictionary with an appropriate
-      converter! Note that converter names are case-sensitive!
+      converter!
 
       Example:
 
@@ -225,8 +225,7 @@ Python DB API.
   registers a callable to convert a bytestring from the database into a custom
   Python type. The converter will be invoked for all database values that are
   of the type ``typename``. Confer the parameter **detect_types** of the
-  **connect** method for how the type detection works. Note that the case
-  ``typename`` and the name of the type in your query must match!
+  **connect** method for how the type detection works.
 
 * **register_adapter** function - ``register_adapter(type, callable)``
   registers a callable to convert the custom Python **type** into one of
@@ -234,6 +233,10 @@ Python DB API.
   value, and must return a value of the following types: int, long, float, str
   (UTF-8 encoded), unicode or buffer.
 
+* **enable_callback_tracebacks** function - ``enable_callback_tracebacks(flag)``
+  Can be used to enable displaying tracebacks of exceptions in user-defined functions, aggregates and other callbacks being printed to stderr.
+  methods should never raise any exception. This feature is off by default.
+
 * **Connection** class
 
   * **isolation_level** attribute (read-write)
@@ -376,7 +379,7 @@ Python DB API.
     execute more than one statement with it, it will raise a Warning. Use
     *executescript* if want to execute multiple SQL statements with one call.
 
-* **executescript** method
+  * **executescript** method
 
     .. code-block:: Python
 
@@ -394,13 +397,19 @@ Python DB API.
      :language: Python
      :source-file: code/executescript.py
 
+  * **interrupt** method
+
+    This method has no arguments. You can call it from a different thread to
+    abort any queries that are currently executing on the connection. This can
+    be used to let the user abort runaway queries, for example.
+
   * **rowcount** attribute
 
     Although pysqlite's Cursors implement this attribute, the database
     engine's own support for the determination of "rows affected"/"rows
     selected" is quirky.
 
-    For ``SELECT`` statements, *rowcount* is always None because pysqlite
+    For ``SELECT`` statements, *rowcount* is always -1 because pysqlite
     cannot determine the number of rows a query produced until all rows
     were fetched.
 
@@ -573,8 +582,8 @@ functions with the connection's **create_function** method:
     the Python function
 
   The function can return any of pysqlite's supported SQLite types: unicode,
-  str, int, long, float, buffer and None.  The function should never raise an
-  exception.
+  str, int, long, float, buffer and None.  Any exception in the user-defined
+  function leads to the SQL statement executed being aborted.
 
   Example:
 
@@ -597,8 +606,9 @@ create new aggregate functions with the connection's *create_aggregate* method.
   method which will return the final result of the aggregate.
 
   The *finalize* method can return any of pysqlite's supported SQLite types:
-  unicode, str, int, long, float, buffer and None. The aggregate class's
-  methods should never raise any exception.
+  unicode, str, int, long, float, buffer and None. Any exception in the
+  aggregate's *__init__*, *step* or *finalize* methods lead to the SQL
+  statement executed being aborted.
 
   Example:
 
@@ -660,6 +670,26 @@ To enable SQLite's shared cache for the calling thread, call the function
    :language: Python
    :source-file: code/shared_cache.py
 
+3.6 Setting an authorizer callback
+----------------------------------
+
+You can set an authorizer callback if you want to restrict what your users can
+do with the database. This is mostly useful if you accept arbitrary SQL from
+users and want to execute it safely. See the relevant section in the SQL
+documentation for details:
+http://sqlite.org/capi3ref.html#sqlite3_set_authorizer
+
+All necessary constants like SQLITE_OK, SQLITE_DENY, SQLITE_IGNORE,
+SQLITE_SELECT, SQLITE_CREATE_INDEX and all other authorizer-related constants
+are available through the dbapi2 module.
+
+Here's an example that demonstrates the usage of this function:
+
+  .. code-block::
+   :language: Python
+   :source-file: code/authorizer.py
+
+
 4. SQLite and Python types
 ==========================
 
@@ -776,8 +806,6 @@ Let's first define a converter function that accepts the string as a parameter a
 !!! Note that converter functions *always* get called with a string, no matter
 under which data type you sent the value to SQLite !!!
 
-!!! Also note that converter names are looked up in a case-sensitive manner !!!
-
   .. code-block:: Python
 
     def convert_point(s):
index f07ad15..bef87e5 100644 (file)
@@ -24,6 +24,7 @@
 import unittest
 from pysqlite2.test import dbapi, types, userfunctions, factory, transactions,\
     hooks, regression
+from pysqlite2 import dbapi2 as sqlite
 
 def suite():
     return unittest.TestSuite(
index 6cc3015..04e16ef 100644 (file)
@@ -61,6 +61,14 @@ class RegressionTests(unittest.TestCase):
 
         con.rollback()
 
+    def CheckColumnNameWithSpaces(self):
+        cur = self.con.cursor()
+        cur.execute('select 1 as "foo bar [datetime]"')
+        self.failUnlessEqual(cur.description[0][0], "foo bar")
+
+        cur.execute('select 1 as "foo baz"')
+        self.failUnlessEqual(cur.description[0][0], "foo baz")
+
 def suite():
     regression_suite = unittest.makeSuite(RegressionTests, "Check")
     return unittest.TestSuite((regression_suite,))
index 977044b..f142280 100644 (file)
@@ -101,16 +101,16 @@ class DeclTypesTests(unittest.TestCase):
         self.cur.execute("create table test(i int, s str, f float, b bool, u unicode, foo foo, bin blob)")
 
         # override float, make them always return the same number
-        sqlite.converters["float"] = lambda x: 47.2
+        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["BOOL"] = lambda x: bool(int(x))
+        sqlite.converters["FOO"] = DeclTypesTests.Foo
 
     def tearDown(self):
-        del sqlite.converters["float"]
-        del sqlite.converters["bool"]
-        del sqlite.converters["foo"]
+        del sqlite.converters["FLOAT"]
+        del sqlite.converters["BOOL"]
+        del sqlite.converters["FOO"]
         self.cur.close()
         self.con.close()
 
@@ -208,14 +208,14 @@ class ColNamesTests(unittest.TestCase):
         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["FOO"] = lambda x: "[%s]" % x
+        sqlite.converters["BAR"] = lambda x: "<%s>" % x
+        sqlite.converters["EXC"] = lambda x: 5/0
 
     def tearDown(self):
-        del sqlite.converters["foo"]
-        del sqlite.converters["bar"]
-        del sqlite.converters["exc"]
+        del sqlite.converters["FOO"]
+        del sqlite.converters["BAR"]
+        del sqlite.converters["EXC"]
         self.cur.close()
         self.con.close()
 
@@ -231,12 +231,6 @@ class ColNamesTests(unittest.TestCase):
         val = self.cur.fetchone()[0]
         self.failUnlessEqual(val, None)
 
-    def CheckExc(self):
-        # Exceptions in type converters result in returned Nones
-        self.cur.execute('select 5 as "x [exc]"')
-        val = self.cur.fetchone()[0]
-        self.failUnlessEqual(val, None)
-
     def CheckColName(self):
         self.cur.execute("insert into test(x) values (?)", ("xxx",))
         self.cur.execute('select x as "x [bar]" from test')
index 7982147..49f27de 100644 (file)
@@ -55,6 +55,9 @@ class AggrNoStep:
     def __init__(self):
         pass
 
+    def finalize(self):
+        return 1
+
 class AggrNoFinalize:
     def __init__(self):
         pass
@@ -144,9 +147,12 @@ class FunctionTests(unittest.TestCase):
     def CheckFuncRefCount(self):
         def getfunc():
             def f():
-                return val
+                return 1
             return f
-        self.con.create_function("reftest", 0, getfunc())
+        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()")
 
@@ -195,9 +201,12 @@ class FunctionTests(unittest.TestCase):
 
     def CheckFuncException(self):
         cur = self.con.cursor()
-        cur.execute("select raiseexception()")
-        val = cur.fetchone()[0]
-        self.failUnlessEqual(val, None)
+        try:
+            cur.execute("select raiseexception()")
+            cur.fetchone()
+            self.fail("should have raised OperationalError")
+        except sqlite.OperationalError, e:
+            self.failUnlessEqual(e.args[0], 'user-defined function raised exception')
 
     def CheckParamString(self):
         cur = self.con.cursor()
@@ -267,31 +276,47 @@ class AggregateTests(unittest.TestCase):
 
     def CheckAggrNoStep(self):
         cur = self.con.cursor()
-        cur.execute("select nostep(t) from test")
+        try:
+            cur.execute("select nostep(t) from test")
+            self.fail("should have raised an AttributeError")
+        except AttributeError, e:
+            self.failUnlessEqual(e.args[0], "AggrNoStep instance has no attribute 'step'")
 
     def CheckAggrNoFinalize(self):
         cur = self.con.cursor()
-        cur.execute("select nofinalize(t) from test")
-        val = cur.fetchone()[0]
-        self.failUnlessEqual(val, None)
+        try:
+            cur.execute("select nofinalize(t) from test")
+            val = cur.fetchone()[0]
+            self.fail("should have raised an OperationalError")
+        except sqlite.OperationalError, e:
+            self.failUnlessEqual(e.args[0], "user-defined aggregate's 'finalize' method raised error")
 
     def CheckAggrExceptionInInit(self):
         cur = self.con.cursor()
-        cur.execute("select excInit(t) from test")
-        val = cur.fetchone()[0]
-        self.failUnlessEqual(val, None)
+        try:
+            cur.execute("select excInit(t) from test")
+            val = cur.fetchone()[0]
+            self.fail("should have raised an OperationalError")
+        except sqlite.OperationalError, e:
+            self.failUnlessEqual(e.args[0], "user-defined aggregate's '__init__' method raised error")
 
     def CheckAggrExceptionInStep(self):
         cur = self.con.cursor()
-        cur.execute("select excStep(t) from test")
-        val = cur.fetchone()[0]
-        self.failUnlessEqual(val, 42)
+        try:
+            cur.execute("select excStep(t) from test")
+            val = cur.fetchone()[0]
+            self.fail("should have raised an OperationalError")
+        except sqlite.OperationalError, e:
+            self.failUnlessEqual(e.args[0], "user-defined aggregate's 'step' method raised error")
 
     def CheckAggrExceptionInFinalize(self):
         cur = self.con.cursor()
-        cur.execute("select excFinalize(t) from test")
-        val = cur.fetchone()[0]
-        self.failUnlessEqual(val, None)
+        try:
+            cur.execute("select excFinalize(t) from test")
+            val = cur.fetchone()[0]
+            self.fail("should have raised an OperationalError")
+        except sqlite.OperationalError, e:
+            self.failUnlessEqual(e.args[0], "user-defined aggregate's 'finalize' method raised error")
 
     def CheckAggrCheckParamStr(self):
         cur = self.con.cursor()
@@ -331,10 +356,55 @@ class AggregateTests(unittest.TestCase):
         val = cur.fetchone()[0]
         self.failUnlessEqual(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):
+        sqlite.enable_callback_tracebacks(1)
+        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")
-    return unittest.TestSuite((function_suite, aggregate_suite))
+    authorizer_suite = unittest.makeSuite(AuthorizerTests, "Check") 
+    return unittest.TestSuite((function_suite, aggregate_suite, authorizer_suite))
 
 def test():
     runner = unittest.TextTestRunner()
index 991cf7d..ba6c770 100644 (file)
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,5 +1,5 @@
 [build_ext]
 define=
-include_dirs=/usr/local/include
-library_dirs=/usr/local/lib
+include_dirs=/usr/include
+library_dirs=/usr/lib
 libraries=sqlite3
diff --git a/src/compat.h b/src/compat.h
deleted file mode 100644 (file)
index d87eac9..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-/* compat.h - compatibility macros
- *
- * Copyright (C) 2006 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_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
-
-#endif
index 9058657..bf74710 100644 (file)
@@ -28,7 +28,7 @@
 #include "cursor.h"
 #include "prepare_protocol.h"
 #include "util.h"
-#include "compat.h"
+#include "sqlitecompat.h"
 
 #include "pythread.h"
 
@@ -405,8 +405,6 @@ void _set_result(sqlite3_context* context, PyObject* py_val)
     PyObject* stringval;
 
     if ((!py_val) || PyErr_Occurred()) {
-        /* Errors in callbacks are ignored, and we return NULL */
-        PyErr_Clear();
         sqlite3_result_null(context);
     } else if (py_val == Py_None) {
         sqlite3_result_null(context);
@@ -519,8 +517,17 @@ void _func_callback(sqlite3_context* context, int argc, sqlite3_value** argv)
         Py_DECREF(args);
     }
 
-    _set_result(context, py_retval);
-    Py_XDECREF(py_retval);
+    if (py_retval) {
+        _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);
+    }
 
     PyGILState_Release(threadstate);
 }
@@ -545,8 +552,13 @@ static void _step_callback(sqlite3_context *context, int argc, sqlite3_value** p
         *aggregate_instance = PyObject_CallFunction(aggregate_class, "");
 
         if (PyErr_Occurred()) {
-            PyErr_Clear();
             *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;
         }
     }
@@ -565,7 +577,12 @@ static void _step_callback(sqlite3_context *context, int argc, sqlite3_value** p
     Py_DECREF(args);
 
     if (!function_result) {
-        PyErr_Clear();
+        if (_enable_callback_tracebacks) {
+            PyErr_Print();
+        } else {
+            PyErr_Clear();
+        }
+        sqlite3_result_error(context, "user-defined aggregate's 'step' method raised error", -1);
     }
 
 error:
@@ -597,13 +614,16 @@ void _final_callback(sqlite3_context* context)
 
     function_result = PyObject_CallMethod(*aggregate_instance, "finalize", "");
     if (!function_result) {
-        PyErr_Clear();
-        Py_INCREF(Py_None);
-        function_result = Py_None;
+        if (_enable_callback_tracebacks) {
+            PyErr_Print();
+        } else {
+            PyErr_Clear();
+        }
+        sqlite3_result_error(context, "user-defined aggregate's 'finalize' method raised error", -1);
+    } else {
+        _set_result(context, function_result);
     }
 
-    _set_result(context, function_result);
-
 error:
     Py_XDECREF(*aggregate_instance);
     Py_XDECREF(function_result);
@@ -699,6 +719,61 @@ PyObject* connection_create_aggregate(Connection* self, PyObject* args, PyObject
     }
 }
 
+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;
+    PyGILState_STATE gilstate;
+
+    gilstate = PyGILState_Ensure();
+    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);
+    }
+
+    PyGILState_Release(gilstate);
+    return rc;
+}
+
+PyObject* connection_set_authorizer(Connection* self, PyObject* args, PyObject* kwargs)
+{
+    PyObject* authorizer_cb;
+
+    static char *kwlist[] = { "authorizer_callback", NULL };
+    int rc;
+
+    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(OperationalError, "Error setting authorizer callback");
+        return NULL;
+    } else {
+        PyDict_SetItem(self->function_pinboard, authorizer_cb, Py_None);
+
+        Py_INCREF(Py_None);
+        return Py_None;
+    }
+}
+
 int check_thread(Connection* self)
 {
     if (self->check_same_thread) {
@@ -737,9 +812,13 @@ static int connection_set_isolation_level(Connection* self, PyObject* isolation_
 
     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->begin_statement = NULL;
         self->isolation_level = Py_None;
 
         res = connection_commit(self, NULL);
@@ -970,6 +1049,24 @@ finally:
     return result;
 }
 
+static PyObject *
+connection_interrupt(Connection* self, PyObject* args)
+{
+    PyObject* retval = NULL;
+
+    if (!check_connection(self)) {
+        goto finally;
+    }
+
+    sqlite3_interrupt(self->db);
+
+    Py_INCREF(Py_None);
+    retval = Py_None;
+
+finally:
+    return retval;
+}
+
 static PyObject *
 connection_create_collation(Connection* self, PyObject* args)
 {
@@ -1063,6 +1160,8 @@ static PyMethodDef connection_methods[] = {
         PyDoc_STR("Creates a new function. Non-standard.")},
     {"create_aggregate", (PyCFunction)connection_create_aggregate, METH_VARARGS|METH_KEYWORDS,
         PyDoc_STR("Creates a new aggregate. Non-standard.")},
+    {"set_authorizer", (PyCFunction)connection_set_authorizer, METH_VARARGS|METH_KEYWORDS,
+        PyDoc_STR("Sets authorizer callback. Non-standard.")},
     {"execute", (PyCFunction)connection_execute, METH_VARARGS,
         PyDoc_STR("Executes a SQL statement. Non-standard.")},
     {"executemany", (PyCFunction)connection_executemany, METH_VARARGS,
@@ -1071,6 +1170,8 @@ static PyMethodDef connection_methods[] = {
         PyDoc_STR("Executes a multiple SQL statements at once. Non-standard.")},
     {"create_collation", (PyCFunction)connection_create_collation, METH_VARARGS,
         PyDoc_STR("Creates a collation function. Non-standard.")},
+    {"interrupt", (PyCFunction)connection_interrupt, METH_NOARGS,
+        PyDoc_STR("Abort any pending database operation. Non-standard.")},
     {NULL, NULL}
 };
 
index 791b6ed..98f5e0d 100644 (file)
 #include "cursor.h"
 #include "module.h"
 #include "util.h"
-#include "compat.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* cursor_iternext(Cursor *self);
 
@@ -133,6 +137,22 @@ void cursor_dealloc(Cursor* self)
     self->ob_type->tp_free((PyObject*)self);
 }
 
+PyObject* _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 build_row_cast_map(Cursor* self)
 {
     int i;
@@ -170,7 +190,7 @@ int build_row_cast_map(Cursor* self)
                             break;
                         }
 
-                        converter = PyDict_GetItem(converters, key);
+                        converter = _get_converter(key);
                         Py_DECREF(key);
                         break;
                     }
@@ -191,7 +211,7 @@ int build_row_cast_map(Cursor* self)
                     }
                 }
 
-                converter = PyDict_GetItem(converters, py_decltype);
+                converter = _get_converter(py_decltype);
                 Py_DECREF(py_decltype);
             }
         }
@@ -224,7 +244,10 @@ PyObject* _build_column_name(const char* colname)
     }
 
     for (pos = colname;; pos++) {
-        if (*pos == 0 || *pos == ' ') {
+        if (*pos == 0 || *pos == '[') {
+            if ((*pos == '[') && (pos > colname) && (*(pos-1) == ' ')) {
+                pos--;
+            }
             return PyString_FromStringAndSize(colname, pos - colname);
         }
     }
@@ -308,13 +331,10 @@ PyObject* _fetch_one_row(Cursor* self)
                     return NULL;
                 }
                 converted = PyObject_CallFunction(converter, "O", item);
+                Py_DECREF(item);
                 if (!converted) {
-                    /* TODO: have a way to log these errors */
-                    Py_INCREF(Py_None);
-                    converted = Py_None;
-                    PyErr_Clear();
+                    break;
                 }
-                Py_DECREF(item);
             }
         } else {
             Py_BEGIN_ALLOW_THREADS
@@ -342,10 +362,10 @@ PyObject* _fetch_one_row(Cursor* self)
 
                     if (!converted) {
                         colname = sqlite3_column_name(self->statement->st, i);
-                        if (colname) {
+                        if (!colname) {
                             colname = "<unknown column name>";
                         }
-                        PyOS_snprintf(buf, sizeof(buf) - 1, "Could not decode to UTF-8 column %s with text %s",
+                        PyOS_snprintf(buf, sizeof(buf) - 1, "Could not decode to UTF-8 column '%s' with text '%s'",
                                      colname , val_str);
                         PyErr_SetString(OperationalError, buf);
                     }
@@ -369,7 +389,12 @@ PyObject* _fetch_one_row(Cursor* self)
             }
         }
 
-        PyTuple_SetItem(row, i, converted);
+        if (converted) {
+            PyTuple_SetItem(row, i, converted);
+        } else {
+            Py_INCREF(Py_None);
+            PyTuple_SetItem(row, i, Py_None);
+        }
     }
 
     if (PyErr_Occurred()) {
@@ -459,6 +484,9 @@ PyObject* _query_execute(Cursor* self, int multiple, PyObject* args)
         Py_DECREF(second_argument);
 
         parameters_iter = PyObject_GetIter(parameters_list);
+        if (!parameters_iter) {
+            goto error;
+        }
     }
 
     if (self->statement != NULL) {
@@ -591,6 +619,14 @@ PyObject* _query_execute(Cursor* self, int multiple, PyObject* args)
                     goto error;
                 }
             } else {
+                if (PyErr_Occurred()) {
+                    /* there was an error that occured in a user-defined callback */
+                    if (_enable_callback_tracebacks) {
+                        PyErr_Print();
+                    } else {
+                        PyErr_Clear();
+                    }
+                }
                 _seterror(self->connection->db);
                 goto error;
             }
@@ -669,7 +705,7 @@ PyObject* _query_execute(Cursor* self, int multiple, PyObject* args)
 error:
     Py_XDECREF(operation_bytestr);
     Py_XDECREF(parameters);
-    Py_DECREF(parameters_iter);
+    Py_XDECREF(parameters_iter);
     Py_XDECREF(parameters_list);
 
     if (PyErr_Occurred()) {
index 93956b7..3022af4 100644 (file)
@@ -1,25 +1,25 @@
-/* module.c - the module itself
- *
- * Copyright (C) 2004-2006 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.
- */
+    /* module.c - the module itself
    *
    * Copyright (C) 2004-2006 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"
@@ -40,6 +40,7 @@ PyObject* Error, *Warning, *InterfaceError, *DatabaseError, *InternalError,
     *NotSupportedError, *OptimizedUnicode;
 
 PyObject* converters;
+int _enable_callback_tracebacks;
 
 static PyObject* module_connect(PyObject* self, PyObject* args, PyObject*
         kwargs)
@@ -140,14 +141,42 @@ static PyObject* module_register_adapter(PyObject* self, PyObject* args, PyObjec
 
 static PyObject* module_register_converter(PyObject* self, PyObject* args, PyObject* kwargs)
 {
-    PyObject* name;
+    char* orig_name;
+    char* name = NULL;
+    char* c;
     PyObject* callable;
+    PyObject* retval = NULL;
 
-    if (!PyArg_ParseTuple(args, "OO", &name, &callable)) {
+    if (!PyArg_ParseTuple(args, "sO", &orig_name, &callable)) {
         return NULL;
     }
 
-    if (PyDict_SetItem(converters, name, callable) != 0) {
+    /* convert the name to lowercase */
+    name = PyMem_Malloc(strlen(orig_name) + 2);
+    if (!name) {
+        goto error;
+    }
+    strcpy(name, orig_name);
+    for (c = name; *c != (char)0; c++) {
+        *c = (*c) & 0xDF;
+    }
+
+    if (PyDict_SetItemString(converters, name, callable) != 0) {
+        goto error;
+    }
+
+    Py_INCREF(Py_None);
+    retval = Py_None;
+error:
+    if (name) {
+        PyMem_Free(name);
+    }
+    return retval;
+}
+
+static PyObject* enable_callback_tracebacks(PyObject* self, PyObject* args, PyObject* kwargs)
+{
+    if (!PyArg_ParseTuple(args, "i", &_enable_callback_tracebacks)) {
         return NULL;
     }
 
@@ -174,13 +203,65 @@ static PyMethodDef module_methods[] = {
     {"register_adapter", (PyCFunction)module_register_adapter, METH_VARARGS, PyDoc_STR("Registers an adapter with pysqlite's adapter registry. Non-standard.")},
     {"register_converter", (PyCFunction)module_register_converter, METH_VARARGS, PyDoc_STR("Registers a converter with pysqlite. Non-standard.")},
     {"adapt",  (PyCFunction)psyco_microprotocols_adapt, METH_VARARGS, psyco_microprotocols_adapt_doc},
+    {"enable_callback_tracebacks",  (PyCFunction)enable_callback_tracebacks, METH_VARARGS, PyDoc_STR("Enable or disable callback functions throwing errors to stderr.")},
     {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("pysqlite2._sqlite", module_methods);
 
@@ -276,25 +357,27 @@ PyMODINIT_FUNC init_sqlite(void)
     OptimizedUnicode = (PyObject*)&PyCell_Type;
     PyDict_SetItemString(dict, "OptimizedUnicode", OptimizedUnicode);
 
-    if (!(tmp_obj = PyInt_FromLong(PARSE_DECLTYPES))) {
-        goto error;
-    }
-    PyDict_SetItemString(dict, "PARSE_DECLTYPES", tmp_obj);
-
-    if (!(tmp_obj = PyInt_FromLong(PARSE_COLNAMES))) {
-        goto error;
+    /* 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);
     }
-    PyDict_SetItemString(dict, "PARSE_COLNAMES", 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 */
     microprotocols_init(dict);
@@ -302,6 +385,8 @@ PyMODINIT_FUNC init_sqlite(void)
     /* initialize the default converters */
     converters_init(dict);
 
+    _enable_callback_tracebacks = 0;
+
     /* Original comment form _bsddb.c in the Python core. This is also still
      * needed nowadays for Python 2.3/2.4.
      * 
index f3e2aa1..00eff1f 100644 (file)
@@ -25,7 +25,7 @@
 #define PYSQLITE_MODULE_H
 #include "Python.h"
 
-#define PYSQLITE_VERSION "2.2.2"
+#define PYSQLITE_VERSION "2.3.0"
 
 extern PyObject* Error;
 extern PyObject* Warning;
@@ -50,6 +50,8 @@ extern PyObject* time_sleep;
  */
 extern PyObject* converters;
 
+extern int _enable_callback_tracebacks;
+
 #define PARSE_DECLTYPES 1
 #define PARSE_COLNAMES 2
 #endif
index 59cd194..80b6135 100644 (file)
--- a/src/row.c
+++ b/src/row.c
@@ -23,7 +23,7 @@
 
 #include "row.h"
 #include "cursor.h"
-#include "compat.h"
+#include "sqlitecompat.h"
 
 void row_dealloc(Row* self)
 {
diff --git a/src/sqlitecompat.h b/src/sqlitecompat.h
new file mode 100644 (file)
index 0000000..c379825
--- /dev/null
@@ -0,0 +1,34 @@
+/* sqlitecompat.h - compatibility macros
+ *
+ * Copyright (C) 2006 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_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
+
+#endif
index 7f1fa9f..55923e7 100644 (file)
@@ -26,7 +26,7 @@
 #include "connection.h"
 #include "microprotocols.h"
 #include "prepare_protocol.h"
-#include "compat.h"
+#include "sqlitecompat.h"
 
 /* prototypes */
 int check_remaining_sql(const char* tail);
@@ -47,8 +47,6 @@ int statement_create(Statement* self, Connection* connection, PyObject* sql)
     PyObject* sql_str;
     char* sql_cstr;
 
-    self->st = NULL;
-
     self->st = NULL;
     self->in_use = 0;
 
@@ -81,6 +79,7 @@ int statement_create(Statement* self, Connection* connection, PyObject* sql)
 
     if (rc == SQLITE_OK && check_remaining_sql(tail)) {
         (void)sqlite3_finalize(self->st);
+        self->st = NULL;
         rc = PYSQLITE_TOO_MUCH_SQL;
     }