Fixed DB-API violation of execute-Method. Free resources after executemany. Tests against SQLite. Improved type handling.
parent
cd8b93ca27
commit
4884cc60d1
39
README.rst
39
README.rst
|
|
@ -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
|
||||
=====
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue