Further type handling improvements. BLOB support. Free resources on cursor destruction.

master
baztian 2011-01-27 11:53:14 +01:00
parent 525c3fa192
commit 2695c9fe42
6 changed files with 130 additions and 41 deletions

View File

@ -133,7 +133,7 @@ Changelog
- Free resources after ``executemany`` call. - Free resources after ``executemany`` call.
- Improved type handling. - Improved type handling. Initial support for BLOB columns.
- 0.1.2 - 0.1.2

View File

@ -17,7 +17,8 @@ Build a new release
3. Assert the right connect method is configured for tests. 3. Assert the right connect method is configured for tests.
4. Run test suite. :: 4. Run test suite. Once for cPython, once for Jython and ideally
against all accessible databases. ::
$ nosetests $ nosetests

View File

@ -25,6 +25,8 @@ import sys
_jdbc_connect = None _jdbc_connect = None
_java_array_byte = None
def _jdbc_connect_jython(jclassname, *args): def _jdbc_connect_jython(jclassname, *args):
if _converters is None: if _converters is None:
from java.sql import Types from java.sql import Types
@ -37,14 +39,17 @@ def _jdbc_connect_jython(jclassname, *args):
_init_converters(types_map) _init_converters(types_map)
# register driver for DriverManager # register driver for DriverManager
__import__(jclassname) __import__(jclassname)
global _java_array_byte
if _java_array_byte is None:
import jarray
def _java_array_byte(data):
return jarray.array(data, 'b')
from java.sql import DriverManager from java.sql import DriverManager
return DriverManager.getConnection(*args) return DriverManager.getConnection(*args)
def _prepare_jython(): def _prepare_jython():
global _jdbc_connect global _jdbc_connect
_jdbc_connect = _jdbc_connect_jython _jdbc_connect = _jdbc_connect_jython
# TODO: find solution for jython
# Binary = buffer
def _jdbc_connect_jpype(jclassname, *args): def _jdbc_connect_jpype(jclassname, *args):
import jpype import jpype
@ -58,6 +63,10 @@ def _jdbc_connect_jpype(jclassname, *args):
for i in types.__javaclass__.getClassFields(): for i in types.__javaclass__.getClassFields():
types_map[i.getName()] = i.getStaticAttribute() types_map[i.getName()] = i.getStaticAttribute()
_init_converters(types_map) _init_converters(types_map)
global _java_array_byte
if _java_array_byte is None:
def _java_array_byte(data):
return jpype.JArray(jpype.JByte, 1)(data)
# register driver for DriverManager # register driver for DriverManager
jpype.JClass(jclassname) jpype.JClass(jclassname)
return jpype.java.sql.DriverManager.getConnection(*args) return jpype.java.sql.DriverManager.getConnection(*args)
@ -65,9 +74,6 @@ def _jdbc_connect_jpype(jclassname, *args):
def _prepare_jpype(): def _prepare_jpype():
global _jdbc_connect global _jdbc_connect
_jdbc_connect = _jdbc_connect_jpype _jdbc_connect = _jdbc_connect_jpype
# TODO: doesn't work for Jython
# global Binary
# Binary = buffer
if sys.platform.lower().startswith('java'): if sys.platform.lower().startswith('java'):
_prepare_jython() _prepare_jython()
@ -143,11 +149,22 @@ class NotSupportedError(DatabaseError):
pass pass
# DB-API 2.0 Type Objects and Constructors # DB-API 2.0 Type Objects and Constructors
Date = datetime.date
Time = datetime.time def _java_sql_blob(data):
return _java_array_byte(data)
Timestamp = datetime.datetime Binary = _java_sql_blob
def _str_func(func):
def to_str(*parms):
return str(func(*parms))
return to_str
Date = _str_func(datetime.date)
Time = _str_func(datetime.time)
Timestamp = _str_func(datetime.datetime)
def DateFromTicks(ticks): def DateFromTicks(ticks):
return apply(Date, time.localtime(ticks)[:3]) return apply(Date, time.localtime(ticks)[:3])
@ -235,10 +252,11 @@ class Cursor(object):
# TODO: this is a possible way to close the open result sets # TODO: this is a possible way to close the open result sets
# but I'm not sure when __del__ will be called # but I'm not sure when __del__ will be called
#__del__ = _close_last __del__ = _close_last
def _set_stmt_parms(self, prep_stmt, parameters): def _set_stmt_parms(self, prep_stmt, parameters):
for i in range(len(parameters)): for i in range(len(parameters)):
# print (i, parameters[i], type(parameters[i]))
prep_stmt.setObject(i + 1, parameters[i]) prep_stmt.setObject(i + 1, parameters[i])
def execute(self, operation, parameters=None): def execute(self, operation, parameters=None):
@ -272,6 +290,7 @@ class Cursor(object):
row = [] row = []
for col in range(1, self._meta.getColumnCount() + 1): for col in range(1, self._meta.getColumnCount() + 1):
sqltype = self._meta.getColumnType(col) sqltype = self._meta.getColumnType(col)
# print sqltype
v = self._rs.getObject(col) v = self._rs.getObject(col)
if v: if v:
converter = _converters.get(sqltype) converter = _converters.get(sqltype)
@ -319,21 +338,31 @@ class Cursor(object):
def setoutputsize(self, size, column): def setoutputsize(self, size, column):
pass pass
def to_datetime(java_val): def _to_datetime(java_val):
#d=datetime.datetime.strptime(str(java_val)[:-7], "%Y-%m-%d %H:%M:%S") d = datetime.datetime.strptime(str(java_val)[:19], "%Y-%m-%d %H:%M:%S")
#return d.replace(microsecond=int(str(java_val.getNanos())[:6])) if not isinstance(java_val, basestring):
d = d.replace(microsecond=int(str(java_val.getNanos())[:6]))
return str(d)
# return str(java_val)
def _to_date(java_val):
d = datetime.datetime.strptime(str(java_val)[:10], "%Y-%m-%d")
return d.strftime("%Y-%m-%d")
# return str(java_val)
def _to_string(java_val):
return str(java_val) return str(java_val)
def to_date(java_val): def _java_to_py(java_method):
#d=datetime.datetime.strptime(str(java_val)[:-7], "%Y-%m-%d %H:%M:%S") def to_py(java_val):
#return d.replace(microsecond=int(str(java_val.getNanos())[:6])) if isinstance(java_val, (basestring, int, long, float, bool)):
return str(java_val) return java_val
return getattr(java_val, java_method)()
return to_py
def to_float(java_val): _to_double = _java_to_py('doubleValue')
return java_val.doubleValue()
def to_int(java_val): _to_int = _java_to_py('intValue')
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
@ -353,9 +382,14 @@ _DEFAULT_CONVERTERS = {
# see # see
# http://download.oracle.com/javase/1.4.2/docs/api/java/sql/Types.html # http://download.oracle.com/javase/1.4.2/docs/api/java/sql/Types.html
# for possible keys # for possible keys
'TIMESTAMP': to_datetime, 'TIMESTAMP': _to_datetime,
'DATE': to_date, 'DATE': _to_date,
'DECIMAL': to_float, 'BINARY': _to_string,
'NUMERIC': to_float, 'DECIMAL': _to_double,
'INTEGER': to_int, 'NUMERIC': _to_double,
'DOUBLE': _to_double,
'FLOAT': _to_double,
'INTEGER': _to_int,
'SMALLINT': _to_int,
'BOOLEAN': _java_to_py('booleanValue'),
} }

