001: /*
002: * Jython Database Specification API 2.0
003: *
004: * $Id: DataHandler.java 2939 2006-09-13 23:17:45Z kzuberi $
005: *
006: * Copyright (c) 2001 brian zimmer <bzimmer@ziclix.com>
007: *
008: */
009: package com.ziclix.python.sql;
010:
011: import org.python.core.Py;
012: import org.python.core.PyFile;
013: import org.python.core.PyLong;
014: import org.python.core.PyObject;
015: import org.python.core.PyList;
016: import org.python.core.PyString;
017:
018: import java.io.BufferedInputStream;
019: import java.io.BufferedReader;
020: import java.io.IOException;
021: import java.io.InputStream;
022: import java.io.Reader;
023: import java.io.StringReader;
024: import java.lang.reflect.Constructor;
025: import java.math.BigDecimal;
026: import java.sql.CallableStatement;
027: import java.sql.Date;
028: import java.sql.PreparedStatement;
029: import java.sql.ResultSet;
030: import java.sql.SQLException;
031: import java.sql.Statement;
032: import java.sql.Time;
033: import java.sql.Timestamp;
034: import java.sql.Types;
035:
036: /**
037: * The DataHandler is responsible mapping the JDBC data type to
038: * a Jython object. Depending on the version of the JDBC
039: * implementation and the particulars of the driver, the type
040: * mapping can be significantly different.
041: *
042: * This interface can also be used to change the behaviour of
043: * the default mappings provided by the cursor. This might be
044: * useful in handling more complicated data types such as BLOBs,
045: * CLOBs and Arrays.
046: *
047: * @author brian zimmer
048: * @author last revised by $Author: kzuberi $
049: * @version $Revision: 2939 $
050: */
051: public class DataHandler {
052:
053: // default size for buffers
054: private static final int INITIAL_SIZE = 1024 * 4;
055:
056: private static final String[] SYSTEM_DATAHANDLERS = { "com.ziclix.python.sql.JDBC20DataHandler" };
057:
058: /**
059: * Handle most generic Java data types.
060: */
061: public DataHandler() {
062: }
063:
064: /**
065: * Some database vendors are case sensitive on calls to DatabaseMetaData,
066: * most notably Oracle. This callback allows a DataHandler to affect the
067: * name.
068: */
069: public String getMetaDataName(PyObject name) {
070: return ((name == Py.None) ? null : name.__str__().toString());
071: }
072:
073: /**
074: * A factory method for determing the correct procedure class to use
075: * per the cursor type.
076: * @param cursor an open cursor
077: * @param name the name of the procedure to invoke
078: * @return an instance of a Procedure
079: * @throws SQLException
080: */
081: public Procedure getProcedure(PyCursor cursor, PyObject name)
082: throws SQLException {
083: return new Procedure(cursor, name);
084: }
085:
086: /**
087: * Returns the row id of the last executed statement.
088: *
089: * @param stmt the current statement
090: * @return the row id of the last executed statement or None
091: * @throws SQLException thrown if an exception occurs
092: *
093: */
094: public PyObject getRowId(Statement stmt) throws SQLException {
095: return Py.None;
096: }
097:
098: /**
099: * A callback prior to each execution of the statement. If the statement is
100: * a PreparedStatement, all the parameters will have been set.
101: */
102: public void preExecute(Statement stmt) throws SQLException {
103: return;
104: }
105:
106: /**
107: * A callback after successfully executing the statement.
108: */
109: public void postExecute(Statement stmt) throws SQLException {
110: return;
111: }
112:
113: /**
114: * Any .execute() which uses prepared statements will receive a callback for deciding
115: * how to map the PyObject to the appropriate JDBC type.
116: *
117: * @param stmt the current PreparedStatement
118: * @param index the index for which this object is bound
119: * @param object the PyObject in question
120: * @throws SQLException
121: */
122: public void setJDBCObject(PreparedStatement stmt, int index,
123: PyObject object) throws SQLException {
124:
125: try {
126: stmt.setObject(index, object.__tojava__(Object.class));
127: } catch (Exception e) {
128: SQLException cause = null, ex = new SQLException(
129: "error setting index [" + index + "]");
130:
131: if (e instanceof SQLException) {
132: cause = (SQLException) e;
133: } else {
134: cause = new SQLException(e.getMessage());
135: }
136:
137: ex.setNextException(cause);
138:
139: throw ex;
140: }
141: }
142:
143: /**
144: * Any .execute() which uses prepared statements will receive a callback for deciding
145: * how to map the PyObject to the appropriate JDBC type. The <i>type</i> is the JDBC
146: * type as obtained from <i>java.sql.Types</i>.
147: *
148: * @param stmt the current PreparedStatement
149: * @param index the index for which this object is bound
150: * @param object the PyObject in question
151: * @param type the <i>java.sql.Types</i> for which this PyObject should be bound
152: * @throws SQLException
153: */
154: public void setJDBCObject(PreparedStatement stmt, int index,
155: PyObject object, int type) throws SQLException {
156:
157: try {
158: if (checkNull(stmt, index, object, type)) {
159: return;
160: }
161:
162: switch (type) {
163:
164: case Types.DATE:
165: Date date = (Date) object.__tojava__(Date.class);
166:
167: stmt.setDate(index, date);
168: break;
169:
170: case Types.TIME:
171: Time time = (Time) object.__tojava__(Time.class);
172:
173: stmt.setTime(index, time);
174: break;
175:
176: case Types.TIMESTAMP:
177: Timestamp timestamp = (Timestamp) object
178: .__tojava__(Timestamp.class);
179:
180: stmt.setTimestamp(index, timestamp);
181: break;
182:
183: case Types.LONGVARCHAR:
184: if (object instanceof PyFile) {
185: object = new PyString(((PyFile) object).read());
186: }
187:
188: String varchar = (String) object
189: .__tojava__(String.class);
190: Reader reader = new BufferedReader(new StringReader(
191: varchar));
192:
193: stmt
194: .setCharacterStream(index, reader, varchar
195: .length());
196: break;
197:
198: case Types.BIT:
199: stmt.setBoolean(index, object.__nonzero__());
200: break;
201:
202: default:
203: if (object instanceof PyFile) {
204: object = new PyString(((PyFile) object).read());
205: }
206:
207: stmt.setObject(index, object.__tojava__(Object.class),
208: type);
209: break;
210: }
211: } catch (Exception e) {
212: SQLException cause = null, ex = new SQLException(
213: "error setting index [" + index + "], type ["
214: + type + "]");
215:
216: if (e instanceof SQLException) {
217: cause = (SQLException) e;
218: } else {
219: cause = new SQLException(e.getMessage());
220: }
221:
222: ex.setNextException(cause);
223:
224: throw ex;
225: }
226: }
227:
228: /**
229: * Given a ResultSet, column and type, return the appropriate
230: * Jython object.
231: *
232: * <p>Note: DO NOT iterate the ResultSet.
233: *
234: * @param set the current ResultSet set to the current row
235: * @param col the column number (adjusted properly for JDBC)
236: * @param type the column type
237: * @throws SQLException if the type is unmappable
238: */
239: public PyObject getPyObject(ResultSet set, int col, int type)
240: throws SQLException {
241:
242: PyObject obj = Py.None;
243:
244: switch (type) {
245:
246: case Types.CHAR:
247: case Types.VARCHAR:
248: String string = set.getString(col);
249:
250: obj = (string == null) ? Py.None : Py.newString(string);
251: break;
252:
253: case Types.LONGVARCHAR:
254: InputStream longvarchar = set.getAsciiStream(col);
255:
256: if (longvarchar == null) {
257: obj = Py.None;
258: } else {
259: try {
260: longvarchar = new BufferedInputStream(longvarchar);
261:
262: byte[] bytes = DataHandler.read(longvarchar);
263:
264: if (bytes != null) {
265: obj = Py.newString(new String(bytes));
266: }
267: } finally {
268: try {
269: longvarchar.close();
270: } catch (Throwable t) {
271: }
272: }
273: }
274: break;
275:
276: case Types.NUMERIC:
277: case Types.DECIMAL:
278: BigDecimal bd = null;
279:
280: try {
281: bd = set.getBigDecimal(col, set.getMetaData()
282: .getPrecision(col));
283: } catch (Throwable t) {
284: bd = set.getBigDecimal(col, 10);
285: }
286:
287: obj = (bd == null) ? Py.None : Py
288: .newFloat(bd.doubleValue());
289: break;
290:
291: case Types.BIT:
292: obj = set.getBoolean(col) ? Py.One : Py.Zero;
293: break;
294:
295: case Types.INTEGER:
296: case Types.TINYINT:
297: case Types.SMALLINT:
298: obj = Py.newInteger(set.getInt(col));
299: break;
300:
301: case Types.BIGINT:
302: obj = new PyLong(set.getLong(col));
303: break;
304:
305: case Types.FLOAT:
306: case Types.REAL:
307: obj = Py.newFloat(set.getFloat(col));
308: break;
309:
310: case Types.DOUBLE:
311: obj = Py.newFloat(set.getDouble(col));
312: break;
313:
314: case Types.TIME:
315: obj = Py.java2py(set.getTime(col));
316: break;
317:
318: case Types.TIMESTAMP:
319: obj = Py.java2py(set.getTimestamp(col));
320: break;
321:
322: case Types.DATE:
323: obj = Py.java2py(set.getDate(col));
324: break;
325:
326: case Types.NULL:
327: obj = Py.None;
328: break;
329:
330: case Types.OTHER:
331: obj = Py.java2py(set.getObject(col));
332: break;
333:
334: case Types.BINARY:
335: case Types.VARBINARY:
336: case Types.LONGVARBINARY:
337: obj = Py.java2py(set.getBytes(col));
338: break;
339:
340: default:
341: Integer[] vals = { new Integer(col), new Integer(type) };
342: String msg = zxJDBC.getString("errorGettingIndex", vals);
343:
344: throw new SQLException(msg);
345: }
346:
347: return (set.wasNull() || (obj == null)) ? Py.None : obj;
348: }
349:
350: /**
351: * Given a CallableStatement, column and type, return the appropriate
352: * Jython object.
353: *
354: * @param stmt the CallableStatement
355: * @param col the column number (adjusted properly for JDBC)
356: * @param type the column type
357: * @throws SQLException if the type is unmappable
358: */
359: public PyObject getPyObject(CallableStatement stmt, int col,
360: int type) throws SQLException {
361:
362: PyObject obj = Py.None;
363:
364: switch (type) {
365:
366: case Types.CHAR:
367: case Types.VARCHAR:
368: case Types.LONGVARCHAR:
369: String string = stmt.getString(col);
370:
371: obj = (string == null) ? Py.None : Py.newString(string);
372: break;
373:
374: case Types.NUMERIC:
375: case Types.DECIMAL:
376: BigDecimal bd = stmt.getBigDecimal(col, 10);
377:
378: obj = (bd == null) ? Py.None : Py
379: .newFloat(bd.doubleValue());
380: break;
381:
382: case Types.BIT:
383: obj = stmt.getBoolean(col) ? Py.One : Py.Zero;
384: break;
385:
386: case Types.INTEGER:
387: case Types.TINYINT:
388: case Types.SMALLINT:
389: obj = Py.newInteger(stmt.getInt(col));
390: break;
391:
392: case Types.BIGINT:
393: obj = new PyLong(stmt.getLong(col));
394: break;
395:
396: case Types.FLOAT:
397: case Types.REAL:
398: obj = Py.newFloat(stmt.getFloat(col));
399: break;
400:
401: case Types.DOUBLE:
402: obj = Py.newFloat(stmt.getDouble(col));
403: break;
404:
405: case Types.TIME:
406: obj = Py.java2py(stmt.getTime(col));
407: break;
408:
409: case Types.TIMESTAMP:
410: obj = Py.java2py(stmt.getTimestamp(col));
411: break;
412:
413: case Types.DATE:
414: obj = Py.java2py(stmt.getDate(col));
415: break;
416:
417: case Types.NULL:
418: obj = Py.None;
419: break;
420:
421: case Types.OTHER:
422: obj = Py.java2py(stmt.getObject(col));
423: break;
424:
425: case Types.BINARY:
426: case Types.VARBINARY:
427: case Types.LONGVARBINARY:
428: obj = Py.java2py(stmt.getBytes(col));
429: break;
430:
431: default:
432: Integer[] vals = { new Integer(col), new Integer(type) };
433: String msg = zxJDBC.getString("errorGettingIndex", vals);
434:
435: throw new SQLException(msg);
436: }
437:
438: return (stmt.wasNull() || (obj == null)) ? Py.None : obj;
439: }
440:
441: /**
442: * Called when a stored procedure or function is executed and OUT parameters
443: * need to be registered with the statement.
444: *
445: * @param statement
446: * @param index the JDBC offset column number
447: * @param colType the column as from DatabaseMetaData (eg, procedureColumnOut)
448: * @param dataType the JDBC datatype from Types
449: * @param dataTypeName the JDBC datatype name
450: *
451: * @throws SQLException
452: *
453: */
454: public void registerOut(CallableStatement statement, int index,
455: int colType, int dataType, String dataTypeName)
456: throws SQLException {
457:
458: try {
459: statement.registerOutParameter(index, dataType);
460: } catch (Throwable t) {
461: SQLException cause = null;
462: SQLException ex = new SQLException("error setting index ["
463: + index + "], coltype [" + colType
464: + "], datatype [" + dataType + "], datatypename ["
465: + dataTypeName + "]");
466:
467: if (t instanceof SQLException) {
468: cause = (SQLException) t;
469: } else {
470: cause = new SQLException(t.getMessage());
471: }
472: ex.setNextException(cause);
473: throw ex;
474: }
475: }
476:
477: /**
478: * Handles checking if the object is null or None and setting it on the statement.
479: *
480: * @return true if the object is null and was set on the statement, false otherwise
481: */
482: public static final boolean checkNull(PreparedStatement stmt,
483: int index, PyObject object, int type) throws SQLException {
484:
485: if ((object == null) || (Py.None == object)) {
486: stmt.setNull(index, type);
487: return true;
488: }
489: return false;
490: }
491:
492: /**
493: * Since the driver needs to the know the length of all streams,
494: * read it into a byte[] array.
495: *
496: * @return the stream as a byte[]
497: */
498: public static final byte[] read(InputStream stream) {
499:
500: int b = -1, read = 0;
501: byte[] results = new byte[INITIAL_SIZE];
502:
503: try {
504: while ((b = stream.read()) != -1) {
505: if (results.length < (read + 1)) {
506: byte[] tmp = results;
507: results = new byte[results.length * 2];
508: System.arraycopy(tmp, 0, results, 0, tmp.length);
509: }
510: results[read++] = (byte) b;
511: }
512: } catch (IOException e) {
513: throw zxJDBC.makeException(e);
514: }
515:
516: byte[] tmp = results;
517: results = new byte[read];
518: System.arraycopy(tmp, 0, results, 0, read);
519: return results;
520: }
521:
522: /**
523: * Read all the chars from the Reader into the String.
524: *
525: * @return the contents of the Reader in a String
526: */
527: public static final String read(Reader reader) {
528:
529: int c = 0;
530: StringBuffer buffer = new StringBuffer(INITIAL_SIZE);
531:
532: try {
533: while ((c = reader.read()) != -1) {
534: buffer.append((char) c);
535: }
536: } catch (IOException e) {
537: throw zxJDBC.makeException(e);
538: }
539:
540: return buffer.toString();
541: }
542:
543: /**
544: * Build the DataHandler chain depending on the VM. This guarentees a DataHandler
545: * but might additionally chain a JDBC2.0 or JDBC3.0 implementation.
546: * @return a DataHandler configured for the VM version
547: */
548: public static final DataHandler getSystemDataHandler() {
549: DataHandler dh = new DataHandler();
550:
551: for (int i = 0; i < SYSTEM_DATAHANDLERS.length; i++) {
552: try {
553: Class c = Class.forName(SYSTEM_DATAHANDLERS[i]);
554: Constructor cons = c
555: .getConstructor(new Class[] { DataHandler.class });
556: dh = (DataHandler) cons
557: .newInstance(new Object[] { dh });
558: } catch (Throwable t) {
559: }
560: }
561:
562: return dh;
563: }
564:
565: /**
566: * Returns a list of datahandlers chained together through the use of delegation.
567: *
568: * @return a list of datahandlers
569: */
570: public PyObject __chain__() {
571: return new PyList(new PyObject[] { Py.java2py(this ) });
572: }
573:
574: /**
575: * Returns the classname of this datahandler.
576: */
577: public String toString() {
578: return getClass().getName();
579: }
580: }
|