Improve robustness of java to python type conversion.

master
baztian 2015-02-08 15:43:27 +01:00
parent 3fef45e608
commit e03b657497
1 changed files with 83 additions and 48 deletions

View File

@ -1,6 +1,6 @@
#-*- coding: utf-8 -*-
# Copyright 2010, 2011, 2012, 2013 Bastian Bowe
# Copyright 2010-2015 Bastian Bowe
#
# This file is part of JayDeBeApi.
# JayDeBeApi is free software: you can redistribute it and/or modify
@ -29,6 +29,12 @@ import re
import sys
import warnings
# Mapping from java.sql.Types attribute name to attribute value
_jdbc_name_to_const = None
# Mapping from java.sql.Types attribute constant value to it's attribute name
_jdbc_const_to_name = None
_jdbc_connect = None
_java_array_byte = None
@ -43,7 +49,7 @@ def _handle_sql_exception_jython(ex):
raise ex
def _jdbc_connect_jython(jclassname, jars, libs, *args):
if _converters is None:
if _jdbc_name_to_const is None:
from java.sql import Types
types = Types
types_map = {}
@ -51,7 +57,7 @@ def _jdbc_connect_jython(jclassname, jars, libs, *args):
for i in dir(types):
if const_re.match(i):
types_map[i] = getattr(types, i)
_init_converters(types_map)
_init_types(types_map)
global _java_array_byte
if _java_array_byte is None:
import jarray
@ -125,12 +131,12 @@ def _jdbc_connect_jpype(jclassname, jars, libs, *driver_args):
jpype.startJVM(jvm_path, *args)
if not jpype.isThreadAttachedToJVM():
jpype.attachThreadToJVM()
if _converters is None:
if _jdbc_name_to_const is None:
types = jpype.java.sql.Types
types_map = {}
for i in types.__javaclass__.getClassFields():
types_map[i.getName()] = i.getStaticAttribute()
_init_converters(types_map)
_init_types(types_map)
global _java_array_byte
if _java_array_byte is None:
def _java_array_byte(data):
@ -175,50 +181,58 @@ paramstyle = 'qmark'
class DBAPITypeObject(object):
_mappings = {}
def __init__(self,*values):
def __init__(self, *values):
"""Construct new DB-API 2.0 type object.
values: Attribute names of java.sql.Types constants"""
self.values = values
for i in values:
if i in DBAPITypeObject._mappings:
raise ValueError, "Non unique mapping for type '%s'" % i
DBAPITypeObject._mappings[i] = self
def __cmp__(self,other):
for type_name in values:
if type_name in DBAPITypeObject._mappings:
raise ValueError, "Non unique mapping for type '%s'" % type_name
DBAPITypeObject._mappings[type_name] = self
def __cmp__(self, other):
if other in self.values:
return 0
if other < self.values:
return 1
else:
return -1
def __repr__(self):
return 'DBAPITypeObject(%s)' % ", ".join([repr(i) for i in self.values])
@classmethod
def _map_jdbc_type_to_dbapi(cls, jdbc_type):
def _map_jdbc_type_to_dbapi(cls, jdbc_type_const):
try:
return cls._mappings[jdbc_type.upper()]
type_name = _jdbc_const_to_name[jdbc_type_const]
except KeyError:
warnings.warn("No type mapping for JDBC type '%s'. "
"Using None as a default." % jdbc_type)
warnings.warn("Unknown JDBC type with constant value %d. "
"Using None as a default type_code." % jdbc_type_const)
return None
try:
return cls._mappings[type_name]
except KeyError:
warnings.warn("No type mapping for JDBC type '%s' (constant value %d). "
"Using None as a default type_code." % (type_name, jdbc_type_const))
return None
STRING = DBAPITypeObject("CHARACTER", "CHAR", "VARCHAR",
"CHARACTER VARYING", "CHAR VARYING", "STRING",)
STRING = DBAPITypeObject('CHAR', 'NCHAR', 'NVARCHAR', 'VARCHAR', 'OTHER')
TEXT = DBAPITypeObject("CLOB", "CHARACTER LARGE OBJECT",
"CHAR LARGE OBJECT", "XML",)
TEXT = DBAPITypeObject('CLOB', 'LONGNVARCHAR', 'LONGNARCHAR', 'NCLOB', 'SQLXML')
BINARY = DBAPITypeObject("BLOB", "BINARY LARGE OBJECT",)
BINARY = DBAPITypeObject('BINARY', 'BLOB', 'LONGVARBINARY', 'VARBINARY')
NUMBER = DBAPITypeObject("INTEGER", "INT", "SMALLINT", "BIGINT",)
NUMBER = DBAPITypeObject('BOOLEAN', 'BIGINT', 'INTEGER', 'SMALLINT')
FLOAT = DBAPITypeObject("FLOAT", "REAL", "DOUBLE", "DECFLOAT")
FLOAT = DBAPITypeObject('FLOAT', 'REAL', 'DOUBLE')
DECIMAL = DBAPITypeObject("DECIMAL", "DEC", "NUMERIC", "NUM",)
DECIMAL = DBAPITypeObject('DECIMAL', 'NUMERIC')
DATE = DBAPITypeObject("DATE",)
DATE = DBAPITypeObject('DATE')
TIME = DBAPITypeObject("TIME",)
TIME = DBAPITypeObject('TIME')
DATETIME = DBAPITypeObject("TIMESTAMP",)
DATETIME = DBAPITypeObject('TIMESTAMP')
ROWID = DBAPITypeObject(())
ROWID = DBAPITypeObject('ROWID')
# DB-API 2.0 Module Interface Exceptions
class Error(exceptions.StandardError):
@ -371,7 +385,12 @@ class Cursor(object):
self._description = []
for col in range(1, count + 1):
size = m.getColumnDisplaySize(col)
jdbc_type = m.getColumnTypeName(col)
jdbc_type = m.getColumnType(col)
if jdbc_type == 0:
# PEP-0249: SQL NULL values are represented by the
# Python None singleton
dbapi_type = None
else:
dbapi_type = DBAPITypeObject._map_jdbc_type_to_dbapi(jdbc_type)
col_desc = ( m.getColumnName(col),
dbapi_type,
@ -450,14 +469,8 @@ class Cursor(object):
row = []
for col in range(1, self._meta.getColumnCount() + 1):
sqltype = self._meta.getColumnType(col)
# print sqltype
# TODO: Oracle 11 will read a oracle.sql.TIMESTAMP
# which can't be converted to string easily
v = self._rs.getObject(col)
if v:
converter = self._converters.get(sqltype)
if converter:
v = converter(v)
converter = self._converters.get(sqltype, _unknownSqlTypeConverter)
v = converter(self._rs, col)
row.append(v)
return tuple(row)
@ -502,20 +515,35 @@ class Cursor(object):
def setoutputsize(self, size, column=None):
pass
def _to_datetime(java_val):
def _unknownSqlTypeConverter(rs, col):
return rs.getObject(col)
def _to_datetime(rs, col):
java_val = rs.getTimestamp(col)
if not java_val:
return
d = datetime.datetime.strptime(str(java_val)[:19], "%Y-%m-%d %H:%M:%S")
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):
def _to_date(rs, col):
java_val = rs.getDate(col)
if not java_val:
return
d = datetime.datetime.strptime(str(java_val)[:10], "%Y-%m-%d")
return d.strftime("%Y-%m-%d")
# return str(java_val)
def _to_binary(rs, col):
java_val = rs.getObject(col)
if java_val is None:
return
return str(java_val)
def _java_to_py(java_method):
def to_py(java_val):
def to_py(rs, col):
java_val = rs.getObject(col)
if java_val is None:
return
if isinstance(java_val, (basestring, int, long, float, bool)):
return java_val
return getattr(java_val, java_method)()
@ -525,6 +553,13 @@ _to_double = _java_to_py('doubleValue')
_to_int = _java_to_py('intValue')
def _init_types(types_map):
global _jdbc_name_to_const
_jdbc_name_to_const = types_map
global _jdbc_const_to_name
_jdbc_const_to_name = dict((y,x) for x,y in types_map.iteritems())
_init_converters(types_map)
def _init_converters(types_map):
"""Prepares the converters for conversion of java types to python
objects.
@ -541,11 +576,11 @@ _converters = None
_DEFAULT_CONVERTERS = {
# see
# http://download.oracle.com/javase/1.4.2/docs/api/java/sql/Types.html
# http://download.oracle.com/javase/6/docs/api/java/sql/Types.html
# for possible keys
'TIMESTAMP': _to_datetime,
'DATE': _to_date,
'BINARY': str,
'BINARY': _to_binary,
'DECIMAL': _to_double,
'NUMERIC': _to_double,
'DOUBLE': _to_double,