001: /*-------------------------------------------------------------------------
002: *
003: * Copyright (c) 2005, PostgreSQL Global Development Group
004: *
005: * IDENTIFICATION
006: * $PostgreSQL: pgjdbc/org/postgresql/jdbc2/TypeInfoCache.java,v 1.8 2007/07/15 15:33:33 jurka Exp $
007: *
008: *-------------------------------------------------------------------------
009: */
010:
011: package org.postgresql.jdbc2;
012:
013: import java.util.Map;
014: import java.util.HashMap;
015: import java.util.Iterator;
016: import java.util.Collections;
017: import java.sql.Types;
018: import java.sql.ResultSet;
019: import java.sql.SQLException;
020: import java.sql.PreparedStatement;
021: import org.postgresql.core.Oid;
022: import org.postgresql.core.BaseStatement;
023: import org.postgresql.core.BaseConnection;
024: import org.postgresql.core.QueryExecutor;
025: import org.postgresql.util.GT;
026: import org.postgresql.util.PGobject;
027: import org.postgresql.util.PSQLState;
028: import org.postgresql.util.PSQLException;
029:
030: public class TypeInfoCache {
031:
032: // pgname (String) -> java.sql.Types (Integer)
033: private static final Map _pgNameToSQLType;
034:
035: // pgname (String) -> java class name (String)
036: // ie "text" -> "java.lang.String"
037: private Map _pgNameToJavaClass;
038:
039: // oid (Integer) -> pgname (String)
040: private Map _oidToPgName;
041: // pgname (String) -> oid (Integer)
042: private Map _pgNameToOid;
043:
044: // pgname (String) -> extension pgobject (Class)
045: private Map _pgNameToPgObject;
046:
047: private BaseConnection _conn;
048: private PreparedStatement _getOidStatement;
049: private PreparedStatement _getNameStatement;
050:
051: private static final Object types[][] = {
052: { "int2", new Integer(Oid.INT2),
053: new Integer(Types.SMALLINT), "java.lang.Integer" },
054: { "int4", new Integer(Oid.INT4),
055: new Integer(Types.INTEGER), "java.lang.Integer" },
056: { "oid", new Integer(Oid.OID), new Integer(Types.BIGINT),
057: "java.lang.Long" },
058: { "int8", new Integer(Oid.INT8), new Integer(Types.BIGINT),
059: "java.lang.Long" },
060: { "money", new Integer(Oid.MONEY),
061: new Integer(Types.DOUBLE), "java.lang.Double" },
062: { "numeric", new Integer(Oid.NUMERIC),
063: new Integer(Types.NUMERIC), "java.math.BigDecimal" },
064: { "float4", new Integer(Oid.FLOAT4),
065: new Integer(Types.REAL), "java.lang.Float" },
066: { "float8", new Integer(Oid.FLOAT8),
067: new Integer(Types.DOUBLE), "java.lang.Double" },
068: { "char", new Integer(Oid.CHAR), new Integer(Types.CHAR),
069: "java.lang.String" },
070: { "bpchar", new Integer(Oid.BPCHAR),
071: new Integer(Types.CHAR), "java.lang.String" },
072: { "varchar", new Integer(Oid.VARCHAR),
073: new Integer(Types.VARCHAR), "java.lang.String" },
074: { "text", new Integer(Oid.TEXT),
075: new Integer(Types.VARCHAR), "java.lang.String" },
076: { "name", new Integer(Oid.NAME),
077: new Integer(Types.VARCHAR), "java.lang.String" },
078: { "bytea", new Integer(Oid.BYTEA),
079: new Integer(Types.BINARY), "[B" },
080: { "bool", new Integer(Oid.BOOL), new Integer(Types.BIT),
081: "java.lang.Boolean" },
082: { "bit", new Integer(Oid.BIT), new Integer(Types.BIT),
083: "java.lang.Boolean" },
084: { "date", new Integer(Oid.DATE), new Integer(Types.DATE),
085: "java.sql.Date" },
086: { "time", new Integer(Oid.TIME), new Integer(Types.TIME),
087: "java.sql.Time" },
088: { "timetz", new Integer(Oid.TIMETZ),
089: new Integer(Types.TIME), "java.sql.Time" },
090: { "timestamp", new Integer(Oid.TIMESTAMP),
091: new Integer(Types.TIMESTAMP), "java.sql.Timestamp" },
092: { "timestamptz", new Integer(Oid.TIMESTAMPTZ),
093: new Integer(Types.TIMESTAMP), "java.sql.Timestamp" } };
094:
095: static {
096: Map pgNameToSQLType = new HashMap();
097: for (int i = 0; i < types.length; i++) {
098: pgNameToSQLType.put(types[i][0], types[i][2]);
099: String arrayType = "_" + types[i][0];
100: pgNameToSQLType.put(arrayType, new Integer(Types.ARRAY));
101: }
102: // needs to be unmodifiable because the iterator is returned
103: // getPGTypeNamesWithSQLTypes() if the content of the map
104: // should ever need to be modified, this could be changed
105: // into a synchronizedMap()
106: _pgNameToSQLType = Collections.unmodifiableMap(pgNameToSQLType);
107: }
108:
109: public TypeInfoCache(BaseConnection conn) {
110: _conn = conn;
111: _oidToPgName = new HashMap();
112: _pgNameToOid = new HashMap();
113: _pgNameToJavaClass = new HashMap();
114: _pgNameToPgObject = new HashMap();
115:
116: for (int i = 0; i < types.length; i++) {
117: _pgNameToJavaClass.put(types[i][0], types[i][3]);
118: _pgNameToOid.put(types[i][0], types[i][1]);
119: _oidToPgName.put(types[i][1], types[i][0]);
120:
121: String arrayType = "_" + types[i][0];
122: _pgNameToJavaClass.put(arrayType, "java.sql.Array");
123: }
124: }
125:
126: public synchronized void addDataType(String type, Class klass)
127: throws SQLException {
128: if (!PGobject.class.isAssignableFrom(klass))
129: throw new PSQLException(
130: GT
131: .tr(
132: "The class {0} does not implement org.postgresql.util.PGobject.",
133: klass.toString()),
134: PSQLState.INVALID_PARAMETER_TYPE);
135:
136: _pgNameToPgObject.put(type, klass);
137: _pgNameToJavaClass.put(type, klass.getName());
138: }
139:
140: public static Iterator getPGTypeNamesWithSQLTypes() {
141: // the map is unmodfiable
142: return _pgNameToSQLType.keySet().iterator();
143: }
144:
145: // no need to synchronize because getSQLType uses an unmodfiable map
146: public int getSQLType(int oid) throws SQLException {
147: return getSQLType(getPGType(oid));
148: }
149:
150: public static int getSQLType(String pgTypeName) {
151: // map is unmodifiable, no need to synchronize
152: Integer i = (Integer) _pgNameToSQLType.get(pgTypeName);
153: if (i != null)
154: return i.intValue();
155: return Types.OTHER;
156: }
157:
158: public synchronized int getPGType(String pgTypeName)
159: throws SQLException {
160: Integer oid = (Integer) _pgNameToOid.get(pgTypeName);
161: if (oid != null)
162: return oid.intValue();
163:
164: String sql;
165: if (_conn.haveMinimumServerVersion("7.3")) {
166: sql = "SELECT oid FROM pg_catalog.pg_type WHERE typname = ?";
167: } else {
168: sql = "SELECT oid FROM pg_type WHERE typname = ?";
169: }
170: if (_getOidStatement == null)
171: _getOidStatement = _conn.prepareStatement(sql);
172:
173: _getOidStatement.setString(1, pgTypeName);
174:
175: // Go through BaseStatement to avoid transaction start.
176: if (!((BaseStatement) _getOidStatement)
177: .executeWithFlags(QueryExecutor.QUERY_SUPPRESS_BEGIN))
178: throw new PSQLException(GT
179: .tr("No results were returned by the query."),
180: PSQLState.NO_DATA);
181:
182: oid = new Integer(Oid.UNSPECIFIED);
183: ResultSet rs = _getOidStatement.getResultSet();
184: if (rs.next()) {
185: oid = new Integer(rs.getInt(1));
186: _oidToPgName.put(oid, pgTypeName);
187: }
188: _pgNameToOid.put(pgTypeName, oid);
189: rs.close();
190:
191: return oid.intValue();
192: }
193:
194: public synchronized String getPGType(int oid) throws SQLException {
195: if (oid == Oid.UNSPECIFIED)
196: return null;
197:
198: String pgTypeName = (String) _oidToPgName.get(new Integer(oid));
199: if (pgTypeName != null)
200: return pgTypeName;
201:
202: String sql;
203: if (_conn.haveMinimumServerVersion("7.3")) {
204: sql = "SELECT typname FROM pg_catalog.pg_type WHERE oid = ?";
205: } else {
206: sql = "SELECT typname FROM pg_type WHERE oid = ?";
207: }
208: if (_getNameStatement == null)
209: _getNameStatement = _conn.prepareStatement(sql);
210:
211: _getNameStatement.setInt(1, oid);
212:
213: // Go through BaseStatement to avoid transaction start.
214: if (!((BaseStatement) _getNameStatement)
215: .executeWithFlags(QueryExecutor.QUERY_SUPPRESS_BEGIN))
216: throw new PSQLException(GT
217: .tr("No results were returned by the query."),
218: PSQLState.NO_DATA);
219:
220: ResultSet rs = _getNameStatement.getResultSet();
221: if (rs.next()) {
222: pgTypeName = rs.getString(1);
223: _pgNameToOid.put(pgTypeName, new Integer(oid));
224: _oidToPgName.put(new Integer(oid), pgTypeName);
225: }
226: rs.close();
227:
228: return pgTypeName;
229: }
230:
231: public synchronized Class getPGobject(String type) {
232: return (Class) _pgNameToPgObject.get(type);
233: }
234:
235: public synchronized String getJavaClass(int oid)
236: throws SQLException {
237: String pgTypeName = getPGType(oid);
238: return (String) _pgNameToJavaClass.get(pgTypeName);
239: }
240:
241: public static int getPrecision(int oid, int typmod) {
242: switch (oid) {
243: case Oid.INT2:
244: return 5;
245:
246: case Oid.OID:
247: case Oid.INT4:
248: return 10;
249:
250: case Oid.INT8:
251: return 19;
252:
253: case Oid.FLOAT4:
254: // For float4 and float8, we can normally only get 6 and 15
255: // significant digits out, but extra_float_digits may raise
256: // that number by up to two digits.
257: return 8;
258:
259: case Oid.FLOAT8:
260: return 17;
261:
262: case Oid.NUMERIC:
263: if (typmod == -1)
264: return 0;
265: return ((typmod - 4) & 0xFFFF0000) >> 16;
266:
267: case Oid.CHAR:
268: case Oid.BOOL:
269: return 1;
270:
271: case Oid.BPCHAR:
272: case Oid.VARCHAR:
273: if (typmod == -1)
274: return 0;
275: return typmod - 4;
276:
277: // datetime types get the
278: // "length in characters of the String representation"
279: case Oid.DATE:
280: case Oid.TIME:
281: case Oid.TIMETZ:
282: case Oid.INTERVAL:
283: case Oid.TIMESTAMP:
284: case Oid.TIMESTAMPTZ:
285: return getDisplaySize(oid, typmod);
286:
287: case Oid.BIT:
288: return typmod;
289:
290: case Oid.VARBIT:
291: if (typmod == -1)
292: return 0;
293: return typmod;
294:
295: case Oid.TEXT:
296: case Oid.BYTEA:
297: default:
298: return 0;
299: }
300: }
301:
302: public static int getScale(int oid, int typmod) {
303: switch (oid) {
304: case Oid.FLOAT4:
305: return 8;
306: case Oid.FLOAT8:
307: return 17;
308: case Oid.NUMERIC:
309: if (typmod == -1)
310: return 0;
311: return (typmod - 4) & 0xFFFF;
312: case Oid.TIME:
313: case Oid.TIMETZ:
314: case Oid.TIMESTAMP:
315: case Oid.TIMESTAMPTZ:
316: if (typmod == -1)
317: return 6;
318: return typmod;
319: case Oid.INTERVAL:
320: if (typmod == -1)
321: return 6;
322: return typmod & 0xFFFF;
323: default:
324: return 0;
325: }
326: }
327:
328: public static boolean isCaseSensitive(int oid) {
329: switch (oid) {
330: case Oid.OID:
331: case Oid.INT2:
332: case Oid.INT4:
333: case Oid.INT8:
334: case Oid.FLOAT4:
335: case Oid.FLOAT8:
336: case Oid.NUMERIC:
337: case Oid.BOOL:
338: case Oid.BIT:
339: case Oid.VARBIT:
340: case Oid.DATE:
341: case Oid.TIME:
342: case Oid.TIMETZ:
343: case Oid.TIMESTAMP:
344: case Oid.TIMESTAMPTZ:
345: case Oid.INTERVAL:
346: return false;
347: default:
348: return true;
349: }
350: }
351:
352: public static boolean isSigned(int oid) {
353: switch (oid) {
354: case Oid.INT2:
355: case Oid.INT4:
356: case Oid.INT8:
357: case Oid.FLOAT4:
358: case Oid.FLOAT8:
359: case Oid.NUMERIC:
360: return true;
361: default:
362: return false;
363: }
364: }
365:
366: public static int getDisplaySize(int oid, int typmod) {
367: switch (oid) {
368: case Oid.INT2:
369: return 6; // -32768 to +32767
370: case Oid.INT4:
371: return 11; // -2147483648 to +2147483647
372: case Oid.OID:
373: return 10; // 0 to 4294967295
374: case Oid.INT8:
375: return 20; // -9223372036854775808 to +9223372036854775807
376: case Oid.FLOAT4:
377: // varies based up the extra_float_digits GUC.
378: return 14; // sign + 8 digits + decimal point + e + sign + 2 digits
379: case Oid.FLOAT8:
380: return 24; // sign + 17 digits + decimal point + e + sign + 3 digits
381: case Oid.CHAR:
382: return 1;
383: case Oid.BOOL:
384: return 1;
385: case Oid.DATE:
386: return 13; // "4713-01-01 BC" to "01/01/4713 BC" - "31/12/32767"
387: case Oid.TIME:
388: case Oid.TIMETZ:
389: case Oid.TIMESTAMP:
390: case Oid.TIMESTAMPTZ:
391: // Calculate the number of decimal digits + the decimal point.
392: int secondSize;
393: switch (typmod) {
394: case -1:
395: secondSize = 6 + 1;
396: break;
397: case 0:
398: secondSize = 0;
399: break;
400: case 1:
401: // Bizarrely SELECT '0:0:0.1'::time(1); returns 2 digits.
402: secondSize = 2 + 1;
403: break;
404: default:
405: secondSize = typmod + 1;
406: break;
407: }
408:
409: // We assume the worst case scenario for all of these.
410: // time = '00:00:00' = 8
411: // date = '5874897-12-31' = 13 (although at large values second precision is lost)
412: // date = '294276-11-20' = 12 --enable-integer-datetimes
413: // zone = '+11:30' = 6;
414:
415: switch (oid) {
416: case Oid.TIME:
417: return 8 + secondSize;
418: case Oid.TIMETZ:
419: return 8 + secondSize + 6;
420: case Oid.TIMESTAMP:
421: return 13 + 1 + 8 + secondSize;
422: case Oid.TIMESTAMPTZ:
423: return 13 + 1 + 8 + secondSize + 6;
424: }
425: case Oid.INTERVAL:
426: return 49; // SELECT LENGTH('-123456789 years 11 months 33 days 23 hours 10.123456 seconds'::interval);
427: case Oid.VARCHAR:
428: case Oid.BPCHAR:
429: if (typmod == -1)
430: return Integer.MAX_VALUE;
431: return typmod - 4;
432: case Oid.NUMERIC:
433: if (typmod == -1)
434: return 131089; // SELECT LENGTH(pow(10::numeric,131071)); 131071 = 2^17-1
435: int precision = (typmod - 4 >> 16) & 0xffff;
436: int scale = (typmod - 4) & 0xffff;
437: // sign + digits + decimal point (only if we have nonzero scale)
438: return 1 + precision + (scale != 0 ? 1 : 0);
439: case Oid.BIT:
440: return typmod;
441: case Oid.VARBIT:
442: if (typmod == -1)
443: return Integer.MAX_VALUE;
444: return typmod;
445: case Oid.TEXT:
446: case Oid.BYTEA:
447: return Integer.MAX_VALUE;
448: default:
449: return Integer.MAX_VALUE;
450: }
451: }
452:
453: public static int getMaximumPrecision(int oid) {
454: switch (oid) {
455: case Oid.NUMERIC:
456: return 1000;
457: case Oid.TIME:
458: case Oid.TIMETZ:
459: // Technically this depends on the --enable-integer-datetimes
460: // configure setting. It is 6 with integer and 10 with float.
461: return 6;
462: case Oid.TIMESTAMP:
463: case Oid.TIMESTAMPTZ:
464: case Oid.INTERVAL:
465: return 6;
466: case Oid.BPCHAR:
467: case Oid.VARCHAR:
468: return 10485760;
469: case Oid.BIT:
470: case Oid.VARBIT:
471: return 83886080;
472: default:
473: return 0;
474: }
475: }
476:
477: }
|