forked from pgjdbc/pgjdbc
/
LargeObject.java
418 lines (383 loc) · 12.9 KB
/
LargeObject.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
/*
* Copyright (c) 2003, PostgreSQL Global Development Group
* See the LICENSE file in the project root for more information.
*/
package org.postgresql.largeobject;
import org.postgresql.core.BaseConnection;
import org.postgresql.fastpath.Fastpath;
import org.postgresql.fastpath.FastpathArg;
import org.postgresql.util.PSQLException;
import org.postgresql.util.PSQLState;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.sql.SQLException;
/**
* This class provides the basic methods required to run the interface, plus a pair of methods that
* provide InputStream and OutputStream classes for this object.
*
* <p>
* Normally, client code would use the getAsciiStream, getBinaryStream, or getUnicodeStream methods
* in ResultSet, or setAsciiStream, setBinaryStream, or setUnicodeStream methods in
* PreparedStatement to access Large Objects.
*
* <p>
* However, sometimes lower level access to Large Objects are required, that are not supported by
* the JDBC specification.
*
* <p>
* Refer to org.postgresql.largeobject.LargeObjectManager on how to gain access to a Large Object,
* or how to create one.
*
* @see org.postgresql.largeobject.LargeObjectManager
* @see java.sql.ResultSet#getAsciiStream
* @see java.sql.ResultSet#getBinaryStream
* @see java.sql.ResultSet#getUnicodeStream
* @see java.sql.PreparedStatement#setAsciiStream
* @see java.sql.PreparedStatement#setBinaryStream
* @see java.sql.PreparedStatement#setUnicodeStream
*/
public class LargeObject
//#if mvn.project.property.postgresql.jdbc.spec >= "JDBC4.1"
implements AutoCloseable
//#endif
/* hi, checkstyle */ {
/**
* Indicates a seek from the begining of a file
*/
public static final int SEEK_SET = 0;
/**
* Indicates a seek from the current position
*/
public static final int SEEK_CUR = 1;
/**
* Indicates a seek from the end of a file
*/
public static final int SEEK_END = 2;
private final Fastpath fp; // Fastpath API to use
private final long oid; // OID of this object
private final int mode; // read/write mode of this object
private final int fd; // the descriptor of the open large object
private BlobOutputStream os; // The current output stream
private boolean closed = false; // true when we are closed
private BaseConnection conn; // Only initialized when open a LOB with CommitOnClose
private boolean commitOnClose; // Only initialized when open a LOB with CommitOnClose
/**
* This opens a large object.
*
* <p>
* If the object does not exist, then an SQLException is thrown.
*
* @param fp FastPath API for the connection to use
* @param oid of the Large Object to open
* @param mode Mode of opening the large object
* @param conn the connection to the database used to access this LOB
* @param commitOnClose commit the transaction when this LOB will be closed (defined in
* LargeObjectManager)
* @throws SQLException if a database-access error occurs.
* @see org.postgresql.largeobject.LargeObjectManager
*/
protected LargeObject(Fastpath fp, long oid, int mode, BaseConnection conn, boolean commitOnClose)
throws SQLException {
this.fp = fp;
this.oid = oid;
this.mode = mode;
if (commitOnClose) {
this.commitOnClose = true;
this.conn = conn;
} else {
this.commitOnClose = false;
}
FastpathArg[] args = new FastpathArg[2];
args[0] = Fastpath.createOIDArg(oid);
args[1] = new FastpathArg(mode);
this.fd = fp.getInteger("lo_open", args);
}
/**
* This opens a large object.
*
* <p>
* If the object does not exist, then an SQLException is thrown.
*
* @param fp FastPath API for the connection to use
* @param oid of the Large Object to open
* @param mode Mode of opening the large object (defined in LargeObjectManager)
* @throws SQLException if a database-access error occurs.
* @see org.postgresql.largeobject.LargeObjectManager
*/
protected LargeObject(Fastpath fp, long oid, int mode) throws SQLException {
this(fp, oid, mode, null, false);
}
public LargeObject copy() throws SQLException {
return new LargeObject(fp, oid, mode);
}
/*
* Release large object resources during garbage cleanup.
*
* This code used to call close() however that was problematic because the scope of the fd is a
* transaction, thus if commit or rollback was called before garbage collection ran then the call
* to close would error out with an invalid large object handle. So this method now does nothing
* and lets the server handle cleanup when it ends the transaction.
*
* protected void finalize() throws SQLException { }
*/
/**
* @return the OID of this LargeObject
* @deprecated As of 8.3, replaced by {@link #getLongOID()}
*/
@Deprecated
public int getOID() {
return (int) oid;
}
/**
* @return the OID of this LargeObject
*/
public long getLongOID() {
return oid;
}
/**
* This method closes the object. You must not call methods in this object after this is called.
*
* @throws SQLException if a database-access error occurs.
*/
public void close() throws SQLException {
if (!closed) {
// flush any open output streams
if (os != null) {
try {
// we can't call os.close() otherwise we go into an infinite loop!
os.flush();
} catch (IOException ioe) {
throw new PSQLException("Exception flushing output stream", PSQLState.DATA_ERROR, ioe);
} finally {
os = null;
}
}
// finally close
FastpathArg[] args = new FastpathArg[1];
args[0] = new FastpathArg(fd);
fp.fastpath("lo_close", args); // true here as we dont care!!
closed = true;
if (this.commitOnClose) {
this.conn.commit();
}
}
}
/**
* Reads some data from the object, and return as a byte[] array
*
* @param len number of bytes to read
* @return byte[] array containing data read
* @throws SQLException if a database-access error occurs.
*/
public byte[] read(int len) throws SQLException {
// This is the original method, where the entire block (len bytes)
// is retrieved in one go.
FastpathArg[] args = new FastpathArg[2];
args[0] = new FastpathArg(fd);
args[1] = new FastpathArg(len);
return fp.getData("loread", args);
}
/**
* Reads some data from the object into an existing array
*
* @param buf destination array
* @param off offset within array
* @param len number of bytes to read
* @return the number of bytes actually read
* @throws SQLException if a database-access error occurs.
*/
public int read(byte[] buf, int off, int len) throws SQLException {
byte[] b = read(len);
if (b.length < len) {
len = b.length;
}
System.arraycopy(b, 0, buf, off, len);
return len;
}
/**
* Writes an array to the object
*
* @param buf array to write
* @throws SQLException if a database-access error occurs.
*/
public void write(byte[] buf) throws SQLException {
FastpathArg[] args = new FastpathArg[2];
args[0] = new FastpathArg(fd);
args[1] = new FastpathArg(buf);
fp.fastpath("lowrite", args);
}
/**
* Writes some data from an array to the object
*
* @param buf destination array
* @param off offset within array
* @param len number of bytes to write
* @throws SQLException if a database-access error occurs.
*/
public void write(byte[] buf, int off, int len) throws SQLException {
FastpathArg[] args = new FastpathArg[2];
args[0] = new FastpathArg(fd);
args[1] = new FastpathArg(buf, off, len);
fp.fastpath("lowrite", args);
}
/**
* Sets the current position within the object.
*
* <p>
* This is similar to the fseek() call in the standard C library. It allows you to have random
* access to the large object.
*
* @param pos position within object
* @param ref Either SEEK_SET, SEEK_CUR or SEEK_END
* @throws SQLException if a database-access error occurs.
*/
public void seek(int pos, int ref) throws SQLException {
FastpathArg[] args = new FastpathArg[3];
args[0] = new FastpathArg(fd);
args[1] = new FastpathArg(pos);
args[2] = new FastpathArg(ref);
fp.fastpath("lo_lseek", args);
}
/**
* Sets the current position within the object using 64-bit value (9.3+)
*
* @param pos position within object
* @param ref Either SEEK_SET, SEEK_CUR or SEEK_END
* @throws SQLException if a database-access error occurs.
*/
public void seek64(long pos, int ref) throws SQLException {
FastpathArg[] args = new FastpathArg[3];
args[0] = new FastpathArg(fd);
args[1] = new FastpathArg(pos);
args[2] = new FastpathArg(ref);
fp.fastpath("lo_lseek64", args);
}
/**
* Sets the current position within the object.
*
* <p>
* This is similar to the fseek() call in the standard C library. It allows you to have random
* access to the large object.
*
* @param pos position within object from begining
* @throws SQLException if a database-access error occurs.
*/
public void seek(int pos) throws SQLException {
seek(pos, SEEK_SET);
}
/**
* @return the current position within the object
* @throws SQLException if a database-access error occurs.
*/
public int tell() throws SQLException {
FastpathArg[] args = new FastpathArg[1];
args[0] = new FastpathArg(fd);
return fp.getInteger("lo_tell", args);
}
/**
* @return the current position within the object
* @throws SQLException if a database-access error occurs.
*/
public long tell64() throws SQLException {
FastpathArg[] args = new FastpathArg[1];
args[0] = new FastpathArg(fd);
return fp.getLong("lo_tell64", args);
}
/**
* This method is inefficient, as the only way to find out the size of the object is to seek to
* the end, record the current position, then return to the original position.
*
* <p>
* A better method will be found in the future.
*
* @return the size of the large object
* @throws SQLException if a database-access error occurs.
*/
public int size() throws SQLException {
int cp = tell();
seek(0, SEEK_END);
int sz = tell();
seek(cp, SEEK_SET);
return sz;
}
/**
* See #size() for information about efficiency.
*
* @return the size of the large object
* @throws SQLException if a database-access error occurs.
*/
public long size64() throws SQLException {
long cp = tell64();
seek64(0, SEEK_END);
long sz = tell64();
seek64(cp, SEEK_SET);
return sz;
}
/**
* Truncates the large object to the given length in bytes. If the number of bytes is larger than
* the current large object length, the large object will be filled with zero bytes. This method
* does not modify the current file offset.
*
* @param len given length in bytes
* @throws SQLException if something goes wrong
*/
public void truncate(int len) throws SQLException {
FastpathArg[] args = new FastpathArg[2];
args[0] = new FastpathArg(fd);
args[1] = new FastpathArg(len);
fp.getInteger("lo_truncate", args);
}
/**
* Truncates the large object to the given length in bytes. If the number of bytes is larger than
* the current large object length, the large object will be filled with zero bytes. This method
* does not modify the current file offset.
*
* @param len given length in bytes
* @throws SQLException if something goes wrong
*/
public void truncate64(long len) throws SQLException {
FastpathArg[] args = new FastpathArg[2];
args[0] = new FastpathArg(fd);
args[1] = new FastpathArg(len);
fp.getInteger("lo_truncate64", args);
}
/**
* Returns an {@link InputStream} from this object.
*
* <p>
* This {@link InputStream} can then be used in any method that requires an InputStream.
*
* @return {@link InputStream} from this object
* @throws SQLException if a database-access error occurs.
*/
public InputStream getInputStream() throws SQLException {
return new BlobInputStream(this, 4096);
}
/**
* Returns an {@link InputStream} from this object, that will limit the amount of data that is
* visible
*
* @param limit maximum number of bytes the resulting stream will serve
* @return {@link InputStream} from this object
* @throws SQLException if a database-access error occurs.
*/
public InputStream getInputStream(long limit) throws SQLException {
return new BlobInputStream(this, 4096, limit);
}
/**
* Returns an {@link OutputStream} to this object.
*
* <p>
* This OutputStream can then be used in any method that requires an OutputStream.
*
* @return {@link OutputStream} from this object
* @throws SQLException if a database-access error occurs.
*/
public OutputStream getOutputStream() throws SQLException {
if (os == null) {
os = new BlobOutputStream(this, 4096);
}
return os;
}
}