001: /*-------------------------------------------------------------------------
002: *
003: * Copyright (c) 2003-2005, PostgreSQL Global Development Group
004: *
005: * IDENTIFICATION
006: * $PostgreSQL: pgjdbc/org/postgresql/fastpath/Fastpath.java,v 1.34 2007/02/19 06:00:24 jurka Exp $
007: *
008: *-------------------------------------------------------------------------
009: */
010: package org.postgresql.fastpath;
011:
012: import java.sql.SQLException;
013: import java.sql.ResultSet;
014: import java.util.Hashtable;
015: import org.postgresql.core.BaseConnection;
016: import org.postgresql.core.QueryExecutor;
017: import org.postgresql.core.ParameterList;
018: import org.postgresql.util.PSQLException;
019: import org.postgresql.util.PSQLState;
020: import org.postgresql.util.GT;
021:
022: /**
023: * This class implements the Fastpath api.
024: *
025: * <p>This is a means of executing functions imbeded in the org.postgresql
026: * backend from within a java application.
027: *
028: * <p>It is based around the file src/interfaces/libpq/fe-exec.c
029: *
030: */
031: public class Fastpath {
032: // Java passes oids around as longs, but in the backend
033: // it's an unsigned int, so we use this to make the conversion
034: // of long -> signed int which the backend interprets as unsigned.
035: private final static long NUM_OIDS = 4294967296L; // 2^32
036:
037: // This maps the functions names to their id's (possible unique just
038: // to a connection).
039: private final Hashtable func = new Hashtable();
040: private final QueryExecutor executor;
041: private final BaseConnection connection;
042:
043: /**
044: * Initialises the fastpath system
045: *
046: * @param conn BaseConnection to attach to
047: */
048: public Fastpath(BaseConnection conn) {
049: this .connection = conn;
050: this .executor = conn.getQueryExecutor();
051: }
052:
053: /**
054: * Send a function call to the PostgreSQL backend
055: *
056: * @param fnId Function id
057: * @param resultType True if the result is an integer, false for other results
058: * @param args FastpathArguments to pass to fastpath
059: * @return null if no data, Integer if an integer result, or byte[] otherwise
060: * @exception SQLException if a database-access error occurs.
061: */
062: public Object fastpath(int fnId, boolean resultType,
063: FastpathArg[] args) throws SQLException {
064: // Turn fastpath array into a parameter list.
065: ParameterList params = executor
066: .createFastpathParameters(args.length);
067: for (int i = 0; i < args.length; ++i) {
068: args[i].populateParameter(params, i + 1);
069: }
070:
071: // Run it.
072: byte[] returnValue = executor.fastpathCall(fnId, params,
073: connection.getAutoCommit());
074:
075: // Interpret results.
076: if (!resultType || returnValue == null)
077: return returnValue;
078:
079: if (returnValue.length != 4)
080: throw new PSQLException(
081: GT
082: .tr(
083: "Fastpath call {0} - No result was returned and we expected an integer.",
084: new Integer(fnId)),
085: PSQLState.NO_DATA);
086:
087: return new Integer((returnValue[3] & 255)
088: | ((returnValue[2] & 255) << 8)
089: | ((returnValue[1] & 255) << 16)
090: | ((returnValue[0] & 255) << 24));
091: }
092:
093: /**
094: * Send a function call to the PostgreSQL backend by name.
095: *
096: * Note: the mapping for the procedure name to function id needs to exist,
097: * usually to an earlier call to addfunction().
098: *
099: * This is the prefered method to call, as function id's can/may change
100: * between versions of the backend.
101: *
102: * For an example of how this works, refer to org.postgresql.largeobject.LargeObject
103: *
104: * @param name Function name
105: * @param resulttype True if the result is an integer, false for other
106: * results
107: * @param args FastpathArguments to pass to fastpath
108: * @return null if no data, Integer if an integer result, or byte[] otherwise
109: * @exception SQLException if name is unknown or if a database-access error
110: * occurs.
111: * @see org.postgresql.largeobject.LargeObject
112: */
113: public Object fastpath(String name, boolean resulttype,
114: FastpathArg[] args) throws SQLException {
115: if (connection.getLogger().logDebug())
116: connection.getLogger().debug("Fastpath: calling " + name);
117: return fastpath(getID(name), resulttype, args);
118: }
119:
120: /**
121: * This convenience method assumes that the return value is an Integer
122: * @param name Function name
123: * @param args Function arguments
124: * @return integer result
125: * @exception SQLException if a database-access error occurs or no result
126: */
127: public int getInteger(String name, FastpathArg[] args)
128: throws SQLException {
129: Integer i = (Integer) fastpath(name, true, args);
130: if (i == null)
131: throw new PSQLException(
132: GT
133: .tr(
134: "Fastpath call {0} - No result was returned and we expected an integer.",
135: name), PSQLState.NO_DATA);
136: return i.intValue();
137: }
138:
139: /**
140: * This convenience method assumes that the return value is an oid.
141: * @param name Function name
142: * @param args Function arguments
143: * @exception SQLException if a database-access error occurs or no result
144: */
145: public long getOID(String name, FastpathArg[] args)
146: throws SQLException {
147: long oid = getInteger(name, args);
148: if (oid < 0)
149: oid += NUM_OIDS;
150: return oid;
151: }
152:
153: /**
154: * This convenience method assumes that the return value is not an Integer
155: * @param name Function name
156: * @param args Function arguments
157: * @return byte[] array containing result
158: * @exception SQLException if a database-access error occurs or no result
159: */
160: public byte[] getData(String name, FastpathArg[] args)
161: throws SQLException {
162: return (byte[]) fastpath(name, false, args);
163: }
164:
165: /**
166: * This adds a function to our lookup table.
167: *
168: * <p>User code should use the addFunctions method, which is based upon a
169: * query, rather than hard coding the oid. The oid for a function is not
170: * guaranteed to remain static, even on different servers of the same
171: * version.
172: *
173: * @param name Function name
174: * @param fnid Function id
175: */
176: public void addFunction(String name, int fnid) {
177: func.put(name, new Integer(fnid));
178: }
179:
180: /**
181: * This takes a ResultSet containing two columns. Column 1 contains the
182: * function name, Column 2 the oid.
183: *
184: * <p>It reads the entire ResultSet, loading the values into the function
185: * table.
186: *
187: * <p><b>REMEMBER</b> to close() the resultset after calling this!!
188: *
189: * <p><b><em>Implementation note about function name lookups:</em></b>
190: *
191: * <p>PostgreSQL stores the function id's and their corresponding names in
192: * the pg_proc table. To speed things up locally, instead of querying each
193: * function from that table when required, a Hashtable is used. Also, only
194: * the function's required are entered into this table, keeping connection
195: * times as fast as possible.
196: *
197: * <p>The org.postgresql.largeobject.LargeObject class performs a query upon it's startup,
198: * and passes the returned ResultSet to the addFunctions() method here.
199: *
200: * <p>Once this has been done, the LargeObject api refers to the functions by
201: * name.
202: *
203: * <p>Dont think that manually converting them to the oid's will work. Ok,
204: * they will for now, but they can change during development (there was some
205: * discussion about this for V7.0), so this is implemented to prevent any
206: * unwarranted headaches in the future.
207: *
208: * @param rs ResultSet
209: * @exception SQLException if a database-access error occurs.
210: * @see org.postgresql.largeobject.LargeObjectManager
211: */
212: public void addFunctions(ResultSet rs) throws SQLException {
213: while (rs.next()) {
214: func.put(rs.getString(1), new Integer(rs.getInt(2)));
215: }
216: }
217:
218: /**
219: * This returns the function id associated by its name.
220: *
221: * <p>If addFunction() or addFunctions() have not been called for this name,
222: * then an SQLException is thrown.
223: *
224: * @param name Function name to lookup
225: * @return Function ID for fastpath call
226: * @exception SQLException is function is unknown.
227: */
228: public int getID(String name) throws SQLException {
229: Integer id = (Integer) func.get(name);
230:
231: // may be we could add a lookup to the database here, and store the result
232: // in our lookup table, throwing the exception if that fails.
233: // We must, however, ensure that if we do, any existing ResultSet is
234: // unaffected, otherwise we could break user code.
235: //
236: // so, until we know we can do this (needs testing, on the TODO list)
237: // for now, we throw the exception and do no lookups.
238: if (id == null)
239: throw new PSQLException(GT.tr(
240: "The fastpath function {0} is unknown.", name),
241: PSQLState.UNEXPECTED_ERROR);
242:
243: return id.intValue();
244: }
245:
246: /**
247: * Creates a FastpathArg with an oid parameter.
248: * This is here instead of a constructor of FastpathArg
249: * because the constructor can't tell the difference between
250: * an long that's really int8 and a long thats an oid.
251: */
252: public static FastpathArg createOIDArg(long oid) {
253: if (oid > Integer.MAX_VALUE)
254: oid -= NUM_OIDS;
255: return new FastpathArg((int) oid);
256: }
257:
258: }
|