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

@ -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
@ -96,6 +92,7 @@ Description: ===================================================================
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,6 +134,11 @@ 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

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,16 +17,16 @@
# 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')
@ -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:')
# http://hsqldb.org/
# 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', # 'jdbc:db2://4.100.73.81:50000/db2t',
# getpass.getuser(), getpass.getpass()) # 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