View File

@ -3,6 +3,11 @@ create table Account (
"ACCOUNT_NO" INTEGER not null, "ACCOUNT_NO" INTEGER not null,
"BALANCE" DECIMAL default 0.0 not null, "BALANCE" DECIMAL default 0.0 not null,
"BLOCKING" DECIMAL, "BLOCKING" DECIMAL,
"DBL_COL" DOUBLE,
"OPENED_AT" DATE,
"VALID" BOOLEAN,
"PRODUCT_NAME" VARCHAR,
"STUFF" BLOB,
primary key ("ACCOUNT_ID") primary key ("ACCOUNT_ID")
); );

View File

@ -1,2 +1,2 @@
insert into ACCOUNT values ('2009-09-10 14:15:22.123456', 18, 12.4, null); insert into ACCOUNT (ACCOUNT_ID, ACCOUNT_NO, BALANCE, BLOCKING) values ('2009-09-10 14:15:22.123456', 18, 12.4, null);
insert into ACCOUNT values ('2009-09-11 14:15:22.123456', 19, 12.9, 1); insert into ACCOUNT (ACCOUNT_ID, ACCOUNT_NO, BALANCE, BLOCKING) values ('2009-09-11 14:15:22.123456', 19, 12.9, 1);

View File

@ -73,13 +73,14 @@ class IntegrationTest(TestCase):
msg = "Warinng: Your are not running the tests against JayDeBeApi." msg = "Warinng: Your are not running the tests against JayDeBeApi."
print >> sys.stderr, msg print >> sys.stderr, msg
import sqlite3 import sqlite3
return sqlite3.connect(':memory:') return sqlite3, sqlite3.connect(':memory:')
def connect(self): def connect(self):
jar_names = [ 'sqlitejdbc-v056.jar', 'hsqldb.jar', 'sqlite.jar' ] jar_names = [ 'sqlitejdbc-v056.jar', 'hsqldb.jar', 'sqlite.jar' ]
jars = [ path.join(jar_dir, i) for i in jar_names ] jars = [ path.join(jar_dir, i) for i in jar_names ]
if is_jython(): if is_jython():
sys.path.extend(jars) sys.path.extend(jars)
# print "CLASSPATH=%s" % path.pathsep.join(jars)
else: else:
self.setup_jpype(jars, [jar_dir]) self.setup_jpype(jars, [jar_dir])
# http://www.zentus.com/sqlitejdbc/ # http://www.zentus.com/sqlitejdbc/
@ -96,10 +97,10 @@ class IntegrationTest(TestCase):
# crap as it returns decimal values as VARCHAR type # crap as it returns decimal values as VARCHAR type
# conn = jaydebeapi.connect('SQLite.JDBCDriver', # conn = jaydebeapi.connect('SQLite.JDBCDriver',
# 'jdbc:sqlite:/:memory:') # 'jdbc:sqlite:/:memory:')
return conn return jaydebeapi, conn
def setUp(self): def setUp(self):
self.conn = self.connect() (self.dbapi, self.conn) = self.connect()
self.sql_file(create_sql) self.sql_file(create_sql)
self.sql_file(insert_sql) self.sql_file(insert_sql)
@ -116,20 +117,23 @@ class IntegrationTest(TestCase):
def test_execute_and_fetch(self): def test_execute_and_fetch(self):
cursor = self.conn.cursor() cursor = self.conn.cursor()
cursor.execute("select * from ACCOUNT") cursor.execute("select ACCOUNT_ID, ACCOUNT_NO, BALANCE, BLOCKING " \
"from ACCOUNT")
result = cursor.fetchall() result = cursor.fetchall()
assert [(u'2009-09-10 14:15:22.123456', 18, 12.4, None), assert [(u'2009-09-10 14:15:22.123456', 18, 12.4, None),
(u'2009-09-11 14:15:22.123456', 19, 12.9, 1)] == result (u'2009-09-11 14:15:22.123456', 19, 12.9, 1)] == result
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 ACCOUNT where ACCOUNT_NO = ?", (18,)) cursor.execute("select ACCOUNT_ID, ACCOUNT_NO, BALANCE, BLOCKING " \
"from ACCOUNT where ACCOUNT_NO = ?", (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
def test_execute_and_fetchone(self): def test_execute_and_fetchone(self):
cursor = self.conn.cursor() cursor = self.conn.cursor()
cursor.execute("select * from ACCOUNT order by ACCOUNT_NO") cursor.execute("select ACCOUNT_ID, ACCOUNT_NO, BALANCE, BLOCKING " \
"from ACCOUNT order by ACCOUNT_NO")
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() cursor.close()
@ -154,17 +158,19 @@ class IntegrationTest(TestCase):
def test_execute_and_fetchmany(self): def test_execute_and_fetchmany(self):
cursor = self.conn.cursor() cursor = self.conn.cursor()
cursor.execute("select * from ACCOUNT order by ACCOUNT_NO") cursor.execute("select ACCOUNT_ID, ACCOUNT_NO, BALANCE, BLOCKING " \
"from ACCOUNT order by ACCOUNT_NO")
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 # TODO: find out why this cursor has to be closed in order to
# let this test work with sqlite # let this test work with sqlite if __del__ is not overridden
# cursor.close() # in cursor
# cursor.close()
def test_executemany(self): def test_executemany(self):
cursor = self.conn.cursor() cursor = self.conn.cursor()
stmt = "insert into ACCOUNT (ACCOUNT_ID, ACCOUNT_NO, BALANCE)" \ stmt = "insert into ACCOUNT (ACCOUNT_ID, ACCOUNT_NO, BALANCE) " \
" values (?, ?, ?)" "values (?, ?, ?)"
parms = ( parms = (
( '2009-09-11 14:15:22.123450', 20, 13.1 ), ( '2009-09-11 14:15:22.123450', 20, 13.1 ),
( '2009-09-11 14:15:22.123451', 21, 13.2 ), ( '2009-09-11 14:15:22.123451', 21, 13.2 ),
@ -172,3 +178,46 @@ class IntegrationTest(TestCase):
) )
cursor.executemany(stmt, parms) cursor.executemany(stmt, parms)
assert cursor.rowcount == 3 assert cursor.rowcount == 3
def test_execute_types(self):
cursor = self.conn.cursor()
stmt = "insert into ACCOUNT (ACCOUNT_ID, ACCOUNT_NO, BALANCE, " \
"BLOCKING, DBL_COL, OPENED_AT, VALID, PRODUCT_NAME) " \
"values (?, ?, ?, ?, ?, ?, ?, ?)"
d = self.dbapi
account_id = d.Timestamp(2010, 01, 26, 14, 31, 59)
account_no = 20
balance = 1.2
blocking = 10.0
dbl_col = 3.5
opened_at = d.Date(2008, 02, 27)
valid = 1
product_name = u'Savings account'
parms = (account_id, account_no, balance, blocking, dbl_col,
opened_at, valid, product_name)
cursor.execute(stmt, parms)
stmt = "select ACCOUNT_ID, ACCOUNT_NO, BALANCE, BLOCKING, " \
"DBL_COL, OPENED_AT, VALID, PRODUCT_NAME " \
"from ACCOUNT where ACCOUNT_NO = ?"
parms = (20, )
cursor.execute(stmt, parms)
result = cursor.fetchone()
cursor.close()
exp = ( '2010-01-26 14:31:59', account_no, balance, blocking,
dbl_col, '2008-02-27', valid, product_name )
assert exp == result
def test_execute_type_blob(self):
cursor = self.conn.cursor()
stmt = "insert into ACCOUNT (ACCOUNT_ID, ACCOUNT_NO, BALANCE, " \
"STUFF) values (?, ?, ?, ?)"
stuff = self.dbapi.Binary('abcdef')
parms = ('2009-09-11 14:15:22.123450', 20, 13.1, stuff)
cursor.execute(stmt, parms)
stmt = "select STUFF from ACCOUNT where ACCOUNT_NO = ?"
parms = (20, )
cursor.execute(stmt, parms)
result = cursor.fetchone()
cursor.close()
value = result[0]
assert 'abcdef' == value