Source code: org/apache/derby/impl/store/raw/data/StreamFileContainer.java
1 /*
2
3 Derby - Class org.apache.derby.impl.store.raw.data.StreamFileContainer
4
5 Copyright 1999, 2004 The Apache Software Foundation or its licensors, as applicable.
6
7 Licensed under the Apache License, Version 2.0 (the "License");
8 you may not use this file except in compliance with the License.
9 You may obtain a copy of the License at
10
11 http://www.apache.org/licenses/LICENSE-2.0
12
13 Unless required by applicable law or agreed to in writing, software
14 distributed under the License is distributed on an "AS IS" BASIS,
15 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 See the License for the specific language governing permissions and
17 limitations under the License.
18
19 */
20
21 package org.apache.derby.impl.store.raw.data;
22
23 import org.apache.derby.iapi.reference.SQLState;
24
25 import org.apache.derby.iapi.services.context.ContextService;
26
27 import org.apache.derby.iapi.services.sanity.SanityManager;
28 import org.apache.derby.iapi.services.io.Storable;
29 import org.apache.derby.iapi.services.io.StreamStorable;
30 import org.apache.derby.iapi.services.io.FormatIdInputStream;
31 import org.apache.derby.iapi.services.io.FormatIdOutputStream;
32 import org.apache.derby.iapi.services.io.FormatIdUtil;
33 import org.apache.derby.iapi.services.io.StoredFormatIds;
34 import org.apache.derby.iapi.services.io.TypedFormat;
35 import org.apache.derby.iapi.services.monitor.Monitor;
36
37 import org.apache.derby.iapi.error.StandardException;
38 import org.apache.derby.iapi.store.access.AccessFactory;
39 import org.apache.derby.iapi.store.access.RowSource;
40 import org.apache.derby.iapi.store.access.RowUtil;
41 import org.apache.derby.iapi.store.access.TransactionController;
42 import org.apache.derby.iapi.store.raw.ContainerKey;
43 import org.apache.derby.iapi.store.raw.RawStoreFactory;
44 import org.apache.derby.iapi.store.raw.StreamContainerHandle;
45
46 import org.apache.derby.io.StorageFactory;
47 import org.apache.derby.io.WritableStorageFactory;
48 import org.apache.derby.io.StorageFile;
49
50 import org.apache.derby.impl.store.raw.data.DecryptInputStream;
51 import org.apache.derby.impl.store.raw.data.StoredFieldHeader;
52 import org.apache.derby.impl.store.raw.data.StoredRecordHeader;
53
54 import org.apache.derby.iapi.services.io.ArrayInputStream;
55 import org.apache.derby.iapi.services.io.FormatableBitSet;
56 import org.apache.derby.iapi.services.io.CompressedNumber;
57 import org.apache.derby.iapi.services.io.DynamicByteArrayOutputStream;
58 import org.apache.derby.iapi.services.io.LimitInputStream;
59 import org.apache.derby.iapi.services.property.PropertyUtil;
60
61 import java.util.Properties;
62 import java.io.InputStream;
63 import java.io.BufferedInputStream;
64 import java.io.OutputStream;
65 import java.io.IOException;
66 import java.io.EOFException;
67 import java.io.InvalidClassException;
68 import java.io.Externalizable;
69
70 /**
71
72 The format of this stream file is:
73 (RH) (FH) (field data) (FH) (field data) ........ (FH) (field data)
74
75 Record header is stored once at the beginning of the file
76 for all the rows stored in this file.
77 Record Header indicates how many fields are in each row.
78 Then we just stored all the column from each row.
79 Field header stored on this file is fixed size with fieldDataLength
80 size set to LARGE_SLOT_SIZE (4) bytes.
81
82 NOTE: No locks are used in this container. All transaction are not logged.
83
84 **/
85
86
87 public class StreamFileContainer implements TypedFormat
88 {
89
90 /**************************************************************************
91 * Constant Fields of the class
92 **************************************************************************
93 */
94
95 /*
96 * typed format
97 * format Id must fit in 4 bytes
98 */
99 protected static int formatIdInteger =
100 StoredFormatIds.RAW_STORE_SINGLE_CONTAINER_STREAM_FILE;
101
102
103 // 4 bytes for field data length
104 protected static final int LARGE_SLOT_SIZE = 4;
105
106 protected static final int MIN_BUFFER_SIZE =
107 RawStoreFactory.STREAM_FILE_BUFFER_SIZE_MINIMUM;
108 protected static final int FIELD_STATUS =
109 StoredFieldHeader.setFixed(StoredFieldHeader.setInitial(), true);
110 protected static final int FIELD_HEADER_SIZE =
111 StoredFieldHeader.size(FIELD_STATUS, 0, LARGE_SLOT_SIZE);
112
113
114 /**************************************************************************
115 * Fields of the class
116 **************************************************************************
117 */
118 protected ContainerKey identity;
119 private BaseDataFileFactory dataFactory; // the factory that made me
120
121 private int bufferSize;
122
123 private StorageFile file;
124
125 private OutputStream fileOut;
126 private DynamicByteArrayOutputStream out;
127 private FormatIdOutputStream logicalDataOut;
128
129 private InputStream fileIn;
130 private BufferedInputStream bufferedIn;
131 private DecryptInputStream decryptIn;
132 private LimitInputStream limitIn;
133 private FormatIdInputStream logicalDataIn;
134
135 private StoredRecordHeader recordHeader;
136
137 private byte[] ciphertext;
138 private byte[] zeroBytes; // in case encryption
139 // stream needs pad.
140
141 /**************************************************************************
142 * Constructors for This class:
143 **************************************************************************
144 */
145
146 /**
147 * Constructor.
148 *
149 * @exception StandardException Standard exception policy.
150 **/
151 StreamFileContainer(
152 ContainerKey identity,
153 BaseDataFileFactory dataFactory)
154 throws StandardException
155 {
156 this.identity = identity;
157 this.dataFactory = dataFactory;
158 }
159
160 /**
161 * Constructor
162 * <p>
163 * when rowSource is passed to the constructor, it will be loaded into the
164 * container after the container has been created.
165 * <p>
166 *
167 * @exception StandardException Standard exception policy.
168 **/
169 StreamFileContainer(
170 ContainerKey identity,
171 BaseDataFileFactory dataFactory,
172 Properties prop)
173 throws StandardException
174 {
175 this.identity = identity;
176 this.dataFactory = dataFactory;
177
178 try
179 {
180 file = getFileName(identity, true, false);
181
182 if (file.exists())
183 {
184 // note I'm left in the no-identity state as fillInIdentity()
185 // hasn't been called.
186 throw StandardException.newException(
187 SQLState.FILE_EXISTS, file);
188 }
189
190 // get the properties to set buffer size
191 // derby.storage.streamFileBufferSize
192 getContainerProperties(prop);
193
194 }
195 catch (SecurityException se)
196 {
197 throw StandardException.newException(
198 SQLState.FILE_CREATE, se, file);
199 }
200 }
201
202 /**************************************************************************
203 * Private/Protected methods of This class:
204 **************************************************************************
205 */
206
207 /**
208 * Open a stream file container.
209 * <p>
210 * Open a container. Open the file that maps to this container, if the
211 * file does not exist then we assume the container was never created
212 * and return.
213 * If the file exists but we have trouble opening it then we throw some
214 * exception.
215 * <p>
216 *
217 * @return The opened StreamFileContainer.
218 *
219 * @param forUpdate Currently only accepts false, updating and existing
220 * stream file container is not currently supported.
221 *
222 * @exception StandardException Standard exception policy.
223 **/
224 protected StreamFileContainer open(boolean forUpdate)
225 throws StandardException
226 {
227
228 file = getFileName(this.identity, false, true);
229 if (!file.exists())
230 return null;
231
232 try
233 {
234 if (!forUpdate)
235 {
236 fileIn = file.getInputStream();
237
238 if (dataFactory.databaseEncrypted())
239 {
240 // if the database is encrypted, when reading the data back
241 // from the file stream, we need to used the decrypt stream
242 // to buffer up the bytes for reading. DecryptInputStream
243 // also decrypts the data.
244
245 MemByteHolder byteHolder =
246 new MemByteHolder(
247 RawStoreFactory.STREAM_FILE_BUFFER_SIZE_DEFAULT);
248
249 decryptIn =
250 new DecryptInputStream(fileIn, byteHolder, dataFactory);
251
252 limitIn = new LimitInputStream(decryptIn);
253 }
254 else
255 {
256 bufferedIn =
257 new BufferedInputStream(
258 fileIn,
259 RawStoreFactory.STREAM_FILE_BUFFER_SIZE_DEFAULT);
260
261 limitIn = new LimitInputStream(bufferedIn);
262 }
263
264 // the logicalDataIn input stream is on top of a limit Input
265 // stream, use a limit stream to make sure we don't read off
266 // more then what each column says it contains
267
268 logicalDataIn = new FormatIdInputStream(limitIn);
269
270 // get the record header
271 recordHeader = new StoredRecordHeader();
272 recordHeader.read(logicalDataIn);
273
274 }
275 else
276 {
277 if (SanityManager.DEBUG)
278 SanityManager.THROWASSERT(
279 "updating existing stream container not supported yet");
280
281 return null;
282 }
283 }
284 catch (IOException ioe)
285 {
286 throw StandardException.newException(
287 SQLState.FILE_CREATE, ioe, file);
288 }
289
290 return this;
291 }
292
293 /**
294 * Close the stream file.
295 * <p>
296 * Close this stream file, and all streams associated with it.
297 * <p>
298 *
299 * @exception StandardException Standard exception policy.
300 **/
301 protected void close()
302 {
303 try
304 {
305
306 if (fileIn != null)
307 {
308 fileIn.close();
309 fileIn = null;
310 if (dataFactory.databaseEncrypted())
311 {
312 decryptIn.close();
313 decryptIn = null;
314 }
315 else
316 {
317 bufferedIn.close();
318 bufferedIn = null;
319 }
320 logicalDataIn.close();
321 logicalDataIn = null;
322 }
323
324 if (fileOut != null)
325 {
326 fileOut.close();
327 logicalDataOut.close();
328 fileOut = null;
329 logicalDataOut = null;
330 out = null;
331 }
332
333 }
334 catch (IOException ioe)
335 {
336 // ignore close errors from fileOut.close() and fileIn.close() -
337 // there isn't much we can do about them anyway - and some of the
338 // interfaces don't want to deal with exceptions from close().
339
340 /*
341 throw StandardException.newException(
342 SQLState.FILE_CREATE, ioe, file);
343 */
344 }
345 }
346
347 /**************************************************************************
348 * Public Methods of This class:
349 **************************************************************************
350 */
351
352 /**
353 * Return my format identifier.
354 **/
355 public int getTypeFormatId()
356 {
357 return StoredFormatIds.RAW_STORE_SINGLE_CONTAINER_STREAM_FILE;
358 }
359
360 /**
361 * Request the system properties associated with a stream container.
362 * <p>
363 * Request the value of properties associated with a stream container.
364 * The following properties can be requested:
365 * derby.storage.streamFileBufferSize
366 *
367 * <p>
368 * To get the value of a particular property add it to the property list,
369 * and on return the value of the property will be set to it's current
370 * value. For example:
371 *
372 * get_prop(ConglomerateController cc)
373 * {
374 * Properties prop = new Properties();
375 * prop.put("derby.storage.streamFileBufferSize", "");
376 * cc.getContainerProperties(prop);
377 *
378 * System.out.println(
379 * "stream table's buffer size = " +
380 * prop.getProperty("derby.storage.streamFileBufferSize");
381 * }
382 *
383 * @param prop Property list to fill in.
384 *
385 * @exception StandardException Standard exception policy.
386 **/
387 public void getContainerProperties(Properties prop)
388 throws StandardException
389 {
390
391 AccessFactory af = (AccessFactory)
392 Monitor.getServiceModule(dataFactory, AccessFactory.MODULE);
393
394 TransactionController tc =
395 (af == null) ?
396 null :
397 af.getTransaction(
398 ContextService.getFactory().getCurrentContextManager());
399
400 bufferSize =
401 PropertyUtil.getServiceInt(tc, prop,
402 RawStoreFactory.STREAM_FILE_BUFFER_SIZE_PARAMETER,
403 RawStoreFactory.STREAM_FILE_BUFFER_SIZE_MINIMUM,
404 RawStoreFactory.STREAM_FILE_BUFFER_SIZE_MAXIMUM,
405 RawStoreFactory.STREAM_FILE_BUFFER_SIZE_DEFAULT);
406 }
407
408 /**
409 * Request the container key associated with the stream container.
410 **/
411 public ContainerKey getIdentity()
412 {
413 return this.identity;
414 }
415
416 /**
417 * Can I use this container?
418 * <p>
419 * This method always return true right now.
420 * In the future when there are different uses for this container,
421 * we may need to add qualifications for this.
422 *
423 * @exception StandardException Standard exception policy.
424 **/
425 protected boolean use(StreamContainerHandle handle)
426 throws StandardException
427 {
428 return true;
429 }
430
431 /**
432 * load data into this container.
433 * <p>
434 * populate the stream container with data in the rowSource
435 * <p>
436 *
437 * @param rowSource The row source to get rows to load into this container.
438 *
439 * @exception StandardException Standard exception policy.
440 **/
441 public void load(RowSource rowSource)
442 throws StandardException
443 {
444 // use this output stream to buffer rows before inserting into file.
445 out = new DynamicByteArrayOutputStream(bufferSize);
446 logicalDataOut = new FormatIdOutputStream(out);
447 boolean encrypted = dataFactory.databaseEncrypted();
448
449 // reserve the first dataFactory.getEncryptionBlockSize() - 1 bytes, if the database is
450 // encrypted These reserved bytes will be used to pad the byte array if
451 // it is not dataFactory.getEncryptionBlockSize() aligned.
452 if (encrypted)
453 {
454 if (zeroBytes == null)
455 zeroBytes = new byte[dataFactory.getEncryptionBlockSize() - 1];
456
457 out.write(zeroBytes, 0, dataFactory.getEncryptionBlockSize() - 1);
458 }
459
460 try
461 {
462 fileOut = file.getOutputStream();
463
464 FormatableBitSet validColumns = rowSource.getValidColumns();
465
466 Object[] row = rowSource.getNextRowFromRowSource();
467
468 int numberFields = 0;
469 if (validColumns != null)
470 {
471 for (int i = validColumns.getLength() - 1; i >= 0; i--)
472 {
473 if (validColumns.isSet(i))
474 {
475 numberFields = i + 1;
476 break;
477 }
478 }
479 }
480 else
481 {
482 numberFields = row.length;
483 }
484
485 // make the record header to have 0 record id
486 recordHeader = new StoredRecordHeader(0, numberFields);
487
488 // write the record header once for all the rows, directly to the
489 // beginning of the file.
490 int rhLen = recordHeader.write(out);
491
492 int validColumnsSize =
493 validColumns == null ? 0 : validColumns.getLength();
494
495 while (row != null)
496 {
497
498 int arrayPosition = -1;
499
500 for (int i = 0; i < numberFields; i++)
501 {
502
503 // write each column out
504 if (validColumns == null)
505 {
506 arrayPosition++;
507 Object column = row[arrayPosition];
508 writeColumn(column);
509 }
510 else
511 {
512
513 if (validColumnsSize > i && validColumns.isSet(i))
514 {
515 arrayPosition++;
516 Object column = row[arrayPosition];
517 writeColumn(column);
518 }
519 else
520 {
521 // it is a non-existent column
522 writeColumn(null);
523 }
524 }
525
526 // put the buffer onto the page, only if it exceeded the
527 // original buffer size or it has less than 100 bytes left
528 // in the buffer
529 if ((out.getUsed() >= bufferSize) ||
530 ((bufferSize - out.getUsed()) < MIN_BUFFER_SIZE))
531 {
532 writeToFile();
533 }
534 }
535
536 // get the next row and its valid columns from the rowSource
537 row = rowSource.getNextRowFromRowSource();
538 }
539
540
541 // Write the buffer to the file if there is something in the output
542 // buffer. Remember we pad the output buffer with
543 // dataFactory.getEncryptionBlockSize() - 1 if this is an encypted database
544 if (encrypted)
545 {
546 if (out.getUsed() > (dataFactory.getEncryptionBlockSize() - 1))
547 writeToFile();
548 }
549 else if (out.getUsed() > 0)
550 {
551 writeToFile();
552 }
553
554 }
555 catch (IOException ioe)
556 {
557 // handle IO error...
558 throw StandardException.newException(
559 SQLState.DATA_UNEXPECTED_EXCEPTION, ioe);
560
561 }
562 finally
563 {
564 close();
565 }
566 }
567
568 /*
569
570 */
571 /**
572 * Write the buffer to the file.
573 * <p>
574 * If the database is encrypted, the dataFactory.getEncryptionBlockSize() - 1 reserved bytes will
575 * be used to pad the byte array to be dataFactory.getEncryptionBlockSize()
576 * aligned. Before the bytes are encrypted and written to the file stream,
577 * the actual length of the byte array is written out as a compressed
578 * integer. This number will be used when decrypting the data.
579 *
580 * If the database is not encrypted, then, we don't reserve the bytes
581 * upfront, and we simple just write the bytes out to the file stream.
582 *
583 * @exception StandardException Standard exception policy.
584 **/
585 private void writeToFile()
586 throws StandardException
587 {
588
589 try
590 {
591 if (dataFactory.databaseEncrypted())
592 {
593 // if db is encrypted,
594 // use the first ENCRYPTION_ALIGN bytes for padding.
595 //
596 int realLen = out.getUsed() - (dataFactory.getEncryptionBlockSize() - 1);
597 int tail = realLen % dataFactory.getEncryptionBlockSize();
598 int padding =
599 (tail == 0) ? 0 :
600 (dataFactory.getEncryptionBlockSize() - tail);
601
602 int startByte = (tail == 0) ? (dataFactory.getEncryptionBlockSize() - 1) : (tail - 1);
603 int encryptedLen = realLen + padding;
604
605 // there is nothing to write, just the encryption padding
606 if (realLen <= 0)
607 return;
608
609 if (ciphertext == null)
610 {
611 ciphertext = new byte[encryptedLen];
612 }
613 else
614 {
615 if (ciphertext.length < encryptedLen)
616 ciphertext = new byte[encryptedLen];
617 }
618
619 dataFactory.encrypt(
620 out.getByteArray(), startByte, encryptedLen, ciphertext, 0);
621
622 // write out the actual length, then the encrypted bytes.
623 CompressedNumber.writeInt(fileOut, realLen);
624 dataFactory.writeInProgress();
625 try
626 {
627 fileOut.write(ciphertext, 0, encryptedLen);
628 }
629 finally
630 {
631 dataFactory.writeFinished();
632 }
633
634 // reset the dynamic buffer
635 out.reset();
636
637 // reserve bytes if database is encrypted.
638 if (dataFactory.databaseEncrypted())
639 {
640 if (zeroBytes == null)
641 zeroBytes = new byte[dataFactory.getEncryptionBlockSize() - 1];
642
643 out.write(zeroBytes, 0, dataFactory.getEncryptionBlockSize() - 1);
644 }
645
646 }
647 else
648 {
649 // nothing to write
650 if (out.getUsed() == 0)
651 return;
652
653 dataFactory.writeInProgress();
654 try
655 {
656 fileOut.write(out.getByteArray(), 0, out.getUsed());
657 }
658 finally
659 {
660 dataFactory.writeFinished();
661 }
662
663 // reset the dynamic buffer
664 out.reset();
665 }
666 }
667 catch (IOException ioe)
668 {
669 throw StandardException.newException(
670 SQLState.DATA_UNEXPECTED_EXCEPTION, ioe);
671 }
672 }
673
674 private void writeColumn(Object column)
675 throws StandardException, IOException
676 {
677
678 int fieldStatus = FIELD_STATUS;
679 if (column == null)
680 {
681 // just write a non-existent header.
682 fieldStatus = StoredFieldHeader.setNonexistent(fieldStatus);
683 StoredFieldHeader.write(out, fieldStatus, 0, LARGE_SLOT_SIZE);
684 return;
685 }
686
687 // if the column is a null column, write the field header now.
688 if (column instanceof Storable)
689 {
690 Storable sColumn = (Storable) column;
691 if (sColumn.isNull())
692 {
693 fieldStatus = StoredFieldHeader.setNull(fieldStatus, true);
694 StoredFieldHeader.write(out, fieldStatus, 0, LARGE_SLOT_SIZE);
695 return;
696 }
697 }
698
699 int beginPosition = out.getPosition();
700 int fieldDataLength = 0;
701
702 // write out the header, mostly to reserve the space
703 StoredFieldHeader.write(
704 out, fieldStatus, fieldDataLength, LARGE_SLOT_SIZE);
705
706 if (column instanceof StreamStorable)
707 {
708 if (((StreamStorable) column).returnStream() != null)
709 {
710 column = (InputStream) ((StreamStorable) column).returnStream();
711 }
712 }
713
714 if (column instanceof InputStream)
715 {
716 InputStream inColumn = (InputStream) column;
717 int bufferLen = inColumn.available();
718 byte[] bufData = new byte[bufferLen];
719
720 do
721 {
722 int lenRead = inColumn.read(bufData, bufferLen, 0);
723 if (lenRead != -1)
724 {
725 fieldDataLength += lenRead;
726 out.write(bufData, lenRead, 0);
727 }
728 else
729 {
730 break;
731 }
732 } while (true);
733
734 }
735 else if (column instanceof Storable)
736 {
737
738 Storable sColumn = (Storable) column;
739 // write field data to the stream, we already handled the null case
740
741 sColumn.writeExternal(logicalDataOut);
742 fieldDataLength =
743 out.getPosition() - beginPosition - FIELD_HEADER_SIZE;
744
745 }
746 else
747 {
748 // Serializable/Externalizable/Formattable
749 // all look the same at this point.
750 logicalDataOut.writeObject(column);
751 fieldDataLength =
752 out.getPosition() - beginPosition - FIELD_HEADER_SIZE;
753 }
754
755 // Now we go back to update the fieldDataLength in the field header
756 int endPosition = out.getPosition();
757 out.setPosition(beginPosition);
758
759 StoredFieldHeader.write(
760 out, fieldStatus, fieldDataLength, LARGE_SLOT_SIZE);
761
762 // set position to the end of the field
763 if (!StoredFieldHeader.isNull(fieldStatus))
764 out.setPosition(endPosition);
765 }
766
767 public boolean fetchNext(Object[] row)
768 throws StandardException
769 {
770
771 boolean inUserCode = false;
772 int columnId = 0;
773
774 try
775 {
776
777 // Get the number of columns in the row.
778 int numberFields = recordHeader.getNumberFields();
779
780 int arrayPosition = 0;
781 for (columnId = 0; columnId < numberFields; columnId++)
782 {
783
784 if (arrayPosition >= row.length)
785 break;
786
787 limitIn.clearLimit();
788
789 // read the field header
790 int fieldStatus = StoredFieldHeader.readStatus(logicalDataIn);
791 int fieldDataLength = StoredFieldHeader.readFieldDataLength(
792 logicalDataIn, fieldStatus, LARGE_SLOT_SIZE);
793
794 limitIn.setLimit(fieldDataLength);
795
796 if (SanityManager.DEBUG)
797 {
798
799 if (StoredFieldHeader.isExtensible(fieldStatus))
800 {
801 SanityManager.THROWASSERT(
802 "extensible fields not supported yet. columnId = "
803 + columnId);
804 }
805
806 SanityManager.ASSERT(!StoredFieldHeader.isOverflow(fieldStatus),
807 "overflow field is not supported yet");
808 }
809
810 Object column = row[arrayPosition];
811
812 // Deal with Storable columns
813 if (StoredFieldHeader.isNullable(fieldStatus))
814 {
815
816 if (column == null)
817 {
818 throw StandardException.newException(
819 SQLState.DATA_NULL_STORABLE_COLUMN,
820 Integer.toString(columnId));
821 }
822
823 // SRW-DJD RESOLVE: - fix error message
824 if (!(column instanceof Storable))
825 {
826 throw StandardException.newException(
827 SQLState.DATA_NULL_STORABLE_COLUMN,
828 column.getClass().getName());
829 }
830
831 Storable sColumn = (Storable) column;
832
833 // is the column null ?
834 if (StoredFieldHeader.isNull(fieldStatus))
835 {
836
837 sColumn.restoreToNull();
838 arrayPosition++;
839 continue;
840 }
841
842 inUserCode = true;
843 sColumn.readExternal(logicalDataIn);
844 inUserCode = false;
845 arrayPosition++;
846 continue;
847 }
848
849 // Only Storables can be null ... SRW-DJD RESOLVE: - fix error message
850 if (StoredFieldHeader.isNull(fieldStatus))
851 {
852 throw StandardException.newException(
853 SQLState.DATA_NULL_STORABLE_COLUMN,
854 Integer.toString(columnId));
855 }
856
857 // This is a non-extensible field, which means the caller must
858 // know the correct type and thus the element in row is the
859 // correct type or null. If the element implements
860 // Externalizable then we can just fill it in, otherwise it
861 // must be Serializable and we have to throw it away.
862
863 Object neColumn = row[arrayPosition];
864
865 if (neColumn instanceof Externalizable)
866 {
867
868 Externalizable exColumn = (Externalizable) neColumn;
869
870 inUserCode = true;
871 exColumn.readExternal(logicalDataIn);
872 inUserCode = false;
873
874 arrayPosition++;
875 continue;
876 }
877
878 // neColumn will be ignored
879 neColumn = null;
880 inUserCode = true;
881 row[arrayPosition] = logicalDataIn.readObject();
882 inUserCode = false;
883
884 arrayPosition++;
885 continue;
886 }
887
888 }
889 catch (IOException ioe)
890 {
891
892 // an exception during the restore of a user column, this doesn't
893 // make the databse corrupt, just that this field is inaccessable
894 if (inUserCode)
895 {
896
897 if (ioe instanceof EOFException)
898 {
899 throw StandardException.newException(
900 SQLState.DATA_STORABLE_READ_MISMATCH,
901 ioe, logicalDataIn.getErrorInfo());
902 }
903
904 throw StandardException.newException(
905 SQLState.DATA_STORABLE_READ_EXCEPTION,
906 ioe, logicalDataIn.getErrorInfo());
907 }
908
909 if (ioe instanceof InvalidClassException)
910 {
911 throw StandardException.newException(
912 SQLState.DATA_STORABLE_READ_EXCEPTION,
913 ioe, logicalDataIn.getErrorInfo());
914 }
915
916 // If we are at the end of the file, trying to fetch the first
917 // column, then we know there is no more rows to fetch
918 if ((ioe instanceof EOFException) && (columnId == 0))
919 {
920 close();
921 return false;
922 }
923
924 throw dataFactory.markCorrupt(
925 StandardException.newException(
926 SQLState.DATA_CORRUPT_STREAM_CONTAINER, ioe, identity));
927
928 }
929 catch (ClassNotFoundException cnfe)
930 {
931
932 if (SanityManager.DEBUG)
933 {
934 SanityManager.ASSERT(inUserCode);
935 }
936
937 // an exception during the restore of a user column, this doesn't
938 // make the databse corrupt, just that this field is inaccessable
939 throw StandardException.newException(
940 SQLState.DATA_STORABLE_READ_MISSING_CLASS, cnfe,
941 logicalDataIn.getErrorInfo());
942
943 }
944 catch (LinkageError le)
945 {
946 if (inUserCode)
947 {
948 throw StandardException.newException(
949 SQLState.DATA_STORABLE_READ_EXCEPTION, le,
950 logicalDataIn.getErrorInfo());
951 }
952 throw le;
953 }
954
955 return true;
956
957 }
958
959 /**
960 * Close the stream file and remove the file.
961 *
962 * @exception StandardException Segment directory cannot be created
963 **/
964 public boolean removeContainer()
965 throws StandardException
966 {
967 close();
968
969 if (file.exists())
970 {
971 return file.delete();
972 }
973 else
974 {
975 return true;
976 }
977
978
979 }
980
981 /**
982 * Return a file name for the identity.
983 * <p>
984 * Return a valid file name for the identity, or null if the data
985 * directory for this segment cannot be created
986 *
987 * @exception StandardException Segment directory cannot be created
988 **/
989 protected StorageFile getFileName(
990 ContainerKey identity,
991 boolean forCreate,
992 boolean errorOK)
993 throws StandardException
994 {
995 if (identity.getSegmentId() == StreamContainerHandle.TEMPORARY_SEGMENT)
996 {
997 return( dataFactory.storageFactory.newStorageFile( dataFactory.storageFactory.getTempDir(),
998 "T" + identity.getContainerId() + ".tmp"));
999 }
1000 else
1001 {
1002 if (SanityManager.DEBUG)
1003 SanityManager.THROWASSERT(
1004 "cannot create stream container in non-temp segments yet.");
1005
1006 StorageFile container = dataFactory.getContainerPath( identity, false);
1007
1008 if (!container.exists())
1009 {
1010
1011 if (!forCreate)
1012 return null;
1013
1014 StorageFile directory = container.getParentDir();
1015
1016 if (!directory.exists())
1017 {
1018 // make sure only 1 thread can create a segment at one time
1019 synchronized(dataFactory)
1020 {
1021 if (!directory.exists())
1022 {
1023 if (!directory.mkdirs())
1024 {
1025 if (errorOK)
1026 return null;
1027 else
1028 throw StandardException.newException(
1029 SQLState.FILE_CANNOT_CREATE_SEGMENT,
1030 directory);
1031 }
1032 }
1033 }
1034 }
1035 }
1036 return container;
1037 }
1038 }
1039}