Fixed DB-API violation of execute-Method. Free resources after executemany. Tests against SQLite. Improved type handling.

master
baztian 2011-01-26 13:28:47 +01:00
parent cd8b93ca27
commit 4884cc60d1
4 changed files with 131 additions and 60 deletions

View File

@ -1,6 +1,6 @@
===================================================================== =================================================================
JayDeBeApi - bridge from JDBC database drivers to Python DB-API JayDeBeApi - bridge from JDBC database drivers to Python DB-API
===================================================================== =================================================================
The JayDeBeApi module allows you to connect from Python code to The JayDeBeApi module allows you to connect from Python code to
databases using Java `JDBC databases using Java `JDBC
@ -11,10 +11,6 @@ It works on ordinary Python (cPython) using the JPype_ Java
integration or on `Jython <http://www.jython.org/>`_ to make use of integration or on `Jython <http://www.jython.org/>`_ to make use of
the Java JDBC driver. the Java JDBC driver.
It has been tested with `Hypersonic SQL (HSQLDB)
<http://hsqldb.org/>`_ and `IBM DB2
<http://www.ibm.com/software/data/db2/>`_ for z/OS.
In contrast to zxJDBC from the Jython project JayDeBeApi let's you In contrast to zxJDBC from the Jython project JayDeBeApi let's you
access a database with Jython AND Python with only minor code access a database with Jython AND Python with only minor code
modifications. JayDeBeApi's future goal is to provide a unique and modifications. JayDeBeApi's future goal is to provide a unique and
@ -88,6 +84,7 @@ in memory database on my Ubuntu machine I'm starting Python by running ::
Now I have to configure JPype Now I have to configure JPype
>>> import jpype
>>> jar = '/path/to/my/driver/hsqldb.jar' >>> jar = '/path/to/my/driver/hsqldb.jar'
>>> args='-Djava.class.path=%s' % jar >>> args='-Djava.class.path=%s' % jar
>>> jpype.startJVM(jvm_path, args) >>> jpype.startJVM(jvm_path, args)
@ -98,6 +95,19 @@ or in Jython I have to
>>> import sys >>> import sys
>>> sys.path.append(jar) >>> sys.path.append(jar)
Supported databases
===================
In theory every database with a suitable JDBC driver should work. It
is known to work with the following databases:
* `SQLite 3 <http://www.sqlite.org/>`_ using `SqliteJDBC
<http://www.zentus.com/sqlitejdbc/>`_ v056
* `Hypersonic SQL (HSQLDB) <http://hsqldb.org/>`_ 1.8.1.3
* `IBM DB2 <http://www.ibm.com/software/data/db2/>`_ for z/OS using
JDBC type 4 drivers.
Contributing Contributing
============ ============
@ -116,18 +126,27 @@ distribution for details.
Changelog Changelog
========= =========
- trunk
- Fixed DB-API_ violation: Use ``curs.execute('foo ?', (bar, baz))``
instead of ``curs.execute('foo ?', bar, baz)``.
- Free resources after ``executemany`` call.
- Improved type handling.
- 0.1.2 - 0.1.2
- ``easy_install JayDeBeApi`` should really work - ``easy_install JayDeBeApi`` should really work.
- 0.1.1 - 0.1.1
- Fixed bug #688290 "NULL values with converters error on fetch." - Fixed bug #688290 "NULL values with converters error on fetch".
- Fixed bug #684909 "Selecting ROWIDs errors out on fetch." - Fixed bug #684909 "Selecting ROWIDs errors out on fetch".
- 0.1 - 0.1
- Initial release - Initial release.
To do To do
===== =====

View File

