From: <le...@us...> - 2008-07-15 21:21:39
|
Revision: 4947 http://jython.svn.sourceforge.net/jython/?rev=4947&view=rev Author: leosoto Date: 2008-07-15 14:21:30 -0700 (Tue, 15 Jul 2008) Log Message: ----------- Improvements to the zxJDBC default DataHandler: - Map DATE, TIME and TIMESTAMP columns to datetime.date, datetime.time and datetime.datetime respectively. Factory methods to create instances of datetime.* were added to Py.java. - Map VARCHAR columns to unicode instead of str. - Don't try to guess the precision of NUMERIC columns . - Use Py.True and Py.False instead of Py.One and Py.Zero. Some of this changes may cause backwards-incompatibility, so I've copied the old DataHandler logic into Jython22DataHandler. Then, old code should simply setup this datahandler on its connections if it is not going to be adapted to the new DataHandler default behaviour. Modified Paths: -------------- branches/asm/Lib/test/test_jy_internals.py branches/asm/src/com/ziclix/python/sql/DataHandler.java branches/asm/src/org/python/core/Py.java Added Paths: ----------- branches/asm/src/com/ziclix/python/sql/Jython22DataHandler.java Modified: branches/asm/Lib/test/test_jy_internals.py =================================================================== --- branches/asm/Lib/test/test_jy_internals.py 2008-07-15 21:05:06 UTC (rev 4946) +++ branches/asm/Lib/test/test_jy_internals.py 2008-07-15 21:21:30 UTC (rev 4947) @@ -3,11 +3,16 @@ """ import unittest import time -from test_support import run_suite +from test.test_support import run_suite import java import jarray +from org.python.core import Py +from java.sql import Date, Time, Timestamp +import datetime + + class WeakIdentityMapTests(unittest.TestCase): def test_functionality(self): @@ -44,7 +49,7 @@ time.sleep(1) assert widmap.get(i) == 'i' # triggers stale weak refs cleanup - assert widmap._internal_map_size() == 1 + assert widmap._internal_map_size() == 1 class LongAsScaledDoubleValueTests(unittest.TestCase): @@ -94,7 +99,7 @@ for d in range(8): for y in [0,255]: assert float((v+d)*256+y) == sdv(((v+d)*256+y)*256, e) - assert e[0] == 1 + assert e[0] == 1 class ExtraMathTests(unittest.TestCase): def test_epsilon(self): @@ -115,6 +120,28 @@ ExtraMath.closeFloor(3.0 - 3.0 * ExtraMath.CLOSE), 2.0) self.assertEquals(ExtraMath.closeFloor(math.log10(10**3)), 3.0) +class DatetimeTypeMappingTest(unittest.TestCase): + def test_date(self): + self.assertEquals(datetime.date(2008, 5, 29), + Py.newDate(Date(108, 4, 29))) + self.assertEquals(datetime.date(1999, 1, 1), + Py.newDate(Date(99, 0, 1))) + + def test_time(self): + self.assertEquals(datetime.time(0, 0, 0), + Py.newTime(Time(0, 0, 0))) + self.assertEquals(datetime.time(23, 59, 59), + Py.newTime(Time(23, 59, 59))) + + def test_datetime(self): + self.assertEquals(datetime.datetime(2008, 1, 1), + Py.newDatetime(Timestamp(108, 0, 1, 0, 0, 0, 0))) + self.assertEquals(datetime.datetime(2008, 5, 29, 16, 50, 0), + Py.newDatetime(Timestamp(108, 4, 29, 16, 50, 0, 0))) + self.assertEquals(datetime.datetime(2008, 5, 29, 16, 50, 1, 1), + Py.newDatetime(Timestamp(108, 4, 29, 16, 50, 1, 1000))) + + def test_main(): test_suite = unittest.TestSuite() test_loader = unittest.TestLoader() @@ -123,6 +150,7 @@ suite_add(WeakIdentityMapTests) suite_add(LongAsScaledDoubleValueTests) suite_add(ExtraMathTests) + suite_add(DatetimeTypeMappingTest) run_suite(test_suite) if __name__ == "__main__": Modified: branches/asm/src/com/ziclix/python/sql/DataHandler.java =================================================================== --- branches/asm/src/com/ziclix/python/sql/DataHandler.java 2008-07-15 21:05:06 UTC (rev 4946) +++ branches/asm/src/com/ziclix/python/sql/DataHandler.java 2008-07-15 21:21:30 UTC (rev 4947) @@ -10,7 +10,6 @@ import org.python.core.Py; import org.python.core.PyFile; -import org.python.core.PyLong; import org.python.core.PyObject; import org.python.core.PyList; import org.python.core.PyString; @@ -151,7 +150,6 @@ * @throws SQLException */ public void setJDBCObject(PreparedStatement stmt, int index, PyObject object, int type) throws SQLException { - try { if (checkNull(stmt, index, object, type)) { return; @@ -236,7 +234,7 @@ case Types.VARCHAR: String string = set.getString(col); - obj = (string == null) ? Py.None : Py.newString(string); + obj = (string == null) ? Py.None : Py.newUnicode(string); break; case Types.LONGVARCHAR: @@ -251,7 +249,7 @@ byte[] bytes = DataHandler.read(longvarchar); if (bytes != null) { - obj = Py.newString(StringUtil.fromBytes(bytes)); + obj = Py.newUnicode(StringUtil.fromBytes(bytes)); } } finally { try { @@ -263,19 +261,12 @@ case Types.NUMERIC: case Types.DECIMAL: - BigDecimal bd = null; - - try { - bd = set.getBigDecimal(col, set.getMetaData().getPrecision(col)); - } catch (Throwable t) { - bd = set.getBigDecimal(col, 10); - } - + BigDecimal bd = set.getBigDecimal(col); obj = (bd == null) ? Py.None : Py.newFloat(bd.doubleValue()); break; case Types.BIT: - obj = set.getBoolean(col) ? Py.One : Py.Zero; + obj = set.getBoolean(col) ? Py.True : Py.False; break; case Types.INTEGER: @@ -285,7 +276,7 @@ break; case Types.BIGINT: - obj = new PyLong(set.getLong(col)); + obj = Py.newLong(set.getLong(col)); break; case Types.FLOAT: @@ -298,15 +289,15 @@ break; case Types.TIME: - obj = Py.java2py(set.getTime(col)); + obj = Py.newTime(set.getTime(col)); break; case Types.TIMESTAMP: - obj = Py.java2py(set.getTimestamp(col)); + obj = Py.newDatetime(set.getTimestamp(col)); break; case Types.DATE: - obj = Py.java2py(set.getDate(col)); + obj = Py.newDate(set.getDate(col)); break; case Types.NULL: @@ -353,18 +344,18 @@ case Types.LONGVARCHAR: String string = stmt.getString(col); - obj = (string == null) ? Py.None : Py.newString(string); + obj = (string == null) ? Py.None : Py.newUnicode(string); break; case Types.NUMERIC: case Types.DECIMAL: - BigDecimal bd = stmt.getBigDecimal(col, 10); + BigDecimal bd = stmt.getBigDecimal(col); obj = (bd == null) ? Py.None : Py.newFloat(bd.doubleValue()); break; case Types.BIT: - obj = stmt.getBoolean(col) ? Py.One : Py.Zero; + obj = stmt.getBoolean(col) ? Py.True : Py.False; break; case Types.INTEGER: @@ -374,7 +365,7 @@ break; case Types.BIGINT: - obj = new PyLong(stmt.getLong(col)); + obj = Py.newLong(stmt.getLong(col)); break; case Types.FLOAT: @@ -387,15 +378,15 @@ break; case Types.TIME: - obj = Py.java2py(stmt.getTime(col)); + obj = Py.newTime(stmt.getTime(col)); break; case Types.TIMESTAMP: - obj = Py.java2py(stmt.getTimestamp(col)); + obj = Py.newDatetime(stmt.getTimestamp(col)); break; case Types.DATE: - obj = Py.java2py(stmt.getDate(col)); + obj = Py.newDate(stmt.getDate(col)); break; case Types.NULL: Added: branches/asm/src/com/ziclix/python/sql/Jython22DataHandler.java =================================================================== --- branches/asm/src/com/ziclix/python/sql/Jython22DataHandler.java (rev 0) +++ branches/asm/src/com/ziclix/python/sql/Jython22DataHandler.java 2008-07-15 21:21:30 UTC (rev 4947) @@ -0,0 +1,452 @@ +/* + * Jython Database Specification API 2.0 + * + * $Id: DataHandler.java 3708 2007-11-20 20:03:46Z pjenvey $ + * + * Copyright (c) 2001 brian zimmer <bz...@zi...> + * + */ +package com.ziclix.python.sql; + +import org.python.core.Py; +import org.python.core.PyFile; +import org.python.core.PyLong; +import org.python.core.PyObject; +import org.python.core.PyList; +import org.python.core.PyString; +import org.python.core.util.StringUtil; + +import java.io.BufferedInputStream; +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.Reader; +import java.io.StringReader; +import java.math.BigDecimal; +import java.sql.CallableStatement; +import java.sql.Date; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.sql.Time; +import java.sql.Timestamp; +import java.sql.Types; + +/** + * A copy of the DataHandler class as it was before Jython 2.5. By that version, + * some backward-incompatible changes was made, as returning datetime.* + * objects for DATE, TIME and TIMESTAMP columns, instead of java.sql.* classes. + * + * @author brian zimmer + * @author last revised by $Author: pjenvey $ + * @version $Revision: 3708 $ + */ +public class Jython22DataHandler extends DataHandler { + /** + * Handle most generic Java data types. + */ + public Jython22DataHandler() {} + + /** + * Some database vendors are case sensitive on calls to DatabaseMetaData, + * most notably Oracle. This callback allows a DataHandler to affect the + * name. + */ + public String getMetaDataName(PyObject name) { + return ((name == Py.None) ? null : name.__str__().toString()); + } + + /** + * A factory method for determing the correct procedure class to use + * per the cursor type. + * @param cursor an open cursor + * @param name the name of the procedure to invoke + * @return an instance of a Procedure + * @throws SQLException + */ + public Procedure getProcedure(PyCursor cursor, PyObject name) throws SQLException { + return new Procedure(cursor, name); + } + + /** + * Returns the row id of the last executed statement. + * + * @param stmt the current statement + * @return the row id of the last executed statement or None + * @throws SQLException thrown if an exception occurs + * + */ + public PyObject getRowId(Statement stmt) throws SQLException { + return Py.None; + } + + /** + * A callback prior to each execution of the statement. If the statement is + * a PreparedStatement, all the parameters will have been set. + */ + public void preExecute(Statement stmt) throws SQLException { + return; + } + + /** + * A callback after successfully executing the statement. + */ + public void postExecute(Statement stmt) throws SQLException { + return; + } + + /** + * Any .execute() which uses prepared statements will receive a callback for deciding + * how to map the PyObject to the appropriate JDBC type. + * + * @param stmt the current PreparedStatement + * @param index the index for which this object is bound + * @param object the PyObject in question + * @throws SQLException + */ + public void setJDBCObject(PreparedStatement stmt, int index, PyObject object) throws SQLException { + + try { + stmt.setObject(index, object.__tojava__(Object.class)); + } catch (Exception e) { + SQLException cause = null, ex = new SQLException("error setting index [" + index + "]"); + + if (e instanceof SQLException) { + cause = (SQLException) e; + } else { + cause = new SQLException(e.getMessage()); + } + + ex.setNextException(cause); + + throw ex; + } + } + + /** + * Any .execute() which uses prepared statements will receive a callback for deciding + * how to map the PyObject to the appropriate JDBC type. The <i>type</i> is the JDBC + * type as obtained from <i>java.sql.Types</i>. + * + * @param stmt the current PreparedStatement + * @param index the index for which this object is bound + * @param object the PyObject in question + * @param type the <i>java.sql.Types</i> for which this PyObject should be bound + * @throws SQLException + */ + public void setJDBCObject(PreparedStatement stmt, int index, PyObject object, int type) throws SQLException { + + try { + if (checkNull(stmt, index, object, type)) { + return; + } + + switch (type) { + + case Types.DATE: + Date date = (Date) object.__tojava__(Date.class); + + stmt.setDate(index, date); + break; + + case Types.TIME: + Time time = (Time) object.__tojava__(Time.class); + + stmt.setTime(index, time); + break; + + case Types.TIMESTAMP: + Timestamp timestamp = (Timestamp) object.__tojava__(Timestamp.class); + + stmt.setTimestamp(index, timestamp); + break; + + case Types.LONGVARCHAR: + if (object instanceof PyFile) { + object = new PyString(((PyFile) object).read()); + } + + String varchar = (String) object.__tojava__(String.class); + Reader reader = new BufferedReader(new StringReader(varchar)); + + stmt.setCharacterStream(index, reader, varchar.length()); + break; + + case Types.BIT: + stmt.setBoolean(index, object.__nonzero__()); + break; + + default : + if (object instanceof PyFile) { + object = new PyString(((PyFile) object).read()); + } + + stmt.setObject(index, object.__tojava__(Object.class), type); + break; + } + } catch (Exception e) { + SQLException cause = null, ex = new SQLException("error setting index [" + index + "], type [" + type + "]"); + + if (e instanceof SQLException) { + cause = (SQLException) e; + } else { + cause = new SQLException(e.getMessage()); + } + + ex.setNextException(cause); + + throw ex; + } + } + + /** + * Given a ResultSet, column and type, return the appropriate + * Jython object. + * + * <p>Note: DO NOT iterate the ResultSet. + * + * @param set the current ResultSet set to the current row + * @param col the column number (adjusted properly for JDBC) + * @param type the column type + * @throws SQLException if the type is unmappable + */ + public PyObject getPyObject(ResultSet set, int col, int type) throws SQLException { + + PyObject obj = Py.None; + + switch (type) { + + case Types.CHAR: + case Types.VARCHAR: + String string = set.getString(col); + + obj = (string == null) ? Py.None : Py.newString(string); + break; + + case Types.LONGVARCHAR: + InputStream longvarchar = set.getAsciiStream(col); + + if (longvarchar == null) { + obj = Py.None; + } else { + try { + longvarchar = new BufferedInputStream(longvarchar); + + byte[] bytes = Jython22DataHandler.read(longvarchar); + + if (bytes != null) { + obj = Py.newString(StringUtil.fromBytes(bytes)); + } + } finally { + try { + longvarchar.close(); + } catch (Throwable t) {} + } + } + break; + + case Types.NUMERIC: + case Types.DECIMAL: + BigDecimal bd = null; + + try { + bd = set.getBigDecimal(col, set.getMetaData().getPrecision(col)); + } catch (Throwable t) { + bd = set.getBigDecimal(col, 10); + } + + obj = (bd == null) ? Py.None : Py.newFloat(bd.doubleValue()); + break; + + case Types.BIT: + obj = set.getBoolean(col) ? Py.One : Py.Zero; + break; + + case Types.INTEGER: + case Types.TINYINT: + case Types.SMALLINT: + obj = Py.newInteger(set.getInt(col)); + break; + + case Types.BIGINT: + obj = new PyLong(set.getLong(col)); + break; + + case Types.FLOAT: + case Types.REAL: + obj = Py.newFloat(set.getFloat(col)); + break; + + case Types.DOUBLE: + obj = Py.newFloat(set.getDouble(col)); + break; + + case Types.TIME: + obj = Py.java2py(set.getTime(col)); + break; + + case Types.TIMESTAMP: + obj = Py.java2py(set.getTimestamp(col)); + break; + + case Types.DATE: + obj = Py.java2py(set.getDate(col)); + break; + + case Types.NULL: + obj = Py.None; + break; + + case Types.OTHER: + obj = Py.java2py(set.getObject(col)); + break; + + case Types.BINARY: + case Types.VARBINARY: + case Types.LONGVARBINARY: + obj = Py.java2py(set.getBytes(col)); + break; + + default : + Integer[] vals = {new Integer(col), new Integer(type)}; + String msg = zxJDBC.getString("errorGettingIndex", vals); + + throw new SQLException(msg); + } + + return (set.wasNull() || (obj == null)) ? Py.None : obj; + } + + /** + * Given a CallableStatement, column and type, return the appropriate + * Jython object. + * + * @param stmt the CallableStatement + * @param col the column number (adjusted properly for JDBC) + * @param type the column type + * @throws SQLException if the type is unmappable + */ + public PyObject getPyObject(CallableStatement stmt, int col, int type) throws SQLException { + + PyObject obj = Py.None; + + switch (type) { + + case Types.CHAR: + case Types.VARCHAR: + case Types.LONGVARCHAR: + String string = stmt.getString(col); + + obj = (string == null) ? Py.None : Py.newString(string); + break; + + case Types.NUMERIC: + case Types.DECIMAL: + BigDecimal bd = stmt.getBigDecimal(col, 10); + + obj = (bd == null) ? Py.None : Py.newFloat(bd.doubleValue()); + break; + + case Types.BIT: + obj = stmt.getBoolean(col) ? Py.One : Py.Zero; + break; + + case Types.INTEGER: + case Types.TINYINT: + case Types.SMALLINT: + obj = Py.newInteger(stmt.getInt(col)); + break; + + case Types.BIGINT: + obj = new PyLong(stmt.getLong(col)); + break; + + case Types.FLOAT: + case Types.REAL: + obj = Py.newFloat(stmt.getFloat(col)); + break; + + case Types.DOUBLE: + obj = Py.newFloat(stmt.getDouble(col)); + break; + + case Types.TIME: + obj = Py.java2py(stmt.getTime(col)); + break; + + case Types.TIMESTAMP: + obj = Py.java2py(stmt.getTimestamp(col)); + break; + + case Types.DATE: + obj = Py.java2py(stmt.getDate(col)); + break; + + case Types.NULL: + obj = Py.None; + break; + + case Types.OTHER: + obj = Py.java2py(stmt.getObject(col)); + break; + + case Types.BINARY: + case Types.VARBINARY: + case Types.LONGVARBINARY: + obj = Py.java2py(stmt.getBytes(col)); + break; + + default : + Integer[] vals = {new Integer(col), new Integer(type)}; + String msg = zxJDBC.getString("errorGettingIndex", vals); + + throw new SQLException(msg); + } + + return (stmt.wasNull() || (obj == null)) ? Py.None : obj; + } + + /** + * Called when a stored procedure or function is executed and OUT parameters + * need to be registered with the statement. + * + * @param statement + * @param index the JDBC offset column number + * @param colType the column as from DatabaseMetaData (eg, procedureColumnOut) + * @param dataType the JDBC datatype from Types + * @param dataTypeName the JDBC datatype name + * + * @throws SQLException + * + */ + public void registerOut(CallableStatement statement, int index, int colType, int dataType, String dataTypeName) throws SQLException { + + try { + statement.registerOutParameter(index, dataType); + } catch (Throwable t) { + SQLException cause = null; + SQLException ex = new SQLException("error setting index [" + + index + "], coltype [" + colType + "], datatype [" + dataType + + "], datatypename [" + dataTypeName + "]"); + + if (t instanceof SQLException) { + cause = (SQLException)t; + } else { + cause = new SQLException(t.getMessage()); + } + ex.setNextException(cause); + throw ex; + } + } + + /** + * Returns a list of datahandlers chained together through the use of delegation. + * + * @return a list of datahandlers + */ + public PyObject __chain__() { + return new PyList(new PyObject[] { Py.java2py(this) }); + } + +} + Modified: branches/asm/src/org/python/core/Py.java =================================================================== --- branches/asm/src/org/python/core/Py.java 2008-07-15 21:05:06 UTC (rev 4946) +++ branches/asm/src/org/python/core/Py.java 2008-07-15 21:21:30 UTC (rev 4947) @@ -12,6 +12,10 @@ import java.io.Serializable; import java.io.StreamCorruptedException; import java.lang.reflect.InvocationTargetException; +import java.sql.Date; +import java.sql.Time; +import java.sql.Timestamp; +import java.util.Calendar; import java.util.HashSet; import java.util.Set; @@ -580,6 +584,54 @@ return t ? Py.True : Py.False; } + public static PyObject newDate(Date date) { + if (date == null) { + return Py.None; + } + PyObject datetimeModule = __builtin__.__import__("datetime"); + PyObject dateClass = datetimeModule.__getattr__("date"); + Calendar cal = Calendar.getInstance(); + cal.setTime(date); + + return dateClass.__call__(newInteger(cal.get(Calendar.YEAR)), + newInteger(cal.get(Calendar.MONTH) + 1), + newInteger(cal.get(Calendar.DAY_OF_MONTH))); + + } + + public static PyObject newTime(Time time) { + if (time == null) { + return Py.None; + } + PyObject datetimeModule = __builtin__.__import__("datetime"); + PyObject timeClass = datetimeModule.__getattr__("time"); + Calendar cal = Calendar.getInstance(); + cal.setTime(time); + return timeClass.__call__(newInteger(cal.get(Calendar.HOUR_OF_DAY)), + newInteger(cal.get(Calendar.MINUTE)), + newInteger(cal.get(Calendar.SECOND)), + newInteger(cal.get(Calendar.MILLISECOND) * + 1000)); + } + + public static PyObject newDatetime(Timestamp timestamp) { + if (timestamp == null) { + return Py.None; + } + PyObject datetimeModule = __builtin__.__import__("datetime"); + PyObject datetimeClass = datetimeModule.__getattr__("datetime"); + Calendar cal = Calendar.getInstance(); + cal.setTime(timestamp); + return datetimeClass.__call__(new PyObject[] { + newInteger(cal.get(Calendar.YEAR)), + newInteger(cal.get(Calendar.MONTH) + 1), + newInteger(cal.get(Calendar.DAY_OF_MONTH)), + newInteger(cal.get(Calendar.HOUR_OF_DAY)), + newInteger(cal.get(Calendar.MINUTE)), + newInteger(cal.get(Calendar.SECOND)), + newInteger(timestamp.getNanos() / 1000)}); + } + public static PyCode newCode(int argcount, String varnames[], String filename, String name, boolean args, boolean keywords, This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |