diff --git a/README.rst b/README.rst index 4c54fb4..52e8f29 100644 --- a/README.rst +++ b/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 `_ to make use of the Java JDBC driver. -It has been tested with `Hypersonic SQL (HSQLDB) -`_ and `IBM 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 `_ using `SqliteJDBC + `_ v056 +* `Hypersonic SQL (HSQLDB) `_ 1.8.1.3 +* `IBM 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 ===== diff --git a/src/JayDeBeApi.egg-info/PKG-INFO b/src/JayDeBeApi.egg-info/PKG-INFO index d317f46..4c151f7 100644 --- a/src/JayDeBeApi.egg-info/PKG-INFO +++ b/src/JayDeBeApi.egg-info/PKG-INFO @@ -7,7 +7,7 @@ Author: Bastian Bowe Author-email: bastian.bowe@gmail.com License: GNU LGPL 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 @@ -19,10 +19,6 @@ Description: =================================================================== integration or on `Jython `_ to make use of the Java JDBC driver. - It has been tested with `Hypersonic SQL (HSQLDB) - `_ and `IBM 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 @@ -37,7 +33,7 @@ Description: =================================================================== You can get and install JayDeBeApi with `easy_install `_ :: - $ easy_install JayDeBeApi + $ easy_install JayDeBeApi If you want to install JayDeBeApi in Jython make sure to have EasyInstall available for it. @@ -45,15 +41,15 @@ Description: =================================================================== Or you can get a copy of the source branch using `bzr `_ by running :: - $ bzr branch lp:jaydebeapi + $ bzr branch lp:jaydebeapi and install it with :: - $ python setup.py install + $ python setup.py install or if you are using Jython use :: - $ jython setup.py install + $ jython setup.py install 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 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 + >>> 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 `_ using `SqliteJDBC + `_ v056 + * `Hypersonic SQL (HSQLDB) `_ 1.8.1.3 + * `IBM DB2 `_ for z/OS using + JDBC type 4 drivers. + + Contributing ============ @@ -124,27 +134,32 @@ 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 + - ``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 ===== - Extract Java calls to seperate Java methods to increase performance. - 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 - pluign mechanism. + pluign mechanism. - SQLAlchemy modules (seperate project) .. _DB-API: http://www.python.org/dev/peps/pep-0249/ diff --git a/src/jaydebeapi/dbapi2.py b/src/jaydebeapi/dbapi2.py index 93ba276..d328318 100644 --- a/src/jaydebeapi/dbapi2.py +++ b/src/jaydebeapi/dbapi2.py @@ -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, } diff --git a/src/test/integration_test.py b/src/test/integration_test.py index 4dbf101..96b943a 100644 --- a/src/test/integration_test.py +++ b/src/test/integration_test.py @@ -17,19 +17,19 @@ # License along with JayDeBeApi. If not, see # . -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') + return sys.platform.lower().startswith('java') class IntegrationTest(TestCase): @@ -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', - # 'jdbc:db2://4.100.73.81:50000/db2t', - # getpass.getuser(), getpass.getpass()) + 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()) + # 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 -