@ -7,7 +7,7 @@ Author: Bastian Bowe
Author-email: bastian.bowe@gmail.com Author-email: bastian.bowe@gmail.com
License: GNU LGPL License: GNU LGPL
Description: ===================================================================== Description: =====================================================================
JayDeBeApi - bridge from JDBC database drivers to Python DB-API JayDeBeApi - bridge from JDBC database drivers to Python DB-API
===================================================================== =====================================================================
The JayDeBeApi module allows you to connect from Python code to The JayDeBeApi module allows you to connect from Python code to
@ -19,10 +19,6 @@ Description: ===================================================================
integration or on `Jython <http://www.jython.org/>`_ to make use of integration or on `Jython <http://www.jython.org/>`_ to make use of
the Java JDBC driver. the Java JDBC driver.
It has been tested with `Hypersonic SQL (HSQLDB)
<http://hsqldb.org/>`_ and `IBM DB2
<http://www.ibm.com/software/data/db2/>`_ for z/OS.
In contrast to zxJDBC from the Jython project JayDeBeApi let's you In contrast to zxJDBC from the Jython project JayDeBeApi let's you
access a database with Jython AND Python with only minor code access a database with Jython AND Python with only minor code
modifications. JayDeBeApi's future goal is to provide a unique and modifications. JayDeBeApi's future goal is to provide a unique and
@ -37,7 +33,7 @@ Description: ===================================================================
You can get and install JayDeBeApi with `easy_install You can get and install JayDeBeApi with `easy_install
<http://peak.telecommunity.com/DevCenter/EasyInstall>`_ :: <http://peak.telecommunity.com/DevCenter/EasyInstall>`_ ::
$ easy_install JayDeBeApi $ easy_install JayDeBeApi
If you want to install JayDeBeApi in Jython make sure to have If you want to install JayDeBeApi in Jython make sure to have
EasyInstall available for it. EasyInstall available for it.
@ -45,15 +41,15 @@ Description: ===================================================================
Or you can get a copy of the source branch using `bzr Or you can get a copy of the source branch using `bzr
<http://bazaar.canonical.com/>`_ by running :: <http://bazaar.canonical.com/>`_ by running ::
$ bzr branch lp:jaydebeapi $ bzr branch lp:jaydebeapi
and install it with :: and install it with ::
$ python setup.py install $ python setup.py install
or if you are using Jython use :: or if you are using Jython use ::
$ jython setup.py install $ jython setup.py install
It has been tested with Jython 2.5.2. It has been tested with Jython 2.5.2.
@ -92,10 +88,11 @@ Description: ===================================================================
access the database driver's jar files. If I want to connect to a HSQL access the database driver's jar files. If I want to connect to a HSQL
in memory database on my Ubuntu machine I'm starting Python by running :: in memory database on my Ubuntu machine I'm starting Python by running ::
$ JAVA_HOME=/usr/lib/jvm/java-6-openjdk python $ JAVA_HOME=/usr/lib/jvm/java-6-openjdk python
Now I have to configure JPype Now I have to configure JPype
>>> import jpype
>>> jar = '/path/to/my/driver/hsqldb.jar' >>> jar = '/path/to/my/driver/hsqldb.jar'
>>> args='-Djava.class.path=%s' % jar >>> args='-Djava.class.path=%s' % jar
>>> jpype.startJVM(jvm_path, args) >>> jpype.startJVM(jvm_path, args)
@ -106,6 +103,19 @@ Description: ===================================================================
>>> import sys >>> import sys
>>> sys.path.append(jar) >>> sys.path.append(jar)
Supported databases
===================
In theory every database with a suitable JDBC driver should work. It
is known to work with the following databases:
* `SQLite 3 <http://www.sqlite.org/>`_ using `SqliteJDBC
<http://www.zentus.com/sqlitejdbc/>`_ v056
* `Hypersonic SQL (HSQLDB) <http://hsqldb.org/>`_ 1.8.1.3
* `IBM DB2 <http://www.ibm.com/software/data/db2/>`_ for z/OS using
JDBC type 4 drivers.
Contributing Contributing
============ ============
@ -124,27 +134,32 @@ Description: ===================================================================
Changelog Changelog
========= =========
- trunk
- Fixed DB-API_ violation: Use ``curs.execute('foo ?', (bar, baz))``
instead of ``curs.execute('foo ?', bar, baz)``.
- 0.1.2 - 0.1.2
- ``easy_install JayDeBeApi`` should really work - ``easy_install JayDeBeApi`` should really work
- 0.1.1 - 0.1.1
- Fixed bug #688290 "NULL values with converters error on fetch." - Fixed bug #688290 "NULL values with converters error on fetch."
- Fixed bug #684909 "Selecting ROWIDs errors out on fetch." - Fixed bug #684909 "Selecting ROWIDs errors out on fetch."
- 0.1 - 0.1
- Initial release - Initial release
To do To do
===== =====
- Extract Java calls to seperate Java methods to increase performance. - Extract Java calls to seperate Java methods to increase performance.
- Check if https://code.launchpad.net/dbapi-compliance can help making - Check if https://code.launchpad.net/dbapi-compliance can help making
JayDeBeApi more DB-API complient. JayDeBeApi more DB-API complient.
- Test it on different databases and provide a flexible db specific - Test it on different databases and provide a flexible db specific
pluign mechanism. pluign mechanism.
- SQLAlchemy modules (seperate project) - SQLAlchemy modules (seperate project)
.. _DB-API: http://www.python.org/dev/peps/pep-0249/ .. _DB-API: http://www.python.org/dev/peps/pep-0249/

