|
| 1 | +packageorg.postgresql.fastpath; |
| 2 | + |
| 3 | +importjava.io.*; |
| 4 | +importjava.lang.*; |
| 5 | +importjava.net.*; |
| 6 | +importjava.util.*; |
| 7 | +importjava.sql.*; |
| 8 | +importorg.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 | +publicclassFastpath |
| 38 | +{ |
| 39 | +// This maps the functions names to their id's (possible unique just |
| 40 | +// to a connection). |
| 41 | +protectedHashtablefunc =newHashtable(); |
| 42 | + |
| 43 | +protectedorg.postgresql.Connectionconn;// our connection |
| 44 | +protectedorg.postgresql.PG_Streamstream;// 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 | +publicFastpath(org.postgresql.Connectionconn,org.postgresql.PG_Streamstream) |
| 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 | +publicObjectfastpath(intfnid,booleanresulttype,FastpathArg[]args)throwsSQLException |
| 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(inti=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(IOExceptionioe) { |
| 96 | +thrownewPSQLException("postgresql.fp.send",newInteger(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 | +Objectresult =null;// our result |
| 113 | +while(true) { |
| 114 | +intin =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 | +intsz =stream.ReceiveIntegerR(4); |
| 126 | +//DriverManager.println("G: size="+sz); //debug |
| 127 | + |
| 128 | +// Return an Integer if |
| 129 | +if(resulttype) |
| 130 | +result =newInteger(stream.ReceiveIntegerR(sz)); |
| 131 | +else { |
| 132 | +bytebuf[] =newbyte[sz]; |
| 133 | +stream.Receive(buf,0,sz); |
| 134 | +result =buf; |
| 135 | + } |
| 136 | +break; |
| 137 | + |
| 138 | +//------------------------------ |
| 139 | +// Error message returned |
| 140 | +case'E': |
| 141 | +thrownewPSQLException("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 | +returnresult; |
| 157 | + |
| 158 | +case'Z': |
| 159 | +break; |
| 160 | + |
| 161 | +default: |
| 162 | +thrownewPSQLException("postgresql.fp.protocol",newCharacter((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 | +publicObjectfastpath(Stringname,booleanresulttype,FastpathArg[]args)throwsSQLException |
| 189 | + { |
| 190 | +//DriverManager.println("Fastpath: calling "+name); |
| 191 | +returnfastpath(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 | +publicintgetInteger(Stringname,FastpathArg[]args)throwsSQLException |
| 202 | + { |
| 203 | +Integeri = (Integer)fastpath(name,true,args); |
| 204 | +if(i==null) |
| 205 | +thrownewPSQLException("postgresql.fp.expint",name); |
| 206 | +returni.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 | +publicbyte[]getData(Stringname,FastpathArg[]args)throwsSQLException |
| 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 | +publicvoidaddFunction(Stringname,intfnid) |
| 233 | + { |
| 234 | +func.put(name,newInteger(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 | +publicvoidaddFunctions(ResultSetrs)throwsSQLException |
| 270 | + { |
| 271 | +while(rs.next()) { |
| 272 | +func.put(rs.getString(1),newInteger(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 | +publicintgetID(Stringname)throwsSQLException |
| 287 | + { |
| 288 | +Integerid = (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 | +thrownewPSQLException("postgresql.fp.unknown",name); |
| 299 | + |
| 300 | +returnid.intValue(); |
| 301 | + } |
| 302 | +} |
| 303 | + |