Source code: org/postgresql/fastpath/Fastpath.java
1 package org.postgresql.fastpath;
2
3 import java.io.*;
4 import java.lang.*;
5 import java.net.*;
6 import java.util.*;
7 import java.sql.*;
8 import org.postgresql.util.*;
9
10 // Important: There are a lot of debug code commented out. Please do not
11 // delete these.
12
13 /**
14 * This class implements the Fastpath api.
15 *
16 * <p>This is a means of executing functions imbeded in the org.postgresql backend
17 * from within a java application.
18 *
19 * <p>It is based around the file src/interfaces/libpq/fe-exec.c
20 *
21 *
22 * <p><b>Implementation notes:</b>
23 *
24 * <p><b><em>Network protocol:</em></b>
25 *
26 * <p>The code within the backend reads integers in reverse.
27 *
28 * <p>There is work in progress to convert all of the protocol to
29 * network order but it may not be there for v6.3
30 *
31 * <p>When fastpath switches, simply replace SendIntegerReverse() with
32 * SendInteger()
33 *
34 * @see org.postgresql.FastpathFastpathArg
35 * @see org.postgresql.LargeObject
36 */
37 public class Fastpath
38 {
39 // This maps the functions names to their id's (possible unique just
40 // to a connection).
41 protected Hashtable func = new Hashtable();
42
43 protected org.postgresql.Connection conn; // our connection
44 protected org.postgresql.PG_Stream stream; // the network stream
45
46 /**
47 * Initialises the fastpath system
48 *
49 * <p><b>Important Notice</b>
50 * <br>This is called from org.postgresql.Connection, and should not be called
51 * from client code.
52 *
53 * @param conn org.postgresql.Connection to attach to
54 * @param stream The network stream to the backend
55 */
56 public Fastpath(org.postgresql.Connection conn,org.postgresql.PG_Stream stream)
57 {
58 this.conn=conn;
59 this.stream=stream;
60 //DriverManager.println("Fastpath initialised");
61 }
62
63 /**
64 * Send a function call to the PostgreSQL backend
65 *
66 * @param fnid Function id
67 * @param resulttype True if the result is an integer, false for other results
68 * @param args FastpathArguments to pass to fastpath
69 * @return null if no data, Integer if an integer result, or byte[] otherwise
70 * @exception SQLException if a database-access error occurs.
71 */
72 public Object fastpath(int fnid,boolean resulttype,FastpathArg[] args) throws SQLException
73 {
74 // added Oct 7 1998 to give us thread safety
75 synchronized(stream) {
76
77 // send the function call
78 try {
79 // 70 is 'F' in ASCII. Note: don't use SendChar() here as it adds padding
80 // that confuses the backend. The 0 terminates the command line.
81 stream.SendInteger(70,1);
82 stream.SendInteger(0,1);
83
84 //stream.SendIntegerReverse(fnid,4);
85 //stream.SendIntegerReverse(args.length,4);
86 stream.SendInteger(fnid,4);
87 stream.SendInteger(args.length,4);
88
89 for(int i=0;i<args.length;i++)
90 args[i].send(stream);
91
92 // This is needed, otherwise data can be lost
93 stream.flush();
94
95 } catch(IOException ioe) {
96 throw new PSQLException("postgresql.fp.send",new Integer(fnid),ioe);
97 }
98
99 // Now handle the result
100
101 // We should get 'V' on sucess or 'E' on error. Anything else is treated
102 // as an error.
103 //int in = stream.ReceiveChar();
104 //DriverManager.println("ReceiveChar() = "+in+" '"+((char)in)+"'");
105 //if(in!='V') {
106 //if(in=='E')
107 //throw new SQLException(stream.ReceiveString(4096));
108 //throw new SQLException("Fastpath: expected 'V' from backend, got "+((char)in));
109 //}
110
111 // Now loop, reading the results
112 Object result = null; // our result
113 while(true) {
114 int in = stream.ReceiveChar();
115 //DriverManager.println("ReceiveChar() = "+in+" '"+((char)in)+"'");
116 switch(in)
117 {
118 case 'V':
119 break;
120
121 //------------------------------
122 // Function returned properly
123 //
124 case 'G':
125 int sz = stream.ReceiveIntegerR(4);
126 //DriverManager.println("G: size="+sz); //debug
127
128 // Return an Integer if
129 if(resulttype)
130 result = new Integer(stream.ReceiveIntegerR(sz));
131 else {
132 byte buf[] = new byte[sz];
133 stream.Receive(buf,0,sz);
134 result = buf;
135 }
136 break;
137
138 //------------------------------
139 // Error message returned
140 case 'E':
141 throw new PSQLException("postgresql.fp.error",stream.ReceiveString(4096));
142
143 //------------------------------
144 // Notice from backend
145 case 'N':
146 conn.addWarning(stream.ReceiveString(4096));
147 break;
148
149 //------------------------------
150 // End of results
151 //
152 // Here we simply return res, which would contain the result
153 // processed earlier. If no result, this already contains null
154 case '0':
155 //DriverManager.println("returning "+result);
156 return result;
157
158 case 'Z':
159 break;
160
161 default:
162 throw new PSQLException("postgresql.fp.protocol",new Character((char)in));
163 }
164 }
165 }
166 }
167
168 /**
169 * Send a function call to the PostgreSQL backend by name.
170 *
171 * Note: the mapping for the procedure name to function id needs to exist,
172 * usually to an earlier call to addfunction().
173 *
174 * This is the prefered method to call, as function id's can/may change
175 * between versions of the backend.
176 *
177 * For an example of how this works, refer to org.postgresql.LargeObject
178 *
179 * @param name Function name
180 * @param resulttype True if the result is an integer, false for other
181 * results
182 * @param args FastpathArguments to pass to fastpath
183 * @return null if no data, Integer if an integer result, or byte[] otherwise
184 * @exception SQLException if name is unknown or if a database-access error
185 * occurs.
186 * @see org.postgresql.LargeObject
187 */
188 public Object fastpath(String name,boolean resulttype,FastpathArg[] args) throws SQLException
189 {
190 //DriverManager.println("Fastpath: calling "+name);
191 return fastpath(getID(name),resulttype,args);
192 }
193
194 /**
195 * This convenience method assumes that the return value is an Integer
196 * @param name Function name
197 * @param args Function arguments
198 * @return integer result
199 * @exception SQLException if a database-access error occurs or no result
200 */
201 public int getInteger(String name,FastpathArg[] args) throws SQLException
202 {
203 Integer i = (Integer)fastpath(name,true,args);
204 if(i==null)
205 throw new PSQLException("postgresql.fp.expint",name);
206 return i.intValue();
207 }
208
209 /**
210 * This convenience method assumes that the return value is an Integer
211 * @param name Function name
212 * @param args Function arguments
213 * @return byte[] array containing result
214 * @exception SQLException if a database-access error occurs or no result
215 */
216 public byte[] getData(String name,FastpathArg[] args) throws SQLException
217 {
218 return (byte[])fastpath(name,false,args);
219 }
220
221 /**
222 * This adds a function to our lookup table.
223 *
224 * <p>User code should use the addFunctions method, which is based upon a
225 * query, rather than hard coding the oid. The oid for a function is not
226 * guaranteed to remain static, even on different servers of the same
227 * version.
228 *
229 * @param name Function name
230 * @param fnid Function id
231 */
232 public void addFunction(String name,int fnid)
233 {
234 func.put(name,new Integer(fnid));
235 }
236
237 /**
238 * This takes a ResultSet containing two columns. Column 1 contains the
239 * function name, Column 2 the oid.
240 *
241 * <p>It reads the entire ResultSet, loading the values into the function
242 * table.
243 *
244 * <p><b>REMEMBER</b> to close() the resultset after calling this!!
245 *
246 * <p><b><em>Implementation note about function name lookups:</em></b>
247 *
248 * <p>PostgreSQL stores the function id's and their corresponding names in
249 * the pg_proc table. To speed things up locally, instead of querying each
250 * function from that table when required, a Hashtable is used. Also, only
251 * the function's required are entered into this table, keeping connection
252 * times as fast as possible.
253 *
254 * <p>The org.postgresql.LargeObject class performs a query upon it's startup,
255 * and passes the returned ResultSet to the addFunctions() method here.
256 *
257 * <p>Once this has been done, the LargeObject api refers to the functions by
258 * name.
259 *
260 * <p>Dont think that manually converting them to the oid's will work. Ok,
261 * they will for now, but they can change during development (there was some
262 * discussion about this for V7.0), so this is implemented to prevent any
263 * unwarranted headaches in the future.
264 *
265 * @param rs ResultSet
266 * @exception SQLException if a database-access error occurs.
267 * @see org.postgresql.LargeObjectManager
268 */
269 public void addFunctions(ResultSet rs) throws SQLException
270 {
271 while(rs.next()) {
272 func.put(rs.getString(1),new Integer(rs.getInt(2)));
273 }
274 }
275
276 /**
277 * This returns the function id associated by its name
278 *
279 * <p>If addFunction() or addFunctions() have not been called for this name,
280 * then an SQLException is thrown.
281 *
282 * @param name Function name to lookup
283 * @return Function ID for fastpath call
284 * @exception SQLException is function is unknown.
285 */
286 public int getID(String name) throws SQLException
287 {
288 Integer id = (Integer)func.get(name);
289
290 // may be we could add a lookup to the database here, and store the result
291 // in our lookup table, throwing the exception if that fails.
292 // We must, however, ensure that if we do, any existing ResultSet is
293 // unaffected, otherwise we could break user code.
294 //
295 // so, until we know we can do this (needs testing, on the TODO list)
296 // for now, we throw the exception and do no lookups.
297 if(id==null)
298 throw new PSQLException("postgresql.fp.unknown",name);
299
300 return id.intValue();
301 }
302 }
303