View File

@ -233,14 +233,20 @@ class Cursor(object):
self._meta = None self._meta = None
self._description = None self._description = None
def _set_stmt_parms(self, prep_stmt, *parameters): # TODO: this is a possible way to close the open result sets
# but I'm not sure when __del__ will be called
#__del__ = _close_last
def _set_stmt_parms(self, prep_stmt, parameters):
for i in range(len(parameters)): for i in range(len(parameters)):
prep_stmt.setObject(i + 1, parameters[i]) prep_stmt.setObject(i + 1, parameters[i])
def execute(self, operation, *parameters): def execute(self, operation, parameters=None):
if not parameters:
parameters = ()
self._close_last() self._close_last()
self._prep = self._connection.jconn.prepareStatement(operation) self._prep = self._connection.jconn.prepareStatement(operation)
self._set_stmt_parms(self._prep, *parameters) self._set_stmt_parms(self._prep, parameters)
is_rs = self._prep.execute() is_rs = self._prep.execute()
self.update_count = self._prep.getUpdateCount() self.update_count = self._prep.getUpdateCount()
if is_rs: if is_rs:
@ -252,11 +258,12 @@ class Cursor(object):
self._close_last() self._close_last()
self._prep = self._connection.jconn.prepareStatement(operation) self._prep = self._connection.jconn.prepareStatement(operation)
for parameters in seq_of_parameters: for parameters in seq_of_parameters:
self._set_stmt_parms(self._prep, *parameters) self._set_stmt_parms(self._prep, parameters)
self._prep.addBatch() self._prep.addBatch()
update_counts = self._prep.executeBatch() update_counts = self._prep.executeBatch()
# self._prep.getWarnings() ??? # self._prep.getWarnings() ???
self.rowcount = sum(update_counts) self.rowcount = sum(update_counts)
self._close_last()
def fetchone(self): def fetchone(self):
#raise if not rs #raise if not rs
@ -276,6 +283,7 @@ class Cursor(object):
def fetchmany(self, size=None): def fetchmany(self, size=None):
if size is None: if size is None:
size = self.arraysize size = self.arraysize
# TODO: handle SQLException if not supported by db
self._rs.setFetchSize(size) self._rs.setFetchSize(size)
rows = [] rows = []
row = None row = None
@ -287,6 +295,7 @@ class Cursor(object):
rows.append(row) rows.append(row)
# reset fetch size # reset fetch size
if row: if row:
# TODO: handle SQLException if not supported by db
self._rs.setFetchSize(0) self._rs.setFetchSize(0)
return rows return rows
@ -311,18 +320,21 @@ class Cursor(object):
pass pass
def to_datetime(java_val): def to_datetime(java_val):
# d=datetime.datetime.strptime(timestmp.toString()[:-7], "%Y-%m-%d %H:%M:%S") #d=datetime.datetime.strptime(str(java_val)[:-7], "%Y-%m-%d %H:%M:%S")
# return d.replace(microsecond=int(str(timestmp.getNanos())[:6])) #return d.replace(microsecond=int(str(java_val.getNanos())[:6]))
return java_val.toString() return str(java_val)
def to_date(java_val): def to_date(java_val):
# d=datetime.datetime.strptime(timestmp.toString()[:-7], "%Y-%m-%d %H:%M:%S") #d=datetime.datetime.strptime(str(java_val)[:-7], "%Y-%m-%d %H:%M:%S")
# return d.replace(microsecond=int(str(timestmp.getNanos())[:6])) #return d.replace(microsecond=int(str(java_val.getNanos())[:6]))
return java_val.toString() return str(java_val)
def to_float(java_val): def to_float(java_val):
return java_val.doubleValue() return java_val.doubleValue()
def to_int(java_val):
return java_val.intValue()
def _init_converters(types_map): def _init_converters(types_map):
"""Prepares the converters for conversion of java types to python """Prepares the converters for conversion of java types to python
objects. objects.
@ -345,4 +357,5 @@ _DEFAULT_CONVERTERS = {
'DATE': to_date, 'DATE': to_date,
'DECIMAL': to_float, 'DECIMAL': to_float,
'NUMERIC': to_float, 'NUMERIC': to_float,
'INTEGER': to_int,
} }

