Further type handling improvements. BLOB support. Free resources on cursor destruction.
parent
525c3fa192
commit
2695c9fe42
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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'),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue