1 5 package org.h2.value; 6 7 import java.io.BufferedInputStream ; 8 import java.io.ByteArrayInputStream ; 9 import java.io.File ; 10 import java.io.IOException ; 11 import java.io.InputStream ; 12 import java.io.Reader ; 13 import java.sql.PreparedStatement ; 14 import java.sql.SQLException ; 15 16 import org.h2.engine.Constants; 17 import org.h2.message.Message; 18 import org.h2.store.DataHandler; 19 import org.h2.store.FileStore; 20 import org.h2.store.FileStoreInputStream; 21 import org.h2.store.FileStoreOutputStream; 22 import org.h2.util.ByteUtils; 23 import org.h2.util.FileUtils; 24 import org.h2.util.IOUtils; 25 import org.h2.util.RandomUtils; 26 import org.h2.util.StringUtils; 27 28 31 32 public class ValueLob extends Value { 33 36 private int type; 37 private long precision; 38 private DataHandler handler; 39 private int tableId; 40 private int objectId; 41 private String fileName; 42 private boolean linked; 43 private byte[] small; 44 private int hash; 45 private boolean compression; 46 private FileStore tempFile; 47 48 private ValueLob(int type, DataHandler handler, String fileName, int tableId, int objectId, boolean linked, long precision, boolean compression) { 49 this.type = type; 50 this.handler = handler; 51 this.fileName = fileName; 52 this.tableId = tableId; 53 this.objectId = objectId; 54 this.linked = linked; 55 this.precision = precision; 56 this.compression = compression; 57 } 58 59 private static ValueLob copy(ValueLob lob) { 60 ValueLob copy = new ValueLob(lob.type, lob.handler, lob.fileName, lob.tableId, lob.objectId, lob.linked, lob.precision, lob.compression); 61 copy.small = lob.small; 62 copy.hash = lob.hash; 63 return copy; 64 } 65 66 private ValueLob(int type, byte[] small) throws SQLException { 67 this.type = type; 68 this.small = small; 69 if(small != null) { 70 if(type == Value.BLOB) { 71 this.precision = small.length; 72 } else { 73 this.precision = getString().length(); 74 } 75 } 76 } 77 78 public static ValueLob createSmallLob(int type, byte[] small) throws SQLException { 79 return new ValueLob(type, small); 80 } 81 82 private static String getFileName(DataHandler handler, int tableId, int objectId) { 83 if (Constants.CHECK && tableId == 0 && objectId == 0) { 84 throw Message.getInternalError("0 LOB"); 85 } 86 if(Constants.LOB_FILES_IN_DIRECTORIES) { 87 String table = tableId < 0 ? ".temp" : ".t" + tableId; 88 return getFileNamePrefix(handler.getDatabasePath(), objectId) + table + Constants.SUFFIX_LOB_FILE; 89 } else { 90 return handler.getDatabasePath() + "." + tableId + "." + objectId + Constants.SUFFIX_LOB_FILE; 91 } 92 } 93 94 public static ValueLob open(int type, DataHandler handler, int tableId, int objectId, long precision, boolean compression) { 95 String fileName = getFileName(handler, tableId, objectId); 96 return new ValueLob(type, handler, fileName, tableId, objectId, true, precision, compression); 97 } 98 99 109 118 public static ValueLob createClob(Reader in, long length, DataHandler handler) throws SQLException { 119 try { 120 boolean compress = handler.getLobCompressionAlgorithm(Value.CLOB) != null; 121 long remaining = Long.MAX_VALUE; 122 if (length >= 0 && length < remaining) { 123 remaining = length; 124 } 125 int len = getBufferSize(handler, compress, remaining); 126 char[] buff = new char[len]; 127 len = IOUtils.readFully(in, buff, len); 128 len = len < 0 ? 0 : len; 129 if (len <= handler.getMaxLengthInplaceLob()) { 130 byte[] small = StringUtils.utf8Encode(new String (buff, 0, len)); 131 return ValueLob.createSmallLob(Value.CLOB, small); 132 } 133 ValueLob lob = new ValueLob(Value.CLOB, null); 134 lob.createFromReader(buff, len, in, remaining, handler); 135 return lob; 136 } catch (IOException e) { 137 throw Message.convert(e); 138 } 139 } 140 141 private static int getBufferSize(DataHandler handler, boolean compress, long remaining) { 142 int bufferSize = compress ? Constants.IO_BUFFER_SIZE_COMPRESS : Constants.IO_BUFFER_SIZE; 143 while(bufferSize < remaining && bufferSize <= handler.getMaxLengthInplaceLob()) { 144 bufferSize += Constants.IO_BUFFER_SIZE; 147 } 148 bufferSize = (int) Math.min(remaining, bufferSize); 149 return bufferSize; 150 } 151 152 private void createFromReader(char[] buff, int len, Reader in, long remaining, DataHandler handler) throws SQLException { 153 try { 154 FileStoreOutputStream out = initLarge(handler); 155 boolean compress = handler.getLobCompressionAlgorithm(Value.CLOB) != null; 156 try { 157 while (true) { 158 precision += len; 159 byte[] b = StringUtils.utf8Encode(new String (buff, 0, len)); 160 out.write(b, 0, b.length); 161 remaining -= len; 162 if (remaining <= 0) { 163 break; 164 } 165 len = getBufferSize(handler, compress, remaining); 166 len = IOUtils.readFully(in, buff, len); 167 if (len <= 0) { 168 break; 169 } 170 } 171 } finally { 172 out.close(); 173 } 174 } catch (IOException e) { 175 throw Message.convert(e); 176 } 177 } 178 179 private static String getFileNamePrefix(String path, int objectId) { 180 String name; 181 int f = objectId % Constants.LOB_FILES_PER_DIRECTORY; 182 if(f > 0) { 183 name = File.separator + objectId; 184 } else { 185 name = ""; 186 } 187 objectId /= Constants.LOB_FILES_PER_DIRECTORY; 188 while(objectId > 0) { 189 f = objectId % Constants.LOB_FILES_PER_DIRECTORY; 190 name = File.separator + f + Constants.SUFFIX_LOBS_DIRECTORY + name; 191 objectId /= Constants.LOB_FILES_PER_DIRECTORY; 192 } 193 name = path + Constants.SUFFIX_LOBS_DIRECTORY + name; 194 return name; 195 } 196 197 private static int getNewObjectId(String path) throws SQLException { 198 int objectId; 199 objectId = 0; 200 while(true) { 201 String dir = getFileNamePrefix(path, objectId); 202 String [] list = FileUtils.listFiles(dir); 203 int fileCount = 0; 204 boolean[] used = new boolean[Constants.LOB_FILES_PER_DIRECTORY]; 205 for(int i=0; i<list.length; i++) { 206 String name = list[i]; 207 if(name.endsWith(".db")) { 208 name = name.substring(name.lastIndexOf(File.separatorChar) + 1); 209 String n = name.substring(0, name.indexOf('.')); 210 int id; 211 try { 212 id = Integer.parseInt(n); 213 } catch(NumberFormatException e) { 214 id = -1; 215 } 216 if(id > 0) { 217 fileCount++; 218 used[id % Constants.LOB_FILES_PER_DIRECTORY] = true; 219 } 220 } 221 } 222 int fileId = -1; 223 if(fileCount < Constants.LOB_FILES_PER_DIRECTORY) { 224 for(int i=1; i<Constants.LOB_FILES_PER_DIRECTORY; i++) { 225 if(!used[i]) { 226 fileId = i; 227 break; 228 } 229 } 230 } 231 if(fileId > 0) { 232 objectId += fileId; 233 break; 234 } else { 235 if(objectId > Integer.MAX_VALUE / Constants.LOB_FILES_PER_DIRECTORY) { 236 objectId = 0; 239 } else { 240 int dirId = RandomUtils.nextInt(Constants.LOB_FILES_PER_DIRECTORY - 1) + 1; 242 objectId = objectId * Constants.LOB_FILES_PER_DIRECTORY; 243 objectId += dirId * Constants.LOB_FILES_PER_DIRECTORY; 244 } 245 } 246 } 247 return objectId; 248 } 249 250 public static ValueLob createBlob(InputStream in, long length, DataHandler handler) throws SQLException { 251 try { 252 long remaining = Long.MAX_VALUE; 253 boolean compress = handler.getLobCompressionAlgorithm(Value.BLOB) != null; 254 if (length >= 0 && length < remaining) { 255 remaining = length; 256 } 257 int len = getBufferSize(handler, compress, remaining); 258 byte[] buff = new byte[len]; 259 len = IOUtils.readFully(in, buff, len); 260 len = len < 0 ? 0 : len; 261 if (len <= handler.getMaxLengthInplaceLob()) { 262 byte[] small = new byte[len]; 263 System.arraycopy(buff, 0, small, 0, len); 264 return ValueLob.createSmallLob(Value.BLOB, small); 265 } 266 ValueLob lob = new ValueLob(Value.BLOB, null); 267 lob.createFromStream(buff, len, in, remaining, handler); 268 return lob; 269 } catch (IOException e) { 270 throw Message.convert(e); 271 } 272 } 273 274 private FileStoreOutputStream initLarge(DataHandler handler) throws IOException , SQLException { 275 this.handler = handler; 276 this.tableId = 0; 277 this.linked = false; 278 this.precision = 0; 279 this.small = null; 280 this.hash = 0; 281 String compressionAlgorithm = handler.getLobCompressionAlgorithm(type); 282 this.compression = compressionAlgorithm != null; 283 synchronized(handler) { 284 if(Constants.LOB_FILES_IN_DIRECTORIES) { 285 objectId = getNewObjectId(handler.getDatabasePath()); 286 fileName = getFileNamePrefix(handler.getDatabasePath(), objectId) + ".temp.db"; 287 } else { 288 objectId = handler.allocateObjectId(false, true); 289 fileName = handler.createTempFile(); 290 } 291 tempFile = handler.openFile(fileName, false); 292 tempFile.autoDelete(); 293 } 294 FileStoreOutputStream out = new FileStoreOutputStream(tempFile, handler, compressionAlgorithm); 295 return out; 296 } 297 298 private void createFromStream(byte[] buff, int len, InputStream in, long remaining, DataHandler handler) throws SQLException { 299 try { 300 FileStoreOutputStream out = initLarge(handler); 301 boolean compress = handler.getLobCompressionAlgorithm(Value.BLOB) != null; 302 try { 303 while (true) { 304 precision += len; 305 out.write(buff, 0, len); 306 remaining -= len; 307 if (remaining <= 0) { 308 break; 309 } 310 len = getBufferSize(handler, compress, remaining); 311 len = IOUtils.readFully(in, buff, len); 312 if (len <= 0) { 313 break; 314 } 315 } 316 } finally { 317 out.close(); 318 } 319 } catch (IOException e) { 320 throw Message.convert(e); 321 } 322 } 323 324 public Value convertTo(int t) throws SQLException { 325 if (t == type) { 326 return this; 327 } else if (t == Value.CLOB) { 328 ValueLob copy = ValueLob.createClob(getReader(), -1, handler); 329 return copy; 330 } else if(t == Value.BLOB) { 331 ValueLob copy = ValueLob.createBlob(getInputStream(), -1, handler); 332 return copy; 333 } 334 return super.convertTo(t); 335 } 336 337 public boolean isLinked() { 338 return linked; 339 } 340 341 public void unlink(DataHandler handler) throws SQLException { 342 if (linked && fileName != null) { 343 String temp; 344 if(Constants.LOB_FILES_IN_DIRECTORIES) { 345 temp = getFileName(handler, -1, objectId); 346 } else { 347 temp = handler.createTempFile(); 349 } 350 FileUtils.delete(temp); 353 FileUtils.rename(fileName, temp); 355 tempFile = FileStore.open(handler, temp, null); 356 tempFile.autoDelete(); 357 tempFile.closeSilently(); 358 fileName = temp; 359 linked = false; 360 } 361 } 362 363 public Value link(DataHandler handler, int tabId) throws SQLException { 364 if(fileName == null) { 365 this.tableId = tabId; 366 return this; 367 } 368 if(linked) { 369 ValueLob copy = ValueLob.copy(this); 370 if(Constants.LOB_FILES_IN_DIRECTORIES) { 371 copy.objectId = getNewObjectId(handler.getDatabasePath()); 372 } else { 373 copy.objectId = handler.allocateObjectId(false, true); 374 } 375 copy.tableId = tabId; 376 String live = getFileName(handler, copy.tableId, copy.objectId); 377 FileUtils.copy(fileName, live); 378 copy.fileName = live; 379 copy.linked = true; 380 return copy; 381 } 382 if (!linked) { 383 this.tableId = tabId; 384 String live = getFileName(handler, tableId, objectId); 385 tempFile.stopAutoDelete(); 386 tempFile = null; 387 FileUtils.rename(fileName, live); 388 fileName = live; 389 linked = true; 390 } 391 return this; 392 } 393 394 public int getTableId() { 395 return tableId; 396 } 397 398 public int getObjectId() { 399 return objectId; 400 } 401 402 public int getType() { 403 return type; 404 } 405 406 public long getPrecision() { 407 return precision; 408 } 409 410 public String getString() throws SQLException { 411 int len = precision > Integer.MAX_VALUE || precision == 0 ? Integer.MAX_VALUE : (int)precision; 412 try { 413 if (type == Value.CLOB) { 414 if (small != null) { 415 return StringUtils.utf8Decode(small); 416 } 417 return IOUtils.readStringAndClose(getReader(), len); 418 } else { 419 byte[] buff; 420 if (small != null) { 421 buff = small; 422 } else { 423 buff = IOUtils.readBytesAndClose(getInputStream(), len); 424 } 425 return ByteUtils.convertBytesToString(buff); 426 } 427 } catch (IOException e) { 428 throw Message.convert(e); 429 } 430 } 431 432 public byte[] getBytes() throws SQLException { 433 byte[] data = getBytesNoCopy(); 434 return ByteUtils.cloneByteArray(data); 435 } 436 437 public byte[] getBytesNoCopy() throws SQLException { 438 if (small != null) { 439 return small; 440 } 441 try { 442 return IOUtils.readBytesAndClose(getInputStream(), Integer.MAX_VALUE); 443 } catch (IOException e) { 444 throw Message.convert(e); 445 } 446 } 447 448 public int hashCode() { 449 if (hash == 0) { 450 try { 451 hash = ByteUtils.getByteArrayHash(getBytes()); 452 } catch(SQLException e) { 453 } 455 } 456 return hash; 457 } 458 459 protected int compareSecure(Value v, CompareMode mode) throws SQLException { 460 if(type == Value.CLOB) { 461 int c = getString().compareTo(v.getString()); 462 return c == 0 ? 0 : (c < 0 ? -1 : 1); 463 } else { 464 byte[] v2 = v.getBytesNoCopy(); 465 return ByteUtils.compareNotNull(getBytes(), v2); 466 } 467 } 468 469 public Object getObject() throws SQLException { 470 if(type == Value.CLOB) { 471 return getReader(); 472 } else { 473 return getInputStream(); 474 } 475 } 476 477 public Reader getReader() throws SQLException { 478 return IOUtils.getReader(getInputStream()); 479 } 480 481 public InputStream getInputStream() throws SQLException { 482 if (fileName == null) { 483 return new ByteArrayInputStream (small); 484 } 485 FileStore store = handler.openFile(fileName, true); 486 return new BufferedInputStream (new FileStoreInputStream(store, handler, compression), Constants.IO_BUFFER_SIZE); 487 } 488 489 public void set(PreparedStatement prep, int parameterIndex) throws SQLException { 490 long p = getPrecision(); 491 if(p > Integer.MAX_VALUE || p <= 0) { 493 p = -1; 494 } 495 if(type == Value.BLOB) { 496 prep.setBinaryStream(parameterIndex, getInputStream(), (int)p); 497 } else { 498 prep.setCharacterStream(parameterIndex, getReader(), (int)p); 499 } 500 } 501 502 public String getSQL() { 503 try { 504 String s; 505 if(type == Value.CLOB) { 506 if(precision < Constants.DEFAULT_MAX_LENGTH_INPLACE_LOB) { 507 s = getString(); 508 return StringUtils.quoteStringSQL(s); 509 } else { 510 return "READ_CLOB('" + fileName + "', "+precision+")"; 511 } 513 } else { 514 if(precision < Constants.DEFAULT_MAX_LENGTH_INPLACE_LOB) { 515 byte[] buff = getBytes(); 516 s = ByteUtils.convertBytesToString(buff); 517 return "X'" + s + "'"; 518 } else { 519 return "READ_BLOB('"+ fileName +"', "+precision+")"; 520 } 521 } 522 } catch(SQLException e) { 523 throw Message.convertToInternal(e); 524 } 525 } 526 527 public byte[] getSmall() { 528 return small; 529 } 530 531 536 public int getDisplaySize() { 537 return 40; 539 } 540 541 protected boolean isEqual(Value v) { 542 try { 543 return compareSecure(v, null) == 0; 544 } catch(SQLException e) { 545 throw Message.getInternalError("compare", e); 547 } 548 } 549 550 public void convertToFileIfRequired(DataHandler handler) throws SQLException { 551 if(Constants.AUTO_CONVERT_LOB_TO_FILES && small != null && small.length > handler.getMaxLengthInplaceLob()) { 552 boolean compress = handler.getLobCompressionAlgorithm(type) != null; 553 int len = getBufferSize(handler, compress, Long.MAX_VALUE); 554 int tabId = tableId; 555 if(type == Value.BLOB) { 556 createFromStream(new byte[len], 0, getInputStream(), Long.MAX_VALUE, handler); 557 } else { 558 createFromReader(new char[len], 0, getReader(), Long.MAX_VALUE, handler); 559 } 560 Value v2 = link(handler, tabId); 561 if(Constants.CHECK && v2 != this) { 562 throw Message.getInternalError(); 563 } 564 } 565 } 566 567 public static void removeAllForTable(DataHandler handler, int tableId) throws SQLException { 568 if(Constants.LOB_FILES_IN_DIRECTORIES) { 569 String dir = getFileNamePrefix(handler.getDatabasePath(), 0); 570 removeAllForTable(handler, dir, tableId); 571 } else { 572 String prefix = handler.getDatabasePath(); 573 String dir = FileUtils.getParent(prefix); 574 String [] list = FileUtils.listFiles(dir); 575 for(int i=0; i<list.length; i++) { 576 String name = list[i]; 577 if(name.startsWith(prefix+"." + tableId) && name.endsWith(".lob.db")) { 578 FileUtils.delete(name); 579 } 580 } 581 } 582 } 583 584 private static void removeAllForTable(DataHandler handler, String dir, int tableId) throws SQLException { 585 String [] list = FileUtils.listFiles(dir); 586 for(int i=0; i<list.length; i++) { 587 if(FileUtils.isDirectory(list[i])) { 588 removeAllForTable(handler, list[i], tableId); 589 } else { 590 String name = list[i]; 591 if(name.endsWith(".t" + tableId + ".lob.db")) { 592 FileUtils.delete(name); 593 } 594 } 595 } 596 } 597 598 public boolean useCompression() { 599 return compression; 600 } 601 602 } 603 | Popular Tags |