View File

@ -17,19 +17,19 @@
# License along with JayDeBeApi. If not, see # License along with JayDeBeApi. If not, see
# <http://www.gnu.org/licenses/>. # <http://www.gnu.org/licenses/>.
import os from os import path
from unittest import TestCase from unittest import TestCase
import jaydebeapi import jaydebeapi
import sys import sys
this_dir = os.path.dirname(os.path.abspath(__file__)) this_dir = path.dirname(path.abspath(__file__))
jar_dir = os.path.abspath(os.path.join(this_dir, '..', '..', jar_dir = path.abspath(path.join(this_dir, '..', '..',
'build', 'lib')) 'build', 'lib'))
create_sql = os.path.join(this_dir, 'data', 'create.sql') create_sql = path.join(this_dir, 'data', 'create.sql')
insert_sql = os.path.join(this_dir, 'data', 'insert.sql') insert_sql = path.join(this_dir, 'data', 'insert.sql')
def is_jython(): def is_jython():
return sys.platform.lower().startswith('java') return sys.platform.lower().startswith('java')
class IntegrationTest(TestCase): class IntegrationTest(TestCase):
@ -50,31 +50,52 @@ class IntegrationTest(TestCase):
for i in stmts: for i in stmts:
cursor.execute(i) cursor.execute(i)
def setup_jpype(self, jars): def setup_jpype(self, jars, libs=None):
import jpype import jpype
if not jpype.isJVMStarted(): if not jpype.isJVMStarted():
jvm_path = jpype.getDefaultJVMPath() jvm_path = jpype.getDefaultJVMPath()
#jvm_path = ('/usr/lib/jvm/java-6-openjdk' #jvm_path = ('/usr/lib/jvm/java-6-openjdk'
# '/jre/lib/i386/client/libjvm.so') # '/jre/lib/i386/client/libjvm.so')
args='-Djava.class.path=%s' % jars # path to shared libraries
jpype.startJVM(jvm_path, args) args = []
if libs:
libs_path = path.pathsep.join(libs)
args.append('-Djava.library.path=%s' % libs_path)
class_path = path.pathsep.join(jars)
args.append('-Djava.class.path=%s' % class_path)
jpype.startJVM(jvm_path, *args)
if not jpype.isThreadAttachedToJVM(): if not jpype.isThreadAttachedToJVM():
jpype.attachThreadToJVM() jpype.attachThreadToJVM()
def setUp(self): def connect(self):
# TODO support more than one jar import sqlite3
#jars='/usr/share/java/hsqldb.jar' return sqlite3.connect(':memory:')
jars=os.path.join(jar_dir, 'hsqldb.jar')
#jars=jars_path(r'C:\Programme\DbVisualizer-4.3.5\jdbc') def connect_(self):
jar_names = [ 'hsqldb.jar', 'sqlitejdbc-v056.jar', 'sqlite.jar' ]
jars = [ path.join(jar_dir, i) for i in jar_names ]
if is_jython(): if is_jython():
sys.path.append(jars) sys.path.extend(jars)
else: else:
self.setup_jpype(jars) self.setup_jpype(jars, [jar_dir])
self.conn = jaydebeapi.connect('org.hsqldb.jdbcDriver', # http://www.zentus.com/sqlitejdbc/
'jdbc:hsqldb:mem', 'SA', '') conn = jaydebeapi.connect('org.sqlite.JDBC',
#conn = jaydebeapi.connect('com.ibm.db2.jcc.DB2Driver', 'jdbc:sqlite::memory:')
# 'jdbc:db2://4.100.73.81:50000/db2t', # http://hsqldb.org/
# getpass.getuser(), getpass.getpass()) # conn = jaydebeapi.connect('org.hsqldb.jdbcDriver',
# 'jdbc:hsqldb:mem', 'SA', '')
# conn = jaydebeapi.connect('com.ibm.db2.jcc.DB2Driver',
# 'jdbc:db2://4.100.73.81:50000/db2t',
# getpass.getuser(),
# getpass.getpass())
# driver from http://www.ch-werner.de/javasqlite/ seems to be
# crap as it returns decimal values as VARCHAR type
# conn = jaydebeapi.connect('SQLite.JDBCDriver',
# 'jdbc:sqlite:/:memory:')
return conn
def setUp(self):
self.conn = self.connect()
self.sql_file(create_sql) self.sql_file(create_sql)
self.sql_file(insert_sql) self.sql_file(insert_sql)
@ -98,7 +119,7 @@ class IntegrationTest(TestCase):
def test_execute_and_fetch_parameter(self): def test_execute_and_fetch_parameter(self):
cursor = self.conn.cursor() cursor = self.conn.cursor()
cursor.execute("select * from konto where konto_nr = ?", 18) cursor.execute("select * from konto where konto_nr = ?", (18,))
result = cursor.fetchall() result = cursor.fetchall()
assert [(u'2009-09-10 14:15:22.123456', 18, 12.4, None)] == result assert [(u'2009-09-10 14:15:22.123456', 18, 12.4, None)] == result
@ -107,6 +128,7 @@ class IntegrationTest(TestCase):
cursor.execute("select * from konto order by konto_nr") cursor.execute("select * from konto order by konto_nr")
result = cursor.fetchone() result = cursor.fetchone()
assert (u'2009-09-10 14:15:22.123456', 18, 12.4, None) == result assert (u'2009-09-10 14:15:22.123456', 18, 12.4, None) == result
cursor.close()
def test_execute_reset_description_without_execute_result(self): def test_execute_reset_description_without_execute_result(self):
"""Excpect the descriptions property being reset when no query """Excpect the descriptions property being reset when no query
@ -121,7 +143,7 @@ class IntegrationTest(TestCase):
def test_execute_and_fetchone_after_end(self): def test_execute_and_fetchone_after_end(self):
cursor = self.conn.cursor() cursor = self.conn.cursor()
cursor.execute("select * from konto where konto_nr = ?", 18) cursor.execute("select * from konto where konto_nr = ?", (18,))
cursor.fetchone() cursor.fetchone()
result = cursor.fetchone() result = cursor.fetchone()
assert None is result assert None is result
@ -131,6 +153,9 @@ class IntegrationTest(TestCase):
cursor.execute("select * from konto order by konto_nr") cursor.execute("select * from konto order by konto_nr")
result = cursor.fetchmany() result = cursor.fetchmany()
assert [(u'2009-09-10 14:15:22.123456', 18, 12.4, None)] == result assert [(u'2009-09-10 14:15:22.123456', 18, 12.4, None)] == result
# TODO: find out why this cursor has to be closed in order to
# let this test work with sqlite
# cursor.close()
def test_executemany(self): def test_executemany(self):
cursor = self.conn.cursor() cursor = self.conn.cursor()
@ -142,4 +167,3 @@ class IntegrationTest(TestCase):
) )
cursor.executemany(stmt, parms) cursor.executemany(stmt, parms)
assert cursor.rowcount == 3 assert cursor.rowcount == 3