1 | /* |
2 | * Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0, |
3 | * and the EPL 1.0 (http://h2database.com/html/license.html). |
4 | * Initial Developer: H2 Group |
5 | */ |
6 | package org.h2.value; |
7 | |
8 | import java.io.BufferedInputStream; |
9 | import java.io.ByteArrayInputStream; |
10 | import java.io.File; |
11 | import java.io.IOException; |
12 | import java.io.InputStream; |
13 | import java.io.Reader; |
14 | import java.sql.PreparedStatement; |
15 | import java.sql.SQLException; |
16 | |
17 | import org.h2.engine.Constants; |
18 | import org.h2.engine.SysProperties; |
19 | import org.h2.message.DbException; |
20 | import org.h2.mvstore.DataUtils; |
21 | import org.h2.store.DataHandler; |
22 | import org.h2.store.FileStore; |
23 | import org.h2.store.FileStoreInputStream; |
24 | import org.h2.store.FileStoreOutputStream; |
25 | import org.h2.store.fs.FileUtils; |
26 | import org.h2.util.IOUtils; |
27 | import org.h2.util.MathUtils; |
28 | import org.h2.util.SmallLRUCache; |
29 | import org.h2.util.StringUtils; |
30 | import org.h2.util.Utils; |
31 | |
32 | /** |
33 | * Implementation of the BLOB and CLOB data types. Small objects are kept in |
34 | * memory and stored in the record. |
35 | * |
36 | * Large objects are stored in their own files. When large objects are set in a |
37 | * prepared statement, they are first stored as 'temporary' files. Later, when |
38 | * they are used in a record, and when the record is stored, the lob files are |
39 | * linked: the file is renamed using the file format (tableId).(objectId). There |
40 | * is one exception: large variables are stored in the file (-1).(objectId). |
41 | * |
42 | * When lobs are deleted, they are first renamed to a temp file, and if the |
43 | * delete operation is committed the file is deleted. |
44 | * |
45 | * Data compression is supported. |
46 | */ |
47 | public class ValueLob extends Value { |
48 | |
49 | /** |
50 | * This counter is used to calculate the next directory to store lobs. It is |
51 | * better than using a random number because less directories are created. |
52 | */ |
53 | private static int dirCounter; |
54 | |
55 | private final int type; |
56 | private long precision; |
57 | private DataHandler handler; |
58 | private int tableId; |
59 | private int objectId; |
60 | private String fileName; |
61 | private boolean linked; |
62 | private byte[] small; |
63 | private int hash; |
64 | private boolean compressed; |
65 | private FileStore tempFile; |
66 | |
67 | private ValueLob(int type, DataHandler handler, String fileName, |
68 | int tableId, int objectId, boolean linked, long precision, |
69 | boolean compressed) { |
70 | this.type = type; |
71 | this.handler = handler; |
72 | this.fileName = fileName; |
73 | this.tableId = tableId; |
74 | this.objectId = objectId; |
75 | this.linked = linked; |
76 | this.precision = precision; |
77 | this.compressed = compressed; |
78 | } |
79 | |
80 | private ValueLob(int type, byte[] small) { |
81 | this.type = type; |
82 | this.small = small; |
83 | if (small != null) { |
84 | if (type == Value.BLOB) { |
85 | this.precision = small.length; |
86 | } else { |
87 | this.precision = getString().length(); |
88 | } |
89 | } |
90 | } |
91 | |
92 | private static ValueLob copy(ValueLob lob) { |
93 | ValueLob copy = new ValueLob(lob.type, lob.handler, lob.fileName, |
94 | lob.tableId, lob.objectId, lob.linked, lob.precision, lob.compressed); |
95 | copy.small = lob.small; |
96 | copy.hash = lob.hash; |
97 | return copy; |
98 | } |
99 | |
100 | /** |
101 | * Create a small lob using the given byte array. |
102 | * |
103 | * @param type the type (Value.BLOB or CLOB) |
104 | * @param small the byte array |
105 | * @return the lob value |
106 | */ |
107 | private static ValueLob createSmallLob(int type, byte[] small) { |
108 | return new ValueLob(type, small); |
109 | } |
110 | |
111 | private static String getFileName(DataHandler handler, int tableId, |
112 | int objectId) { |
113 | if (SysProperties.CHECK && tableId == 0 && objectId == 0) { |
114 | DbException.throwInternalError("0 LOB"); |
115 | } |
116 | String table = tableId < 0 ? ".temp" : ".t" + tableId; |
117 | return getFileNamePrefix(handler.getDatabasePath(), objectId) + |
118 | table + Constants.SUFFIX_LOB_FILE; |
119 | } |
120 | |
121 | /** |
122 | * Create a LOB value with the given parameters. |
123 | * |
124 | * @param type the data type |
125 | * @param handler the file handler |
126 | * @param tableId the table object id |
127 | * @param objectId the object id |
128 | * @param precision the precision (length in elements) |
129 | * @param compression if compression is used |
130 | * @return the value object |
131 | */ |
132 | public static ValueLob openLinked(int type, DataHandler handler, |
133 | int tableId, int objectId, long precision, boolean compression) { |
134 | String fileName = getFileName(handler, tableId, objectId); |
135 | return new ValueLob(type, handler, fileName, tableId, objectId, |
136 | true/* linked */, precision, compression); |
137 | } |
138 | |
139 | /** |
140 | * Create a LOB value with the given parameters. |
141 | * |
142 | * @param type the data type |
143 | * @param handler the file handler |
144 | * @param tableId the table object id |
145 | * @param objectId the object id |
146 | * @param precision the precision (length in elements) |
147 | * @param compression if compression is used |
148 | * @param fileName the file name |
149 | * @return the value object |
150 | */ |
151 | public static ValueLob openUnlinked(int type, DataHandler handler, |
152 | int tableId, int objectId, long precision, boolean compression, |
153 | String fileName) { |
154 | return new ValueLob(type, handler, fileName, tableId, objectId, |
155 | false/* linked */, precision, compression); |
156 | } |
157 | |
158 | /** |
159 | * Create a CLOB value from a stream. |
160 | * |
161 | * @param in the reader |
162 | * @param length the number of characters to read, or -1 for no limit |
163 | * @param handler the data handler |
164 | * @return the lob value |
165 | */ |
166 | private static ValueLob createClob(Reader in, long length, |
167 | DataHandler handler) { |
168 | try { |
169 | if (handler == null) { |
170 | String s = IOUtils.readStringAndClose(in, (int) length); |
171 | return createSmallLob(Value.CLOB, s.getBytes(Constants.UTF8)); |
172 | } |
173 | boolean compress = handler.getLobCompressionAlgorithm(Value.CLOB) != null; |
174 | long remaining = Long.MAX_VALUE; |
175 | if (length >= 0 && length < remaining) { |
176 | remaining = length; |
177 | } |
178 | int len = getBufferSize(handler, compress, remaining); |
179 | char[] buff; |
180 | if (len >= Integer.MAX_VALUE) { |
181 | String data = IOUtils.readStringAndClose(in, -1); |
182 | buff = data.toCharArray(); |
183 | len = buff.length; |
184 | } else { |
185 | buff = new char[len]; |
186 | len = IOUtils.readFully(in, buff, len); |
187 | } |
188 | if (len <= handler.getMaxLengthInplaceLob()) { |
189 | byte[] small = new String(buff, 0, len).getBytes(Constants.UTF8); |
190 | return ValueLob.createSmallLob(Value.CLOB, small); |
191 | } |
192 | ValueLob lob = new ValueLob(Value.CLOB, null); |
193 | lob.createFromReader(buff, len, in, remaining, handler); |
194 | return lob; |
195 | } catch (IOException e) { |
196 | throw DbException.convertIOException(e, null); |
197 | } |
198 | } |
199 | |
200 | private static int getBufferSize(DataHandler handler, boolean compress, |
201 | long remaining) { |
202 | if (remaining < 0 || remaining > Integer.MAX_VALUE) { |
203 | remaining = Integer.MAX_VALUE; |
204 | } |
205 | int inplace = handler.getMaxLengthInplaceLob(); |
206 | long m = compress ? |
207 | Constants.IO_BUFFER_SIZE_COMPRESS : Constants.IO_BUFFER_SIZE; |
208 | if (m < remaining && m <= inplace) { |
209 | // using "1L" to force long arithmetic |
210 | m = Math.min(remaining, inplace + 1L); |
211 | // the buffer size must be bigger than the inplace lob, otherwise we |
212 | // can't know if it must be stored in-place or not |
213 | m = MathUtils.roundUpLong(m, Constants.IO_BUFFER_SIZE); |
214 | } |
215 | m = Math.min(remaining, m); |
216 | m = MathUtils.convertLongToInt(m); |
217 | if (m < 0) { |
218 | m = Integer.MAX_VALUE; |
219 | } |
220 | return (int) m; |
221 | } |
222 | |
223 | private void createFromReader(char[] buff, int len, Reader in, |
224 | long remaining, DataHandler h) throws IOException { |
225 | FileStoreOutputStream out = initLarge(h); |
226 | boolean compress = h.getLobCompressionAlgorithm(Value.CLOB) != null; |
227 | try { |
228 | while (true) { |
229 | precision += len; |
230 | byte[] b = new String(buff, 0, len).getBytes(Constants.UTF8); |
231 | out.write(b, 0, b.length); |
232 | remaining -= len; |
233 | if (remaining <= 0) { |
234 | break; |
235 | } |
236 | len = getBufferSize(h, compress, remaining); |
237 | len = IOUtils.readFully(in, buff, len); |
238 | if (len == 0) { |
239 | break; |
240 | } |
241 | } |
242 | } finally { |
243 | out.close(); |
244 | } |
245 | } |
246 | |
247 | private static String getFileNamePrefix(String path, int objectId) { |
248 | String name; |
249 | int f = objectId % SysProperties.LOB_FILES_PER_DIRECTORY; |
250 | if (f > 0) { |
251 | name = SysProperties.FILE_SEPARATOR + objectId; |
252 | } else { |
253 | name = ""; |
254 | } |
255 | objectId /= SysProperties.LOB_FILES_PER_DIRECTORY; |
256 | while (objectId > 0) { |
257 | f = objectId % SysProperties.LOB_FILES_PER_DIRECTORY; |
258 | name = SysProperties.FILE_SEPARATOR + f + |
259 | Constants.SUFFIX_LOBS_DIRECTORY + name; |
260 | objectId /= SysProperties.LOB_FILES_PER_DIRECTORY; |
261 | } |
262 | name = FileUtils.toRealPath(path + |
263 | Constants.SUFFIX_LOBS_DIRECTORY + name); |
264 | return name; |
265 | } |
266 | |
267 | private static int getNewObjectId(DataHandler h) { |
268 | String path = h.getDatabasePath(); |
269 | if ((path != null) && (path.length() == 0)) { |
270 | path = new File(Utils.getProperty("java.io.tmpdir", "."), |
271 | SysProperties.PREFIX_TEMP_FILE).getAbsolutePath(); |
272 | } |
273 | int newId = 0; |
274 | int lobsPerDir = SysProperties.LOB_FILES_PER_DIRECTORY; |
275 | while (true) { |
276 | String dir = getFileNamePrefix(path, newId); |
277 | String[] list = getFileList(h, dir); |
278 | int fileCount = 0; |
279 | boolean[] used = new boolean[lobsPerDir]; |
280 | for (String name : list) { |
281 | if (name.endsWith(Constants.SUFFIX_DB_FILE)) { |
282 | name = FileUtils.getName(name); |
283 | String n = name.substring(0, name.indexOf('.')); |
284 | int id; |
285 | try { |
286 | id = Integer.parseInt(n); |
287 | } catch (NumberFormatException e) { |
288 | id = -1; |
289 | } |
290 | if (id > 0) { |
291 | fileCount++; |
292 | used[id % lobsPerDir] = true; |
293 | } |
294 | } |
295 | } |
296 | int fileId = -1; |
297 | if (fileCount < lobsPerDir) { |
298 | for (int i = 1; i < lobsPerDir; i++) { |
299 | if (!used[i]) { |
300 | fileId = i; |
301 | break; |
302 | } |
303 | } |
304 | } |
305 | if (fileId > 0) { |
306 | newId += fileId; |
307 | invalidateFileList(h, dir); |
308 | break; |
309 | } |
310 | if (newId > Integer.MAX_VALUE / lobsPerDir) { |
311 | // this directory path is full: start from zero |
312 | newId = 0; |
313 | dirCounter = MathUtils.randomInt(lobsPerDir - 1) * lobsPerDir; |
314 | } else { |
315 | // calculate the directory. |
316 | // start with 1 (otherwise we don't know the number of |
317 | // directories). |
318 | // it doesn't really matter what directory is used, it might as |
319 | // well be random (but that would generate more directories): |
320 | // int dirId = RandomUtils.nextInt(lobsPerDir - 1) + 1; |
321 | int dirId = (dirCounter++ / (lobsPerDir - 1)) + 1; |
322 | newId = newId * lobsPerDir; |
323 | newId += dirId * lobsPerDir; |
324 | } |
325 | } |
326 | return newId; |
327 | } |
328 | |
329 | private static void invalidateFileList(DataHandler h, String dir) { |
330 | SmallLRUCache<String, String[]> cache = h.getLobFileListCache(); |
331 | if (cache != null) { |
332 | synchronized (cache) { |
333 | cache.remove(dir); |
334 | } |
335 | } |
336 | } |
337 | |
338 | private static String[] getFileList(DataHandler h, String dir) { |
339 | SmallLRUCache<String, String[]> cache = h.getLobFileListCache(); |
340 | String[] list; |
341 | if (cache == null) { |
342 | list = FileUtils.newDirectoryStream(dir).toArray(new String[0]); |
343 | } else { |
344 | synchronized (cache) { |
345 | list = cache.get(dir); |
346 | if (list == null) { |
347 | list = FileUtils.newDirectoryStream(dir).toArray(new String[0]); |
348 | cache.put(dir, list); |
349 | } |
350 | } |
351 | } |
352 | return list; |
353 | } |
354 | |
355 | /** |
356 | * Create a BLOB value from a stream. |
357 | * |
358 | * @param in the input stream |
359 | * @param length the number of characters to read, or -1 for no limit |
360 | * @param handler the data handler |
361 | * @return the lob value |
362 | */ |
363 | private static ValueLob createBlob(InputStream in, long length, |
364 | DataHandler handler) { |
365 | try { |
366 | if (handler == null) { |
367 | byte[] data = IOUtils.readBytesAndClose(in, (int) length); |
368 | return createSmallLob(Value.BLOB, data); |
369 | } |
370 | long remaining = Long.MAX_VALUE; |
371 | boolean compress = handler.getLobCompressionAlgorithm(Value.BLOB) != null; |
372 | if (length >= 0 && length < remaining) { |
373 | remaining = length; |
374 | } |
375 | int len = getBufferSize(handler, compress, remaining); |
376 | byte[] buff; |
377 | if (len >= Integer.MAX_VALUE) { |
378 | buff = IOUtils.readBytesAndClose(in, -1); |
379 | len = buff.length; |
380 | } else { |
381 | buff = DataUtils.newBytes(len); |
382 | len = IOUtils.readFully(in, buff, len); |
383 | } |
384 | if (len <= handler.getMaxLengthInplaceLob()) { |
385 | byte[] small = DataUtils.newBytes(len); |
386 | System.arraycopy(buff, 0, small, 0, len); |
387 | return ValueLob.createSmallLob(Value.BLOB, small); |
388 | } |
389 | ValueLob lob = new ValueLob(Value.BLOB, null); |
390 | lob.createFromStream(buff, len, in, remaining, handler); |
391 | return lob; |
392 | } catch (IOException e) { |
393 | throw DbException.convertIOException(e, null); |
394 | } |
395 | } |
396 | |
397 | private FileStoreOutputStream initLarge(DataHandler h) { |
398 | this.handler = h; |
399 | this.tableId = 0; |
400 | this.linked = false; |
401 | this.precision = 0; |
402 | this.small = null; |
403 | this.hash = 0; |
404 | String compressionAlgorithm = h.getLobCompressionAlgorithm(type); |
405 | this.compressed = compressionAlgorithm != null; |
406 | synchronized (h) { |
407 | String path = h.getDatabasePath(); |
408 | if ((path != null) && (path.length() == 0)) { |
409 | path = new File(Utils.getProperty("java.io.tmpdir", "."), |
410 | SysProperties.PREFIX_TEMP_FILE).getAbsolutePath(); |
411 | } |
412 | objectId = getNewObjectId(h); |
413 | fileName = getFileNamePrefix(path, objectId) + Constants.SUFFIX_TEMP_FILE; |
414 | tempFile = h.openFile(fileName, "rw", false); |
415 | tempFile.autoDelete(); |
416 | } |
417 | FileStoreOutputStream out = new FileStoreOutputStream(tempFile, h, |
418 | compressionAlgorithm); |
419 | return out; |
420 | } |
421 | |
422 | private void createFromStream(byte[] buff, int len, InputStream in, |
423 | long remaining, DataHandler h) throws IOException { |
424 | FileStoreOutputStream out = initLarge(h); |
425 | boolean compress = h.getLobCompressionAlgorithm(Value.BLOB) != null; |
426 | try { |
427 | while (true) { |
428 | precision += len; |
429 | out.write(buff, 0, len); |
430 | remaining -= len; |
431 | if (remaining <= 0) { |
432 | break; |
433 | } |
434 | len = getBufferSize(h, compress, remaining); |
435 | len = IOUtils.readFully(in, buff, len); |
436 | if (len <= 0) { |
437 | break; |
438 | } |
439 | } |
440 | } finally { |
441 | out.close(); |
442 | } |
443 | } |
444 | |
445 | /** |
446 | * Convert a lob to another data type. The data is fully read in memory |
447 | * except when converting to BLOB or CLOB. |
448 | * |
449 | * @param t the new type |
450 | * @return the converted value |
451 | */ |
452 | @Override |
453 | public Value convertTo(int t) { |
454 | if (t == type) { |
455 | return this; |
456 | } else if (t == Value.CLOB) { |
457 | ValueLob copy = ValueLob.createClob(getReader(), -1, handler); |
458 | return copy; |
459 | } else if (t == Value.BLOB) { |
460 | ValueLob copy = ValueLob.createBlob(getInputStream(), -1, handler); |
461 | return copy; |
462 | } |
463 | return super.convertTo(t); |
464 | } |
465 | |
466 | @Override |
467 | public boolean isLinked() { |
468 | return linked; |
469 | } |
470 | |
471 | /** |
472 | * Get the current file name where the lob is saved. |
473 | * |
474 | * @return the file name or null |
475 | */ |
476 | public String getFileName() { |
477 | return fileName; |
478 | } |
479 | |
480 | @Override |
481 | public void close() { |
482 | if (fileName != null) { |
483 | if (tempFile != null) { |
484 | tempFile.stopAutoDelete(); |
485 | tempFile = null; |
486 | } |
487 | deleteFile(handler, fileName); |
488 | } |
489 | } |
490 | |
491 | @Override |
492 | public void unlink(DataHandler handler) { |
493 | if (linked && fileName != null) { |
494 | String temp; |
495 | // synchronize on the database, to avoid concurrent temp file |
496 | // creation / deletion / backup |
497 | synchronized (handler) { |
498 | temp = getFileName(handler, -1, objectId); |
499 | deleteFile(handler, temp); |
500 | renameFile(handler, fileName, temp); |
501 | tempFile = FileStore.open(handler, temp, "rw"); |
502 | tempFile.autoDelete(); |
503 | tempFile.closeSilently(); |
504 | fileName = temp; |
505 | linked = false; |
506 | } |
507 | } |
508 | } |
509 | |
510 | @Override |
511 | public Value link(DataHandler h, int tabId) { |
512 | if (fileName == null) { |
513 | this.tableId = tabId; |
514 | return this; |
515 | } |
516 | if (linked) { |
517 | ValueLob copy = ValueLob.copy(this); |
518 | copy.objectId = getNewObjectId(h); |
519 | copy.tableId = tabId; |
520 | String live = getFileName(h, copy.tableId, copy.objectId); |
521 | copyFileTo(h, fileName, live); |
522 | copy.fileName = live; |
523 | copy.linked = true; |
524 | return copy; |
525 | } |
526 | if (!linked) { |
527 | this.tableId = tabId; |
528 | String live = getFileName(h, tableId, objectId); |
529 | if (tempFile != null) { |
530 | tempFile.stopAutoDelete(); |
531 | tempFile = null; |
532 | } |
533 | renameFile(h, fileName, live); |
534 | fileName = live; |
535 | linked = true; |
536 | } |
537 | return this; |
538 | } |
539 | |
540 | /** |
541 | * Get the current table id of this lob. |
542 | * |
543 | * @return the table id |
544 | */ |
545 | @Override |
546 | public int getTableId() { |
547 | return tableId; |
548 | } |
549 | |
550 | /** |
551 | * Get the current object id of this lob. |
552 | * |
553 | * @return the object id |
554 | */ |
555 | public int getObjectId() { |
556 | return objectId; |
557 | } |
558 | |
559 | @Override |
560 | public int getType() { |
561 | return type; |
562 | } |
563 | |
564 | @Override |
565 | public long getPrecision() { |
566 | return precision; |
567 | } |
568 | |
569 | @Override |
570 | public String getString() { |
571 | int len = precision > Integer.MAX_VALUE || precision == 0 ? |
572 | Integer.MAX_VALUE : (int) precision; |
573 | try { |
574 | if (type == Value.CLOB) { |
575 | if (small != null) { |
576 | return new String(small, Constants.UTF8); |
577 | } |
578 | return IOUtils.readStringAndClose(getReader(), len); |
579 | } |
580 | byte[] buff; |
581 | if (small != null) { |
582 | buff = small; |
583 | } else { |
584 | buff = IOUtils.readBytesAndClose(getInputStream(), len); |
585 | } |
586 | return StringUtils.convertBytesToHex(buff); |
587 | } catch (IOException e) { |
588 | throw DbException.convertIOException(e, fileName); |
589 | } |
590 | } |
591 | |
592 | @Override |
593 | public byte[] getBytes() { |
594 | if (type == CLOB) { |
595 | // convert hex to string |
596 | return super.getBytes(); |
597 | } |
598 | byte[] data = getBytesNoCopy(); |
599 | return Utils.cloneByteArray(data); |
600 | } |
601 | |
602 | @Override |
603 | public byte[] getBytesNoCopy() { |
604 | if (type == CLOB) { |
605 | // convert hex to string |
606 | return super.getBytesNoCopy(); |
607 | } |
608 | if (small != null) { |
609 | return small; |
610 | } |
611 | try { |
612 | return IOUtils.readBytesAndClose( |
613 | getInputStream(), Integer.MAX_VALUE); |
614 | } catch (IOException e) { |
615 | throw DbException.convertIOException(e, fileName); |
616 | } |
617 | } |
618 | |
619 | @Override |
620 | public int hashCode() { |
621 | if (hash == 0) { |
622 | if (precision > 4096) { |
623 | // TODO: should calculate the hash code when saving, and store |
624 | // it in the database file |
625 | return (int) (precision ^ (precision >>> 32)); |
626 | } |
627 | if (type == CLOB) { |
628 | hash = getString().hashCode(); |
629 | } else { |
630 | hash = Utils.getByteArrayHash(getBytes()); |
631 | } |
632 | } |
633 | return hash; |
634 | } |
635 | |
636 | @Override |
637 | protected int compareSecure(Value v, CompareMode mode) { |
638 | if (type == Value.CLOB) { |
639 | return Integer.signum(getString().compareTo(v.getString())); |
640 | } |
641 | byte[] v2 = v.getBytesNoCopy(); |
642 | return Utils.compareNotNullSigned(getBytes(), v2); |
643 | } |
644 | |
645 | @Override |
646 | public Object getObject() { |
647 | if (type == Value.CLOB) { |
648 | return getReader(); |
649 | } |
650 | return getInputStream(); |
651 | } |
652 | |
653 | @Override |
654 | public Reader getReader() { |
655 | return IOUtils.getBufferedReader(getInputStream()); |
656 | } |
657 | |
658 | @Override |
659 | public InputStream getInputStream() { |
660 | if (fileName == null) { |
661 | return new ByteArrayInputStream(small); |
662 | } |
663 | FileStore store = handler.openFile(fileName, "r", true); |
664 | boolean alwaysClose = SysProperties.lobCloseBetweenReads; |
665 | return new BufferedInputStream( |
666 | new FileStoreInputStream(store, handler, compressed, alwaysClose), |
667 | Constants.IO_BUFFER_SIZE); |
668 | } |
669 | |
670 | @Override |
671 | public void set(PreparedStatement prep, int parameterIndex) |
672 | throws SQLException { |
673 | long p = getPrecision(); |
674 | if (p > Integer.MAX_VALUE || p <= 0) { |
675 | p = -1; |
676 | } |
677 | if (type == Value.BLOB) { |
678 | prep.setBinaryStream(parameterIndex, getInputStream(), (int) p); |
679 | } else { |
680 | prep.setCharacterStream(parameterIndex, getReader(), (int) p); |
681 | } |
682 | } |
683 | |
684 | @Override |
685 | public String getSQL() { |
686 | String s; |
687 | if (type == Value.CLOB) { |
688 | s = getString(); |
689 | return StringUtils.quoteStringSQL(s); |
690 | } |
691 | byte[] buff = getBytes(); |
692 | s = StringUtils.convertBytesToHex(buff); |
693 | return "X'" + s + "'"; |
694 | } |
695 | |
696 | @Override |
697 | public String getTraceSQL() { |
698 | if (small != null && getPrecision() <= SysProperties.MAX_TRACE_DATA_LENGTH) { |
699 | return getSQL(); |
700 | } |
701 | StringBuilder buff = new StringBuilder(); |
702 | if (type == Value.CLOB) { |
703 | buff.append("SPACE(").append(getPrecision()); |
704 | } else { |
705 | buff.append("CAST(REPEAT('00', ").append(getPrecision()).append(") AS BINARY"); |
706 | } |
707 | buff.append(" /* ").append(fileName).append(" */)"); |
708 | return buff.toString(); |
709 | } |
710 | |
711 | /** |
712 | * Get the data if this a small lob value. |
713 | * |
714 | * @return the data |
715 | */ |
716 | @Override |
717 | public byte[] getSmall() { |
718 | return small; |
719 | } |
720 | |
721 | @Override |
722 | public int getDisplaySize() { |
723 | return MathUtils.convertLongToInt(getPrecision()); |
724 | } |
725 | |
726 | @Override |
727 | public boolean equals(Object other) { |
728 | return other instanceof ValueLob && compareSecure((Value) other, null) == 0; |
729 | } |
730 | |
731 | /** |
732 | * Store the lob data to a file if the size of the buffer is larger than the |
733 | * maximum size for an in-place lob. |
734 | * |
735 | * @param h the data handler |
736 | */ |
737 | public void convertToFileIfRequired(DataHandler h) { |
738 | try { |
739 | if (small != null && small.length > h.getMaxLengthInplaceLob()) { |
740 | boolean compress = h.getLobCompressionAlgorithm(type) != null; |
741 | int len = getBufferSize(h, compress, Long.MAX_VALUE); |
742 | int tabId = tableId; |
743 | if (type == Value.BLOB) { |
744 | createFromStream( |
745 | DataUtils.newBytes(len), 0, getInputStream(), Long.MAX_VALUE, h); |
746 | } else { |
747 | createFromReader( |
748 | new char[len], 0, getReader(), Long.MAX_VALUE, h); |
749 | } |
750 | Value v2 = link(h, tabId); |
751 | if (SysProperties.CHECK && v2 != this) { |
752 | DbException.throwInternalError(); |
753 | } |
754 | } |
755 | } catch (IOException e) { |
756 | throw DbException.convertIOException(e, null); |
757 | } |
758 | } |
759 | |
760 | /** |
761 | * Check if this lob value is compressed. |
762 | * |
763 | * @return true if it is |
764 | */ |
765 | public boolean isCompressed() { |
766 | return compressed; |
767 | } |
768 | |
769 | private static synchronized void deleteFile(DataHandler handler, |
770 | String fileName) { |
771 | // synchronize on the database, to avoid concurrent temp file creation / |
772 | // deletion / backup |
773 | synchronized (handler.getLobSyncObject()) { |
774 | FileUtils.delete(fileName); |
775 | } |
776 | } |
777 | |
778 | private static synchronized void renameFile(DataHandler handler, |
779 | String oldName, String newName) { |
780 | synchronized (handler.getLobSyncObject()) { |
781 | FileUtils.move(oldName, newName); |
782 | } |
783 | } |
784 | |
785 | private static void copyFileTo(DataHandler h, String sourceFileName, |
786 | String targetFileName) { |
787 | synchronized (h.getLobSyncObject()) { |
788 | try { |
789 | IOUtils.copyFiles(sourceFileName, targetFileName); |
790 | } catch (IOException e) { |
791 | throw DbException.convertIOException(e, null); |
792 | } |
793 | } |
794 | } |
795 | |
796 | @Override |
797 | public int getMemory() { |
798 | if (small != null) { |
799 | return small.length + 104; |
800 | } |
801 | return 140; |
802 | } |
803 | |
804 | /** |
805 | * Create an independent copy of this temporary value. |
806 | * The file will not be deleted automatically. |
807 | * |
808 | * @return the value |
809 | */ |
810 | @Override |
811 | public ValueLob copyToTemp() { |
812 | ValueLob lob; |
813 | if (type == CLOB) { |
814 | lob = ValueLob.createClob(getReader(), precision, handler); |
815 | } else { |
816 | lob = ValueLob.createBlob(getInputStream(), precision, handler); |
817 | } |
818 | return lob; |
819 | } |
820 | |
821 | @Override |
822 | public Value convertPrecision(long precision, boolean force) { |
823 | if (this.precision <= precision) { |
824 | return this; |
825 | } |
826 | ValueLob lob; |
827 | if (type == CLOB) { |
828 | lob = ValueLob.createClob(getReader(), precision, handler); |
829 | } else { |
830 | lob = ValueLob.createBlob(getInputStream(), precision, handler); |
831 | } |
832 | return lob; |
833 | } |
834 | |
835 | } |