001: /*-------------------------------------------------------------------------
002: *
003: * Copyright (c) 2004-2005, PostgreSQL Global Development Group
004: *
005: * IDENTIFICATION
006: * $PostgreSQL: pgjdbc/org/postgresql/jdbc2/AbstractJdbc2Array.java,v 1.18 2005/12/04 21:40:33 jurka Exp $
007: *
008: *-------------------------------------------------------------------------
009: */
010: package org.postgresql.jdbc2;
011:
012: import org.postgresql.core.*;
013: import org.postgresql.util.PSQLException;
014: import org.postgresql.util.PSQLState;
015: import org.postgresql.util.GT;
016:
017: import java.math.BigDecimal;
018: import java.sql.ResultSet;
019: import java.sql.SQLException;
020: import java.sql.Timestamp;
021: import java.sql.Types;
022: import java.util.ArrayList;
023: import java.util.Map;
024: import java.util.Vector;
025: import java.util.GregorianCalendar;
026:
027: /**
028: * Array is used collect one column of query result data.
029: *
030: * <p>Read a field of type Array into either a natively-typed
031: * Java array object or a ResultSet. Accessor methods provide
032: * the ability to capture array slices.
033: *
034: * <p>Other than the constructor all methods are direct implementations
035: * of those specified for java.sql.Array. Please refer to the javadoc
036: * for java.sql.Array for detailed descriptions of the functionality
037: * and parameters of the methods of this class.
038: *
039: * @see ResultSet#getArray
040: */
041:
042: public class AbstractJdbc2Array {
043: private BaseConnection conn = null;
044: private Field field = null;
045: private BaseResultSet rs;
046: private int idx = 0;
047: private String rawString = null;
048:
049: /**
050: * Create a new Array
051: *
052: * @param conn a database connection
053: * @param idx 1-based index of the query field to load into this Array
054: * @param field the Field descriptor for the field to load into this Array
055: * @param rs the ResultSet from which to get the data for this Array
056: */
057: public AbstractJdbc2Array(BaseConnection conn, int idx,
058: Field field, BaseResultSet rs) throws SQLException {
059: this .conn = conn;
060: this .field = field;
061: this .rs = rs;
062: this .idx = idx;
063: this .rawString = rs.getFixedString(idx);
064: }
065:
066: public Object getArray() throws SQLException {
067: return getArrayImpl(1, 0, null);
068: }
069:
070: public Object getArray(long index, int count) throws SQLException {
071: return getArrayImpl(index, count, null);
072: }
073:
074: public Object getArrayImpl(Map map) throws SQLException {
075: return getArrayImpl(1, 0, map);
076: }
077:
078: public Object getArrayImpl(long index, int count, Map map)
079: throws SQLException {
080: if (map != null && !map.isEmpty()) // For now maps aren't supported.
081: throw org.postgresql.Driver.notImplemented(this .getClass(),
082: "getArrayImpl(long,int,Map)");
083:
084: if (index < 1)
085: throw new PSQLException(GT.tr(
086: "The array index is out of range: {0}", new Long(
087: index)), PSQLState.DATA_ERROR);
088: Object retVal = null;
089:
090: ArrayList array = new ArrayList();
091:
092: /* Check if the String is also not an empty array
093: * otherwise there will be an exception thrown below
094: * in the ResultSet.toX with an empty string.
095: * -- Doug Fields <dfields-pg-jdbc@pexicom.com> Feb 20, 2002 */
096:
097: if (rawString != null && !rawString.equals("{}")) {
098: char[] chars = rawString.toCharArray();
099: StringBuffer sbuf = new StringBuffer();
100: boolean foundOpen = false;
101: boolean insideString = false;
102:
103: /**
104: * Starting with 8.0 non-standard (beginning index
105: * isn't 1) bounds the dimensions are returned in the
106: * data formatted like so "[0:3]={0,1,2,3,4}".
107: * Older versions simply do not return the bounds.
108: *
109: * Right now we ignore these bounds, but we could
110: * consider allowing these index values to be used
111: * even though the JDBC spec says 1 is the first
112: * index. I'm not sure what a client would like
113: * to see, so we just retain the old behavior.
114: */
115: int startOffset = 0;
116: if (chars[0] == '[') {
117: while (chars[startOffset] != '=') {
118: startOffset++;
119: }
120: startOffset++; // skip =
121: }
122:
123: for (int i = startOffset; i < chars.length; i++) {
124: if (chars[i] == '\\')
125: //escape character that we need to skip
126: i++;
127: else if (!insideString && chars[i] == '{') {
128: if (foundOpen) // Only supports 1-D arrays for now
129: throw new PSQLException(
130: GT
131: .tr("Multi-dimensional arrays are currently not supported."),
132: PSQLState.NOT_IMPLEMENTED);
133: foundOpen = true;
134: continue;
135: } else if (chars[i] == '"') {
136: insideString = !insideString;
137: continue;
138: } else if (!insideString
139: && (chars[i] == ',' || chars[i] == '}')
140: || i == chars.length - 1) {
141: if (chars[i] != '"' && chars[i] != '}'
142: && chars[i] != ',')
143: sbuf.append(chars[i]);
144: array.add(sbuf.toString());
145: sbuf = new StringBuffer();
146: continue;
147: }
148: sbuf.append(chars[i]);
149: }
150: }
151: String[] arrayContents = (String[]) array
152: .toArray(new String[array.size()]);
153: if (count == 0)
154: count = arrayContents.length;
155: index--;
156: if (index + count > arrayContents.length)
157: throw new PSQLException(
158: GT
159: .tr(
160: "The array index is out of range: {0}, number of elements: {1}.",
161: new Object[] {
162: new Long(index + count),
163: new Long(
164: arrayContents.length) }),
165: PSQLState.DATA_ERROR);
166:
167: int i = 0;
168: GregorianCalendar cal = null;
169: switch (getBaseType()) {
170: case Types.BIT:
171: retVal = new boolean[count];
172: for (; count > 0; count--)
173: ((boolean[]) retVal)[i++] = AbstractJdbc2ResultSet
174: .toBoolean(arrayContents[(int) index++]);
175: break;
176: case Types.SMALLINT:
177: case Types.INTEGER:
178: retVal = new int[count];
179: for (; count > 0; count--)
180: ((int[]) retVal)[i++] = AbstractJdbc2ResultSet
181: .toInt(arrayContents[(int) index++]);
182: break;
183: case Types.BIGINT:
184: retVal = new long[count];
185: for (; count > 0; count--)
186: ((long[]) retVal)[i++] = AbstractJdbc2ResultSet
187: .toLong(arrayContents[(int) index++]);
188: break;
189: case Types.NUMERIC:
190: retVal = new BigDecimal[count];
191: for (; count > 0; count--)
192: ((BigDecimal[]) retVal)[i++] = AbstractJdbc2ResultSet
193: .toBigDecimal(arrayContents[(int) index++], -1);
194: break;
195: case Types.REAL:
196: retVal = new float[count];
197: for (; count > 0; count--)
198: ((float[]) retVal)[i++] = AbstractJdbc2ResultSet
199: .toFloat(arrayContents[(int) index++]);
200: break;
201: case Types.DOUBLE:
202: retVal = new double[count];
203: for (; count > 0; count--)
204: ((double[]) retVal)[i++] = AbstractJdbc2ResultSet
205: .toDouble(arrayContents[(int) index++]);
206: break;
207: case Types.CHAR:
208: case Types.VARCHAR:
209: retVal = new String[count];
210: for (; count > 0; count--)
211: ((String[]) retVal)[i++] = arrayContents[(int) index++];
212: break;
213: case Types.DATE:
214: retVal = new java.sql.Date[count];
215: for (; count > 0; count--)
216: ((java.sql.Date[]) retVal)[i++] = conn
217: .getTimestampUtils().toDate(null,
218: arrayContents[(int) index++]);
219: break;
220: case Types.TIME:
221: retVal = new java.sql.Time[count];
222: for (; count > 0; count--)
223: ((java.sql.Time[]) retVal)[i++] = conn
224: .getTimestampUtils().toTime(null,
225: arrayContents[(int) index++]);
226: break;
227: case Types.TIMESTAMP:
228: retVal = new Timestamp[count];
229: for (; count > 0; count--)
230: ((java.sql.Timestamp[]) retVal)[i++] = conn
231: .getTimestampUtils().toTimestamp(null,
232: arrayContents[(int) index++]);
233: break;
234:
235: // Other datatypes not currently supported. If you are really using other types ask
236: // yourself if an array of non-trivial data types is really good database design.
237: default:
238: if (conn.getLogger().logDebug())
239: conn.getLogger().debug(
240: "getArrayImpl(long,int,Map) with "
241: + getBaseTypeName());
242: throw org.postgresql.Driver.notImplemented(this .getClass(),
243: "getArrayImpl(long,int,Map)");
244: }
245: return retVal;
246: }
247:
248: public int getBaseType() throws SQLException {
249: return conn.getSQLType(getBaseTypeName());
250: }
251:
252: public String getBaseTypeName() throws SQLException {
253: String fType = conn.getPGType(field.getOID());
254: if (fType.charAt(0) == '_')
255: fType = fType.substring(1);
256: return fType;
257: }
258:
259: public java.sql.ResultSet getResultSet() throws SQLException {
260: return getResultSetImpl(1, 0, null);
261: }
262:
263: public java.sql.ResultSet getResultSet(long index, int count)
264: throws SQLException {
265: return getResultSetImpl(index, count, null);
266: }
267:
268: public java.sql.ResultSet getResultSetImpl(Map map)
269: throws SQLException {
270: return getResultSetImpl(1, 0, map);
271: }
272:
273: private void fillIntegerResultSet(long index, int[] intArray,
274: Vector rows) throws SQLException {
275: for (int i = 0; i < intArray.length; i++) {
276: byte[][] tuple = new byte[2][0];
277: tuple[0] = conn.encodeString(Integer.toString((int) index
278: + i)); // Index
279: tuple[1] = conn.encodeString(Integer.toString(intArray[i])); // Value
280: rows.addElement(tuple);
281: }
282: }
283:
284: private void fillStringResultSet(long index, String[] strArray,
285: Vector rows) throws SQLException {
286: for (int i = 0; i < strArray.length; i++) {
287: byte[][] tuple = new byte[2][0];
288: tuple[0] = conn.encodeString(Integer.toString((int) index
289: + i)); // Index
290: tuple[1] = conn.encodeString(strArray[i]); // Value
291: rows.addElement(tuple);
292: }
293: }
294:
295: public java.sql.ResultSet getResultSetImpl(long index, int count,
296: java.util.Map map) throws SQLException {
297: Object array = getArrayImpl(index, count, map);
298: Vector rows = new Vector();
299: Field[] fields = new Field[2];
300: fields[0] = new Field("INDEX", Oid.INT2);
301: switch (getBaseType()) {
302: case Types.BIT:
303: boolean[] booleanArray = (boolean[]) array;
304: fields[1] = new Field("VALUE", Oid.BOOL);
305: for (int i = 0; i < booleanArray.length; i++) {
306: byte[][] tuple = new byte[2][0];
307: tuple[0] = conn.encodeString(Integer
308: .toString((int) index + i)); // Index
309: tuple[1] = conn.encodeString((booleanArray[i] ? "YES"
310: : "NO")); // Value
311: rows.addElement(tuple);
312: }
313: break;
314: case Types.SMALLINT:
315: fields[1] = new Field("VALUE", Oid.INT2);
316: fillIntegerResultSet(index, (int[]) array, rows);
317: break;
318: case Types.INTEGER:
319: fields[1] = new Field("VALUE", Oid.INT4);
320: fillIntegerResultSet(index, (int[]) array, rows);
321: break;
322: case Types.BIGINT:
323: long[] longArray = (long[]) array;
324: fields[1] = new Field("VALUE", Oid.INT8);
325: for (int i = 0; i < longArray.length; i++) {
326: byte[][] tuple = new byte[2][0];
327: tuple[0] = conn.encodeString(Integer
328: .toString((int) index + i)); // Index
329: tuple[1] = conn.encodeString(Long
330: .toString(longArray[i])); // Value
331: rows.addElement(tuple);
332: }
333: break;
334: case Types.NUMERIC:
335: BigDecimal[] bdArray = (BigDecimal[]) array;
336: fields[1] = new Field("VALUE", Oid.NUMERIC);
337: for (int i = 0; i < bdArray.length; i++) {
338: byte[][] tuple = new byte[2][0];
339: tuple[0] = conn.encodeString(Integer
340: .toString((int) index + i)); // Index
341: tuple[1] = conn.encodeString(bdArray[i].toString()); // Value
342: rows.addElement(tuple);
343: }
344: break;
345: case Types.REAL:
346: float[] floatArray = (float[]) array;
347: fields[1] = new Field("VALUE", Oid.FLOAT4);
348: for (int i = 0; i < floatArray.length; i++) {
349: byte[][] tuple = new byte[2][0];
350: tuple[0] = conn.encodeString(Integer
351: .toString((int) index + i)); // Index
352: tuple[1] = conn.encodeString(Float
353: .toString(floatArray[i])); // Value
354: rows.addElement(tuple);
355: }
356: break;
357: case Types.DOUBLE:
358: double[] doubleArray = (double[]) array;
359: fields[1] = new Field("VALUE", Oid.FLOAT8);
360: for (int i = 0; i < doubleArray.length; i++) {
361: byte[][] tuple = new byte[2][0];
362: tuple[0] = conn.encodeString(Integer
363: .toString((int) index + i)); // Index
364: tuple[1] = conn.encodeString(Double
365: .toString(doubleArray[i])); // Value
366: rows.addElement(tuple);
367: }
368: break;
369: case Types.CHAR:
370: fields[1] = new Field("VALUE", Oid.BPCHAR);
371: fillStringResultSet(index, (String[]) array, rows);
372: break;
373: case Types.VARCHAR:
374: fields[1] = new Field("VALUE", Oid.VARCHAR);
375: fillStringResultSet(index, (String[]) array, rows);
376: break;
377: case Types.DATE:
378: java.sql.Date[] dateArray = (java.sql.Date[]) array;
379: fields[1] = new Field("VALUE", Oid.DATE);
380: for (int i = 0; i < dateArray.length; i++) {
381: byte[][] tuple = new byte[2][0];
382: tuple[0] = conn.encodeString(Integer
383: .toString((int) index + i)); // Index
384: tuple[1] = conn.encodeString(conn.getTimestampUtils()
385: .toString(null, dateArray[i])); // Value
386: rows.addElement(tuple);
387: }
388: break;
389: case Types.TIME:
390: java.sql.Time[] timeArray = (java.sql.Time[]) array;
391: fields[1] = new Field("VALUE", Oid.TIME);
392: for (int i = 0; i < timeArray.length; i++) {
393: byte[][] tuple = new byte[2][0];
394: tuple[0] = conn.encodeString(Integer
395: .toString((int) index + i)); // Index
396: tuple[1] = conn.encodeString(conn.getTimestampUtils()
397: .toString(null, timeArray[i])); // Value
398: rows.addElement(tuple);
399: }
400: break;
401: case Types.TIMESTAMP:
402: java.sql.Timestamp[] timestampArray = (java.sql.Timestamp[]) array;
403: fields[1] = new Field("VALUE", Oid.TIMESTAMPTZ);
404: for (int i = 0; i < timestampArray.length; i++) {
405: byte[][] tuple = new byte[2][0];
406: tuple[0] = conn.encodeString(Integer
407: .toString((int) index + i)); // Index
408: tuple[1] = conn.encodeString(conn.getTimestampUtils()
409: .toString(null, timestampArray[i])); // Value
410: rows.addElement(tuple);
411: }
412: break;
413:
414: // Other datatypes not currently supported. If you are really using other types ask
415: // yourself if an array of non-trivial data types is really good database design.
416: default:
417: if (conn.getLogger().logDebug())
418: conn.getLogger().debug(
419: "getResultSetImpl(long,int,Map) with "
420: + getBaseTypeName());
421: throw org.postgresql.Driver.notImplemented(this .getClass(),
422: "getResultSetImpl(long,int,Map)");
423: }
424: BaseStatement stat = (BaseStatement) conn.createStatement(
425: ResultSet.TYPE_SCROLL_INSENSITIVE,
426: ResultSet.CONCUR_READ_ONLY);
427: return (ResultSet) stat.createDriverResultSet(fields, rows);
428: }
429:
430: public String toString() {
431: return rawString;
432: }
433: }
|