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
=====================================================================
=================================================================
The JayDeBeApi module allows you to connect from Python code to
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
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
access a database with Jython AND Python with only minor code
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
>>> import jpype
>>> jar = '/path/to/my/driver/hsqldb.jar'
>>> args='-Djava.class.path=%s' % jar
>>> jpype.startJVM(jvm_path, args)
@ -98,6 +95,19 @@ or in Jython I have to
>>> import sys
>>> 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
============
@ -116,18 +126,27 @@ distribution for details.
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
- ``easy_install JayDeBeApi`` should really work
- ``easy_install JayDeBeApi`` should really work.
- 0.1.1
- Fixed bug #688290 "NULL values with converters error on fetch."
- Fixed bug #684909 "Selecting ROWIDs errors out on fetch."
- Fixed bug #688290 "NULL values with converters error on fetch".
- Fixed bug #684909 "Selecting ROWIDs errors out on fetch".
- 0.1
- Initial release
- Initial release.
To do
=====

View File

@ -19,10 +19,6 @@ Description: ===================================================================
integration or on `Jython <http://www.jython.org/>`_ to make use of
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
access a database with Jython AND Python with only minor code
modifications. JayDeBeApi's future goal is to provide a unique and
@ -96,6 +92,7 @@ Description: ===================================================================
Now I have to configure JPype
>>> import jpype
>>> jar = '/path/to/my/driver/hsqldb.jar'
>>> args='-Djava.class.path=%s' % jar
>>> jpype.startJVM(jvm_path, args)
@ -106,6 +103,19 @@ Description: ===================================================================
>>> import sys
>>> 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
============
@ -124,6 +134,11 @@ Description: ===================================================================
Changelog
=========
- trunk
- Fixed DB-API_ violation: Use ``curs.execute('foo ?', (bar, baz))``
instead of ``curs.execute('foo ?', bar, baz)``.
- 0.1.2
- ``easy_install JayDeBeApi`` should really work

View File

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

View File

@ -17,16 +17,16 @@
# License along with JayDeBeApi. If not, see
# <http://www.gnu.org/licenses/>.
import os
from os import path
from unittest import TestCase
import jaydebeapi
import sys
this_dir = os.path.dirname(os.path.abspath(__file__))
jar_dir = os.path.abspath(os.path.join(this_dir, '..', '..',
this_dir = path.dirname(path.abspath(__file__))
jar_dir = path.abspath(path.join(this_dir, '..', '..',
'build', 'lib'))
create_sql = os.path.join(this_dir, 'data', 'create.sql')
insert_sql = os.path.join(this_dir, 'data', 'insert.sql')
create_sql = path.join(this_dir, 'data', 'create.sql')
insert_sql = path.join(this_dir, 'data', 'insert.sql')
def is_jython():
return sys.platform.lower().startswith('java')
@ -50,31 +50,52 @@ class IntegrationTest(TestCase):
for i in stmts:
cursor.execute(i)
def setup_jpype(self, jars):
def setup_jpype(self, jars, libs=None):
import jpype
if not jpype.isJVMStarted():
jvm_path = jpype.getDefaultJVMPath()
#jvm_path = ('/usr/lib/jvm/java-6-openjdk'
# '/jre/lib/i386/client/libjvm.so')
args='-Djava.class.path=%s' % jars
jpype.startJVM(jvm_path, args)
# path to shared libraries
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():
jpype.attachThreadToJVM()
def setUp(self):
# TODO support more than one jar
#jars='/usr/share/java/hsqldb.jar'
jars=os.path.join(jar_dir, 'hsqldb.jar')
#jars=jars_path(r'C:\Programme\DbVisualizer-4.3.5\jdbc')
def connect(self):
import sqlite3
return sqlite3.connect(':memory:')
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():
sys.path.append(jars)
sys.path.extend(jars)
else:
self.setup_jpype(jars)
self.conn = jaydebeapi.connect('org.hsqldb.jdbcDriver',
'jdbc:hsqldb:mem', 'SA', '')
#conn = jaydebeapi.connect('com.ibm.db2.jcc.DB2Driver',
self.setup_jpype(jars, [jar_dir])
# http://www.zentus.com/sqlitejdbc/
conn = jaydebeapi.connect('org.sqlite.JDBC',
'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',
# 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(insert_sql)
@ -98,7 +119,7 @@ class IntegrationTest(TestCase):
def test_execute_and_fetch_parameter(self):
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()
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")
result = cursor.fetchone()
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):
"""Excpect the descriptions property being reset when no query
@ -121,7 +143,7 @@ class IntegrationTest(TestCase):
def test_execute_and_fetchone_after_end(self):
cursor = self.conn.cursor()
cursor.execute("select * from konto where konto_nr = ?", 18)
cursor.execute("select * from konto where konto_nr = ?", (18,))
cursor.fetchone()
result = cursor.fetchone()
assert None is result
@ -131,6 +153,9 @@ class IntegrationTest(TestCase):
cursor.execute("select * from konto order by konto_nr")
result = cursor.fetchmany()
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):
cursor = self.conn.cursor()
@ -142,4 +167,3 @@ class IntegrationTest(TestCase):
)
cursor.executemany(stmt, parms)
assert cursor.rowcount == 3