KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > mckoi > database > MasterTableDataSource


1 /**
2  * com.mckoi.database.MasterTableDataSource 19 Nov 2000
3  *
4  * Mckoi SQL Database ( http://www.mckoi.com/database )
5  * Copyright (C) 2000, 2001, 2002 Diehl and Associates, Inc.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * Version 2 as published by the Free Software Foundation.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License Version 2 for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * Version 2 along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19  *
20  * Change Log:
21  *
22  *
23  */

24
25 package com.mckoi.database;
26
27 import java.util.ArrayList JavaDoc;
28 import java.io.*;
29 import com.mckoi.util.IntegerListInterface;
30 import com.mckoi.util.IntegerIterator;
31 import com.mckoi.util.IntegerVector;
32 import com.mckoi.util.ByteArrayUtil;
33 import com.mckoi.util.UserTerminal;
34 import com.mckoi.util.Cache;
35 import com.mckoi.debug.*;
36
37 /**
38  * A master table data source provides facilities for read/writing and
39  * maintaining low level data in a table. It provides primitive table
40  * operations such as retrieving a cell from a table, accessing the table's
41  * DataTableDef, accessing indexes, and providing views of transactional
42  * versions of the data.
43  * <p>
44  * Logically, a master table data source contains a dynamic number of rows and
45  * a fixed number of columns. Each row has an associated state - either
46  * DELETED, UNCOMMITTED, COMMITTED_ADDED or COMMITTED_REMOVED. A DELETED
47  * row is a row that can be reused by a new row added to the table.
48  * <p>
49  * When a new row is added to the table, it is marked as UNCOMMITTED. It is
50  * later tagged as COMMITTED_ADDED when the transaction that caused the row
51  * addition is committed. If a row commits a row removal, the row is tagged
52  * as COMMITTED_REMOVED and later the row garbage collector marks the row as
53  * DELETED when there are no remaining references to the row.
54  * <p>
55  * A master table also maintains a list of indexes for the table.
56  * <p>
57  * How the master table logical structure is translated to a form that is
58  * stored persistantly is implementation specific. This allows us flexibility
59  * with different types of storage schemes.
60  *
61  * @author Tobias Downer
62  */

63
64 abstract class MasterTableDataSource {
65
66
67   // ---------- System information ----------
68

69   /**
70    * The global TransactionSystem object that points to the global system
71    * that this table source belongs to.
72    */

73   private TransactionSystem system;
74
75   /**
76    * The StoreSystem implementation that represents the data persistence
77    * layer.
78    */

79   private StoreSystem store_system;
80   
81   // ---------- State information ----------
82

83   /**
84    * An integer that uniquely identifies this data source within the
85    * conglomerate.
86    */

87   protected int table_id;
88
89   /**
90    * True if this table source is closed.
91    */

92   protected boolean is_closed;
93
94   // ---------- Root locking ----------
95

96   /**
97    * The number of root locks this table data source has on it.
98    * <p>
99    * While a MasterTableDataSource has at least 1 root lock, it may not
100    * reclaim deleted space in the data store. A root lock means that data
101    * is still being pointed to in this file (even possibly committed deleted
102    * data).
103    */

104   private int root_lock;
105
106   // ---------- Persistant data ----------
107

108   /**
109    * A DataTableDef object that describes the table topology. This includes
110    * the name and columns of the table.
111    */

112   protected DataTableDef table_def;
113
114   /**
115    * A DataIndexSetDef object that describes the indexes on the table.
116    */

117   protected DataIndexSetDef index_def;
118   
119   /**
120    * A cached TableName for this data source.
121    */

122   private TableName cached_table_name;
123   
124   /**
125    * A multi-version representation of the table indices kept for this table
126    * including the row list and the scheme indices. This contains the
127    * transaction journals.
128    */

129   protected MultiVersionTableIndices table_indices;
130
131   /**
132    * The list of RIDList objects for each column in this table. This is
133    * a sorting optimization.
134    */

135   protected RIDList[] column_rid_list;
136
137   // ---------- Cached information ----------
138

139   /**
140    * Set to false to disable cell caching.
141    */

142   protected boolean DATA_CELL_CACHING = true;
143
144   /**
145    * A reference to the DataCellCache object.
146    */

147   protected final DataCellCache cache;
148   
149   /**
150    * The number of columns in this table. This is a cached optimization.
151    */

152   protected int column_count;
153
154   
155   
156   // --------- Parent information ----------
157

158   /**
159    * The list of all open transactions managed by the parent conglomerate.
160    * This is a thread safe object, and is updated whenever new transactions
161    * are created, or transactions are closed.
162    */

163   private OpenTransactionList open_transactions;
164
165   // ---------- Row garbage collection ----------
166

167   /**
168    * Manages scanning and deleting of rows marked as deleted within this
169    * data source.
170    */

171   protected MasterTableGarbageCollector garbage_collector;
172
173   // ---------- Blob management ----------
174

175   /**
176    * An abstracted reference to a BlobStore for managing blob references and
177    * blob data.
178    */

179   protected BlobStoreInterface blob_store_interface;
180
181   // ---------- Stat keys ----------
182

183   /**
184    * The keys we use for Database.stats() for information for this table.
185    */

186   protected String JavaDoc root_lock_key;
187   protected String JavaDoc total_hits_key;
188   protected String JavaDoc file_hits_key;
189   protected String JavaDoc delete_hits_key;
190   protected String JavaDoc insert_hits_key;
191
192   /**
193    * Constructs the MasterTableDataSource. The argument is a reference
194    * to an object that manages the list of open transactions in the
195    * conglomerate. This object uses this information to determine how journal
196    * entries are to be merged with the master indices.
197    */

198   MasterTableDataSource(TransactionSystem system,
199                         StoreSystem store_system,
200                         OpenTransactionList open_transactions,
201                         BlobStoreInterface blob_store_interface) {
202
203     this.system = system;
204     this.store_system = store_system;
205     this.open_transactions = open_transactions;
206     this.blob_store_interface = blob_store_interface;
207     this.garbage_collector = new MasterTableGarbageCollector(this);
208     this.cache = system.getDataCellCache();
209     is_closed = true;
210
211     if (DATA_CELL_CACHING) {
212       DATA_CELL_CACHING = (cache != null);
213     }
214
215   }
216
217   /**
218    * Returns the TransactionSystem for this table.
219    */

220   public final TransactionSystem getSystem() {
221     return system;
222   }
223
224   /**
225    * Returns the DebugLogger object that can be used to log debug messages.
226    */

227   public final DebugLogger Debug() {
228     return getSystem().Debug();
229   }
230
231   /**
232    * Returns the TableName of this table source.
233    */

234   public TableName getTableName() {
235     return getDataTableDef().getTableName();
236   }
237
238   /**
239    * Returns the name of this table source.
240    */

241   public String JavaDoc getName() {
242     return getDataTableDef().getName();
243   }
244
245   /**
246    * Returns the schema name of this table source.
247    */

248   public String JavaDoc getSchema() {
249     return getDataTableDef().getSchema();
250   }
251
252   /**
253    * Returns a cached TableName for this data source.
254    */

255   synchronized TableName cachedTableName() {
256     if (cached_table_name != null) {
257       return cached_table_name;
258     }
259     cached_table_name = getTableName();
260     return cached_table_name;
261   }
262   
263   /**
264    * Updates the master records from the journal logs up to the given
265    * 'commit_id'. This could be a fairly expensive operation if there are
266    * a lot of modifications because each change could require a lookup
267    * of records in the data source.
268    * <p>
269    * NOTE: It's extremely important that when this is called, there are no
270    * transaction open that are using the merged journal. If there is, then
271    * a transaction may be able to see changes in a table that were made
272    * after the transaction started.
273    * <p>
274    * After this method is called, it's best to update the index file
275    * with a call to 'synchronizeIndexFiles'
276    */

277   synchronized void mergeJournalChanges(long commit_id) {
278
279     boolean all_merged = table_indices.mergeJournalChanges(commit_id);
280     // If all journal entries merged then schedule deleted row collection.
281
if (all_merged && !isReadOnly()) {
282       checkForCleanup();
283     }
284
285   }
286
287   /**
288    * Returns a list of all MasterTableJournal objects that have been
289    * successfully committed against this table that have an 'commit_id' that
290    * is greater or equal to the given.
291    * <p>
292    * This is part of the conglomerate commit check phase and will be on a
293    * commit_lock.
294    */

295   synchronized MasterTableJournal[] findAllJournalsSince(long commit_id) {
296     return table_indices.findAllJournalsSince(commit_id);
297   }
298
299   // ---------- Getters ----------
300

301   /**
302    * Returns table_id - the unique identifier for this data source.
303    */

304   int getTableID() {
305     return table_id;
306   }
307
308   /**
309    * Returns the DataTableDef object that represents the topology of this
310    * table data source (name, columns, etc). Note that this information
311    * can't be changed during the lifetime of a data source.
312    */

313   DataTableDef getDataTableDef() {
314     return table_def;
315   }
316
317   /**
318    * Returns the DataIndexSetDef object that represents the indexes on this
319    * table.
320    */

321   DataIndexSetDef getDataIndexSetDef() {
322     return index_def;
323   }
324
325   // ---------- Convenient statics ----------
326

327   /**
328    * Creates a unique table name to give a file. This could be changed to suit
329    * a particular OS's style of filesystem namespace. Or it could return some
330    * arbitarily unique number. However, for debugging purposes it's often
331    * a good idea to return a name that a user can recognise.
332    * <p>
333    * The 'table_id' is a guarenteed unique number between all tables.
334    */

335   protected static String JavaDoc makeTableFileName(TransactionSystem system,
336                                           int table_id, TableName table_name) {
337     
338     // NOTE: We may want to change this for different file systems.
339
// For example DOS is not able to handle more than 8 characters
340
// and is case insensitive.
341
String JavaDoc tid = Integer.toString(table_id);
342     int pad = 3 - tid.length();
343     StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
344     for (int i = 0; i < pad; ++i) {
345       buf.append('0');
346     }
347
348     String JavaDoc str = table_name.toString().replace('.', '_');
349
350     // Go through each character and remove each non a-z,A-Z,0-9,_ character.
351
// This ensure there are no strange characters in the file name that the
352
// underlying OS may not like.
353
StringBuffer JavaDoc osified_name = new StringBuffer JavaDoc();
354     int count = 0;
355     for (int i = 0; i < str.length() || count > 64; ++i) {
356       char c = str.charAt(i);
357       if ((c >= 'a' && c <= 'z') ||
358           (c >= 'A' && c <= 'Z') ||
359           (c >= '0' && c <= '9') ||
360           c == '_') {
361         osified_name.append(c);
362         ++count;
363       }
364     }
365
366     return new String JavaDoc(buf) + tid + new String JavaDoc(osified_name);
367   }
368   
369   
370   // ---------- Abstract methods ----------
371

372   /**
373    * Returns a string that uniquely identifies this table within the
374    * conglomerate context. For example, the filename of the table. This
375    * string can be used to open and initialize the table also.
376    */

377   abstract String JavaDoc getSourceIdent();
378   
379   /**
380    * Sets the record type for the given record in the table and returns the
381    * previous state of the record. This is used to change the state of a
382    * row in the table.
383    */

384   abstract int writeRecordType(int row_index, int row_state)
385                                                            throws IOException;
386   
387   /**
388    * Reads the record state for the given record in the table.
389    */

390   abstract int readRecordType(int row_index) throws IOException;
391   
392   /**
393    * Returns true if the record with the given index is deleted from the table.
394    * A deleted row can not be read.
395    */

396   abstract boolean recordDeleted(int row_index) throws IOException;
397
398   /**
399    * Returns the raw count or rows in the table, including uncommited,
400    * committed and deleted rows. This is basically the maximum number of rows
401    * we can iterate through.
402    */

403   abstract int rawRowCount() throws IOException;
404
405   /**
406    * Removes the row at the given index so that any resources associated with
407    * the row may be immediately available to be recycled.
408    */

409   abstract void internalDeleteRow(int row_index) throws IOException;
410   
411   /**
412    * Creates and returns an IndexSet object that is used to create indices
413    * for this table source. The IndexSet represents a snapshot of the
414    * table and the given point in time.
415    * <p>
416    * NOTE: Not synchronized because we synchronize in the IndexStore object.
417    */

418   abstract IndexSet createIndexSet();
419
420   /**
421    * Commits changes made to an IndexSet returned by the 'createIndexSet'
422    * method. This method also disposes the IndexSet so it is no longer
423    * valid.
424    */

425   abstract void commitIndexSet(IndexSet index_set);
426   
427   /**
428    * Adds a new row to this table and returns an index that is used to
429    * reference this row by the 'getCellContents' method.
430    * <p>
431    * Note that this method will not effect the master index or column schemes.
432    * This is a low level mechanism for adding unreferenced data into a
433    * conglomerate. The data is referenced by committing the change where it
434    * eventually migrates into the master index and schemes.
435    */

436   abstract int internalAddRow(RowData data) throws IOException;
437
438   /**
439    * Returns the cell contents of the given cell in the table. It is the
440    * responsibility of the implemented method to perform caching as it deems
441    * fit. Some representations may not require such extensive caching as
442    * others.
443    */

444   abstract TObject internalGetCellContents(int column, int row);
445
446   /**
447    * Atomically returns the current 'unique_id' value for this table.
448    */

449   abstract long currentUniqueID();
450
451   /**
452    * Atomically returns the next 'unique_id' value from this table.
453    */

454   abstract long nextUniqueID();
455
456   /**
457    * Sets the unique id for this store. This must only be used under
458    * extraordinary circumstances, such as restoring from a backup, or
459    * converting from one file to another.
460    */

461   abstract void setUniqueID(long value);
462   
463   /**
464    * Disposes of all in-memory resources associated with this table and
465    * invalidates this object. If 'pending_drop' is true then the table is
466    * to be disposed pending a call to 'drop'. If 'pending_drop' is true then
467    * any persistant resources that are allocated may be freed.
468    */

469   abstract void dispose(boolean pending_drop) throws IOException;
470
471   /**
472    * Disposes and drops this table. If the dispose failed for any reason,
473    * it returns false, otherwise true. If the drop failed, it should be
474    * retried at a later time.
475    */

476   abstract boolean drop() throws IOException;
477
478   /**
479    * Called by the 'shutdown hook' on the conglomerate. This method should
480    * block until the table can by put into a safe mode and then prevent any
481    * further access to the object after it returns. It must operate very
482    * quickly.
483    */

484   abstract void shutdownHookCleanup();
485   
486
487
488   /**
489    * Returns true if a compact table is necessary. By default, we return
490    * true however it is recommended this method is overwritten and the table
491    * tested.
492    */

493   boolean isWorthCompacting() {
494     return true;
495   }
496
497   /**
498    * Creates a SelectableScheme object for the given column in this table.
499    * This reads the index from the index set (if there is one) then wraps
500    * it around the selectable schema as appropriate.
501    * <p>
502    * NOTE: This needs to be deprecated in support of composite indexes.
503    */

504   synchronized SelectableScheme createSelectableSchemeForColumn(
505                     IndexSet index_set, TableDataSource table, int column) {
506     // What's the type of scheme for this column?
507
DataTableColumnDef column_def = getDataTableDef().columnAt(column);
508
509     // If the column isn't indexable then return a BlindSearch object
510
if (!column_def.isIndexableType()) {
511       return new BlindSearch(table, column);
512     }
513
514     String JavaDoc scheme_type = column_def.getIndexScheme();
515     if (scheme_type.equals("InsertSearch")) {
516       // Search the TableIndexDef for this column
517
DataIndexSetDef index_set_def = getDataIndexSetDef();
518       int index_i = index_set_def.findIndexForColumns(
519                                        new String JavaDoc[] { column_def.getName() });
520       return createSelectableSchemeForIndex(index_set, table, index_i);
521     }
522     else if (scheme_type.equals("BlindSearch")) {
523       return new BlindSearch(table, column);
524     }
525     else {
526       throw new Error JavaDoc("Unknown scheme type");
527     }
528   }
529
530   /**
531    * Creates a SelectableScheme object for the given index in the index set def
532    * in this table.
533    * This reads the index from the index set (if there is one) then wraps
534    * it around the selectable schema as appropriate.
535    */

536   synchronized SelectableScheme createSelectableSchemeForIndex(
537                IndexSet index_set, TableDataSource table, int index_i) {
538
539     // Get the IndexDef object
540
DataIndexDef index_def = getDataIndexSetDef().indexAt(index_i);
541
542     if (index_def.getType().equals("BLIST")) {
543       String JavaDoc[] cols = index_def.getColumnNames();
544       DataTableDef table_def = getDataTableDef();
545       if (cols.length == 1) {
546         // If a single column
547
int col_index = table_def.findColumnName(cols[0]);
548         // Get the index from the index set and set up the new InsertSearch
549
// scheme.
550
IntegerListInterface index_list =
551                                    index_set.getIndex(index_def.getPointer());
552         InsertSearch iis = new InsertSearch(table, col_index, index_list);
553         return iis;
554       }
555       else {
556         throw new RuntimeException JavaDoc(
557                           "Multi-column indexes not supported at this time.");
558       }
559     }
560     else {
561       throw new RuntimeException JavaDoc("Unrecognised type.");
562     }
563                  
564   }
565
566   /**
567    * Creates a minimal TableDataSource object that represents this
568    * MasterTableDataSource. It does not implement the 'getColumnScheme'
569    * method.
570    */

571   protected TableDataSource minimalTableDataSource(
572                                   final IntegerListInterface master_index) {
573     // Make a TableDataSource that represents the master table over this
574
// index.
575
return new TableDataSource() {
576       public TransactionSystem getSystem() {
577         return system;
578       }
579       public DataTableDef getDataTableDef() {
580         return MasterTableDataSource.this.getDataTableDef();
581       }
582       public int getRowCount() {
583         // NOTE: Returns the number of rows in the master index before journal
584
// entries have been made.
585
return master_index.size();
586       }
587       public RowEnumeration rowEnumeration() {
588         // NOTE: Returns iterator across master index before journal entry
589
// changes.
590
// Get an iterator across the row list.
591
final IntegerIterator iterator = master_index.iterator();
592         // Wrap it around a RowEnumeration object.
593
return new RowEnumeration() {
594           public boolean hasMoreRows() {
595             return iterator.hasNext();
596           }
597           public int nextRowIndex() {
598             return iterator.next();
599           }
600         };
601       }
602       public SelectableScheme getColumnScheme(int column) {
603         throw new Error JavaDoc("Not implemented.");
604       }
605       public TObject getCellContents(int column, int row) {
606         return MasterTableDataSource.this.getCellContents(column, row);
607       }
608     };
609   }
610
611   /**
612    * Builds a complete index set on the data in this table. This must only be
613    * called when either, a) we are under a commit lock, or b) there is a
614    * guarentee that no concurrect access to the indexing information can happen
615    * (such as when we are creating the table).
616    * <p>
617    * NOTE: We assume that the index information for this table is blank before
618    * this method is called.
619    */

620   synchronized void buildIndexes() throws IOException {
621     IndexSet index_set = createIndexSet();
622     
623     DataIndexSetDef index_set_def = getDataIndexSetDef();
624
625     final int row_count = rawRowCount();
626
627     // Master index is always on index position 0
628
IntegerListInterface master_index = index_set.getIndex(0);
629
630     // First, update the master index
631
for (int row_index = 0; row_index < row_count; ++row_index) {
632       // If this row isn't deleted, set the index information for it,
633
if (!recordDeleted(row_index)) {
634         // First add to master index
635
boolean inserted = master_index.uniqueInsertSort(row_index);
636         if (!inserted) {
637           throw new RuntimeException JavaDoc(
638                      "Assertion failed: Master index entry was duplicated.");
639         }
640       }
641     }
642
643     // Commit the master index
644
commitIndexSet(index_set);
645
646     // Now go ahead and build each index in this table
647
int index_count = index_set_def.indexCount();
648     for (int i = 0; i < index_count; ++i) {
649       buildIndex(i);
650     }
651
652   }
653
654   /**
655    * Builds the given index number (from the DataIndexSetDef). This must only
656    * be called when either, a) we are under a commit lock, or b) there is a
657    * guarentee that no concurrect access to the indexing information can happen
658    * (such as when we are creating the table).
659    * <p>
660    * NOTE: We assume that the index number in this table is blank before this
661    * method is called.
662    */

663   synchronized void buildIndex(final int index_number) throws IOException {
664     DataIndexSetDef index_set_def = getDataIndexSetDef();
665
666     IndexSet index_set = createIndexSet();
667
668     // Master index is always on index position 0
669
IntegerListInterface master_index = index_set.getIndex(0);
670     // A minimal TableDataSource for constructing the indexes
671
TableDataSource min_table_source = minimalTableDataSource(master_index);
672
673     // Set up schemes for the index,
674
SelectableScheme scheme = createSelectableSchemeForIndex(index_set,
675                                               min_table_source, index_number);
676
677     // Rebuild the entire index
678
int row_count = rawRowCount();
679     for (int row_index = 0; row_index < row_count; ++row_index) {
680
681       // If this row isn't deleted, set the index information for it,
682
if (!recordDeleted(row_index)) {
683         scheme.insert(row_index);
684       }
685
686     }
687
688     // Commit the index
689
commitIndexSet(index_set);
690
691   }
692
693
694
695
696   /**
697    * Adds a new transaction modification to this master table source. This
698    * information represents the information that was added/removed in the
699    * table in this transaction. The IndexSet object represents the changed
700    * index information to commit to this table.
701    * <p>
702    * It's guarenteed that 'commit_id' additions will be sequential.
703    */

704   synchronized void commitTransactionChange(long commit_id,
705                               MasterTableJournal change, IndexSet index_set) {
706     // ASSERT: Can't do this if source is read only.
707
if (isReadOnly()) {
708       throw new Error JavaDoc("Can't commit transaction journal, table is read only.");
709     }
710
711     change.setCommitID(commit_id);
712
713     try {
714
715       // Add this journal to the multi version table indices log
716
table_indices.addTransactionJournal(change);
717
718       // Write the modified index set to the index store
719
// (Updates the index file)
720
commitIndexSet(index_set);
721
722       // Update the state of the committed added data to the file system.
723
// (Updates data to the allocation file)
724
//
725
// ISSUE: This can add up to a lot of changes to the allocation file and
726
// the Java runtime could potentially be terminated in the middle of
727
// the update. If an interruption happens the allocation information
728
// may be incorrectly flagged. The type of corruption this would
729
// result in would be;
730
// + From an 'update' the updated record may disappear.
731
// + From a 'delete' the deleted record may not delete.
732
// + From an 'insert' the inserted record may not insert.
733
//
734
// Note, the possibility of this type of corruption occuring has been
735
// minimized as best as possible given the current architecture.
736
// Also note that is not possible for a table file to become corrupted
737
// beyond recovery from this issue.
738

739       int size = change.entries();
740       for (int i = 0; i < size; ++i) {
741         byte b = change.getCommand(i);
742         int row_index = change.getRowIndex(i);
743         // Was a row added or removed?
744
if (MasterTableJournal.isAddCommand(b)) {
745
746           // Record commit added
747
int old_type = writeRecordType(row_index, 0x010);
748           // Check the record was in an uncommitted state before we changed
749
// it.
750
if ((old_type & 0x0F0) != 0) {
751             writeRecordType(row_index, old_type & 0x0F0);
752             throw new Error JavaDoc("Record " + row_index + " of table " + this +
753                             " was not in an uncommitted state!");
754           }
755
756         }
757         else if (MasterTableJournal.isRemoveCommand(b)) {
758
759           // Record commit removed
760
int old_type = writeRecordType(row_index, 0x020);
761           // Check the record was in an added state before we removed it.
762
if ((old_type & 0x0F0) != 0x010) {
763             writeRecordType(row_index, old_type & 0x0F0);
764 // System.out.println(change);
765
throw new Error JavaDoc("Record " + row_index + " of table " + this +
766                             " was not in an added state!");
767           }
768           // Notify collector that this row has been marked as deleted.
769
garbage_collector.markRowAsDeleted(row_index);
770
771         }
772       }
773
774     }
775     catch (IOException e) {
776       Debug().writeException(e);
777       throw new Error JavaDoc("IO Error: " + e.getMessage());
778     }
779
780   }
781
782   /**
783    * Rolls back a transaction change in this table source. Any rows added
784    * to the table will be uncommited rows (type_key = 0). Those rows must be
785    * marked as committed deleted.
786    */

787   synchronized void rollbackTransactionChange(MasterTableJournal change) {
788
789     // ASSERT: Can't do this is source is read only.
790
if (isReadOnly()) {
791       throw new Error JavaDoc(
792                    "Can't rollback transaction journal, table is read only.");
793     }
794
795     // Any rows added in the journal are marked as committed deleted and the
796
// journal is then discarded.
797

798     try {
799       // Mark all rows in the data_store as appropriate to the changes.
800
int size = change.entries();
801       for (int i = 0; i < size; ++i) {
802         byte b = change.getCommand(i);
803         int row_index = change.getRowIndex(i);
804         // Make row as added or removed.
805
if (MasterTableJournal.isAddCommand(b)) {
806           // Record commit removed (we are rolling back remember).
807
// int old_type = data_store.writeRecordType(row_index + 1, 0x020);
808
int old_type = writeRecordType(row_index, 0x020);
809           // Check the record was in an uncommitted state before we changed
810
// it.
811
if ((old_type & 0x0F0) != 0) {
812 // data_store.writeRecordType(row_index + 1, old_type & 0x0F0);
813
writeRecordType(row_index, old_type & 0x0F0);
814             throw new Error JavaDoc("Record " + row_index + " was not in an " +
815                             "uncommitted state!");
816           }
817           // Notify collector that this row has been marked as deleted.
818
garbage_collector.markRowAsDeleted(row_index);
819         }
820         else if (MasterTableJournal.isRemoveCommand(b)) {
821           // Any journal entries marked as TABLE_REMOVE are ignored because
822
// we are rolling back. This means the row is not logically changed.
823
}
824       }
825
826       // The journal entry is discarded, the indices do not need to be updated
827
// to reflect this rollback.
828
}
829     catch (IOException e) {
830       Debug().writeException(e);
831       throw new Error JavaDoc("IO Error: " + e.getMessage());
832     }
833   }
834
835   /**
836    * Returns a MutableTableDataSource object that represents this data source
837    * at the time the given transaction started. Any modifications to the
838    * returned table are logged in the table journal.
839    * <p>
840    * This is a key method in this object because it allows us to get a data
841    * source that represents the data in the table before any modifications
842    * may have been committed.
843    */

844   MutableTableDataSource createTableDataSourceAtCommit(
845                                               SimpleTransaction transaction) {
846     return createTableDataSourceAtCommit(transaction,
847                                          new MasterTableJournal(getTableID()));
848   }
849
850   /**
851    * Returns a MutableTableDataSource object that represents this data source
852    * at the time the given transaction started, and also also makes any
853    * modifications that are described by the journal in the table.
854    * <p>
855    * This method is useful for merging the changes made by a transaction into
856    * a view of the table.
857    */

858   MutableTableDataSource createTableDataSourceAtCommit(
859                   SimpleTransaction transaction, MasterTableJournal journal) {
860     return new MMutableTableDataSource(transaction, journal);
861   }
862
863   // ---------- File IO level table modification ----------
864

865   /**
866    * Sets up the DataIndexSetDef object from the information set in this object.
867    * This will only setup a default IndexSetDef on the information in the
868    * DataTableDef.
869    */

870   protected synchronized void setupDataIndexSetDef() {
871     // Create the initial DataIndexSetDef object.
872
index_def = new DataIndexSetDef(table_def.getTableName());
873     for (int i = 0; i < table_def.columnCount(); ++i) {
874       DataTableColumnDef col_def = table_def.columnAt(i);
875       if (col_def.isIndexableType() &&
876           col_def.getIndexScheme().equals("InsertSearch")) {
877         index_def.addDataIndexDef(new DataIndexDef("ANON-COLUMN:" + i,
878                                   new String JavaDoc[] { col_def.getName() }, i + 1,
879                                   "BLIST", false));
880       }
881     }
882   }
883   
884   /**
885    * Sets up the DataTableDef. This would typically only ever be called from
886    * the 'create' method.
887    */

888   protected synchronized void setupDataTableDef(DataTableDef table_def) {
889
890     // Check table_id isn't too large.
891
if ((table_id & 0x0F0000000) != 0) {
892       throw new Error JavaDoc("'table_id' exceeds maximum possible keys.");
893     }
894
895     this.table_def = table_def;
896     
897     // The name of the table to create,
898
TableName table_name = table_def.getTableName();
899
900     // Create table indices
901
table_indices = new MultiVersionTableIndices(getSystem(),
902                                           table_name, table_def.columnCount());
903     // The column rid list cache
904
column_rid_list = new RIDList[table_def.columnCount()];
905
906     // Setup the DataIndexSetDef
907
setupDataIndexSetDef();
908   }
909
910   /**
911    * Loads the internal variables.
912    */

913   protected synchronized void loadInternal() {
914     // Set up the stat keys.
915
String JavaDoc table_name = table_def.getName();
916     String JavaDoc schema_name = table_def.getSchema();
917     String JavaDoc n = table_name;
918     if (schema_name.length() > 0) {
919       n = schema_name + "." + table_name;
920     }
921     root_lock_key = "MasterTableDataSource.RootLocks." + n;
922     total_hits_key = "MasterTableDataSource.Hits.Total." + n;
923     file_hits_key = "MasterTableDataSource.Hits.File." + n;
924     delete_hits_key = "MasterTableDataSource.Hits.Delete." + n;
925     insert_hits_key = "MasterTableDataSource.Hits.Insert." + n;
926
927     column_count = table_def.columnCount();
928
929     is_closed = false;
930
931   }
932
933   /**
934    * Returns true if this table source is closed.
935    */

936   synchronized boolean isClosed() {
937     return is_closed;
938   }
939
940   /**
941    * Returns true if the source is read only.
942    */

943   boolean isReadOnly() {
944     return system.readOnlyAccess();
945   }
946
947   /**
948    * Returns the StoreSystem object used to manage stores in the persistence
949    * system.
950    */

951   protected StoreSystem storeSystem() {
952     return store_system;
953   }
954   
955   /**
956    * Adds a new row to this table and returns an index that is used to
957    * reference this row by the 'getCellContents' method.
958    * <p>
959    * Note that this method will not effect the master index or column schemes.
960    * This is a low level mechanism for adding unreferenced data into a
961    * conglomerate. The data is referenced by committing the change where it
962    * eventually migrates into the master index and schemes.
963    */

964   int addRow(RowData data) throws IOException {
965     int row_number;
966     
967     synchronized(this) {
968
969       row_number = internalAddRow(data);
970       
971     } // synchronized
972

973     // Update stats
974
getSystem().stats().increment(insert_hits_key);
975
976     // Return the record index of the new data in the table
977
return row_number;
978   }
979   
980   /**
981    * Actually deletes the row from the table. This is a permanent removal of
982    * the row from the table. After this method is called, the row can not
983    * be retrieved again. This is generally only used by the row garbage
984    * collector.
985    * <p>
986    * There is no checking in this method.
987    */

988   private synchronized void doHardRowRemove(int row_index) throws IOException {
989
990     // If we have a rid_list for any of the columns, then update the indexing
991
// there,
992
for (int i = 0; i < column_count; ++i) {
993       RIDList rid_list = column_rid_list[i];
994       if (rid_list != null) {
995         rid_list.removeRID(row_index);
996       }
997     }
998
999     // Internally delete the row,
1000
internalDeleteRow(row_index);
1001
1002    // Update stats
1003
system.stats().increment(delete_hits_key);
1004
1005  }
1006
1007  /**
1008   * Permanently removes a row from this table. This must only be used when
1009   * it is determined that a transaction does not reference this row, and
1010   * that an open result set does not reference this row. This will remove
1011   * the row permanently from the underlying file representation. Calls to
1012   * 'getCellContents(col, row)' where row is deleted will be undefined after
1013   * this method is called.
1014   * <p>
1015   * Note that the removed row must not be contained within the master index,
1016   * or be referenced by the index schemes, or be referenced in the
1017   * transaction modification list.
1018   */

1019  synchronized void hardRemoveRow(final int record_index) throws IOException {
1020    // ASSERTION: We are not under a root lock.
1021
if (!isRootLocked()) {
1022// int type_key = data_store.readRecordType(record_index + 1);
1023
int type_key = readRecordType(record_index);
1024      // Check this record is marked as committed removed.
1025
if ((type_key & 0x0F0) == 0x020) {
1026        doHardRowRemove(record_index);
1027      }
1028      else {
1029        throw new Error JavaDoc(
1030                    "Row isn't marked as committed removed: " + record_index);
1031      }
1032    }
1033    else {
1034      throw new Error JavaDoc("Assertion failed: " +
1035                      "Can't remove row, table is under a root lock.");
1036    }
1037  }
1038
1039  /**
1040   * Checks the given record index, and if it's possible to reclaim it then
1041   * it does so here. Rows are only removed if they are marked as committed
1042   * removed.
1043   */

1044  synchronized boolean hardCheckAndReclaimRow(final int record_index)
1045                                                          throws IOException {
1046    // ASSERTION: We are not under a root lock.
1047
if (!isRootLocked()) {
1048      // Row already deleted?
1049
if (!recordDeleted(record_index)) {
1050        int type_key = readRecordType(record_index);
1051        // Check this record is marked as committed removed.
1052
if ((type_key & 0x0F0) == 0x020) {
1053// System.out.println("[" + getName() + "] " +
1054
// "Hard Removing: " + record_index);
1055
doHardRowRemove(record_index);
1056          return true;
1057        }
1058      }
1059      return false;
1060    }
1061    else {
1062      throw new Error JavaDoc("Assertion failed: " +
1063                      "Can't remove row, table is under a root lock.");
1064    }
1065  }
1066
1067  /**
1068   * Returns the record type of the given record index. Returns a type that
1069   * is compatible with RawDiagnosticTable record type.
1070   */

1071  synchronized int recordTypeInfo(int record_index) throws IOException {
1072// ++record_index;
1073
if (recordDeleted(record_index)) {
1074      return RawDiagnosticTable.DELETED;
1075    }
1076    int type_key = readRecordType(record_index) & 0x0F0;
1077    if (type_key == 0) {
1078      return RawDiagnosticTable.UNCOMMITTED;
1079    }
1080    else if (type_key == 0x010) {
1081      return RawDiagnosticTable.COMMITTED_ADDED;
1082    }
1083    else if (type_key == 0x020) {
1084      return RawDiagnosticTable.COMMITTED_REMOVED;
1085    }
1086    return RawDiagnosticTable.RECORD_STATE_ERROR;
1087
1088  }
1089
1090  /**
1091   * This is called by the 'open' method. It performs a scan of the records
1092   * and marks any rows that are uncommitted as deleted. It also checks
1093   * that the row is not within the master index.
1094   */

1095  protected synchronized void doOpeningScan() throws IOException {
1096    long in_time = System.currentTimeMillis();
1097
1098    // ASSERTION: No root locks and no pending transaction changes,
1099
// VERY important we assert there's no pending transactions.
1100
if (isRootLocked() || hasTransactionChangesPending()) {
1101      // This shouldn't happen if we are calling from 'open'.
1102
throw new RuntimeException JavaDoc(
1103                  "Odd, we are root locked or have pending journal changes.");
1104    }
1105
1106    // This is pointless if we are in read only mode.
1107
if (!isReadOnly()) {
1108      // A journal of index changes during this scan...
1109
MasterTableJournal journal = new MasterTableJournal();
1110
1111      // Get the master index of rows in this table
1112
IndexSet index_set = createIndexSet();
1113      IntegerListInterface master_index = index_set.getIndex(0);
1114
1115      // NOTE: We assume the index information is correct and that the
1116
// allocation information is potentially bad.
1117

1118      int row_count = rawRowCount();
1119      for (int i = 0 ; i < row_count; ++i) {
1120        // Is this record marked as deleted?
1121
if (!recordDeleted(i)) {
1122          // Get the type flags for this record.
1123
int type = recordTypeInfo(i);
1124          // Check if this record is marked as committed removed, or is an
1125
// uncommitted record.
1126
if (type == RawDiagnosticTable.COMMITTED_REMOVED ||
1127              type == RawDiagnosticTable.UNCOMMITTED) {
1128            // Check it's not in the master index...
1129
if (!master_index.contains(i)) {
1130              // Delete it.
1131
doHardRowRemove(i);
1132            }
1133            else {
1134              Debug().write(Lvl.ERROR, this,
1135                          "Inconsistant: Row is indexed but marked as " +
1136                          "removed or uncommitted.");
1137              Debug().write(Lvl.ERROR, this,
1138                          "Row: " + i + " Type: " + type +
1139                          " Table: " + getTableName());
1140              // Mark the row as committed added because it is in the index.
1141
writeRecordType(i, 0x010);
1142
1143            }
1144          }
1145          else {
1146            // Must be committed added. Check it's indexed.
1147
if (!master_index.contains(i)) {
1148              // Not indexed, so data is inconsistant.
1149
Debug().write(Lvl.ERROR, this,
1150                 "Inconsistant: Row committed added but not in master index.");
1151              Debug().write(Lvl.ERROR, this,
1152                          "Row: " + i + " Type: " + type +
1153                          " Table: " + getTableName());
1154              // Mark the row as committed removed because it is not in the
1155
// index.
1156
writeRecordType(i, 0x020);
1157
1158            }
1159          }
1160        }
1161        else { // if deleted
1162
// Check this record isn't in the master index.
1163
if (master_index.contains(i)) {
1164            // It's in the master index which is wrong! We should remake the
1165
// indices.
1166
Debug().write(Lvl.ERROR, this,
1167                        "Inconsistant: Row is removed but in index.");
1168            Debug().write(Lvl.ERROR, this,
1169                        "Row: " + i + " Table: " + getTableName());
1170            // Mark the row as committed added because it is in the index.
1171
writeRecordType(i, 0x010);
1172
1173          }
1174        }
1175      } // for (int i = 0 ; i < row_count; ++i)
1176

1177      // Dispose the index set
1178
index_set.dispose();
1179
1180    }
1181
1182    long bench_time = System.currentTimeMillis() - in_time;
1183    if (Debug().isInterestedIn(Lvl.INFORMATION)) {
1184      Debug().write(Lvl.INFORMATION, this,
1185         "Opening scan for " + toString() + " (" + getTableName() + ") took " +
1186         bench_time + "ms.");
1187    }
1188
1189  }
1190
1191
1192  /**
1193   * Returns an implementation of RawDiagnosticTable that we can use to
1194   * diagnose problems with the data in this source.
1195   */

1196  RawDiagnosticTable getRawDiagnosticTable() {
1197    return new MRawDiagnosticTable();
1198  }
1199
1200  
1201  /**
1202   * Returns the cell contents of the given cell in the table. This will
1203   * look up the cell in the file if it can't be found in the cell cache. This
1204   * method is undefined if row has been removed or was not returned by
1205   * the 'addRow' method.
1206   */

1207  TObject getCellContents(int column, int row) {
1208    if (row < 0) {
1209      throw new Error JavaDoc("'row' is < 0");
1210    }
1211    return internalGetCellContents(column, row);
1212  }
1213  
1214  /**
1215   * Grabs a root lock on this table.
1216   * <p>
1217   * While a MasterTableDataSource has at least 1 root lock, it may not
1218   * reclaim deleted space in the data store. A root lock means that data
1219   * is still being pointed to in this file (even possibly committed deleted
1220   * data).
1221   */

1222  synchronized void addRootLock() {
1223    system.stats().increment(root_lock_key);
1224    ++root_lock;
1225  }
1226
1227  /**
1228   * Removes a root lock from this table.
1229   * <p>
1230   * While a MasterTableDataSource has at least 1 root lock, it may not
1231   * reclaim deleted space in the data store. A root lock means that data
1232   * is still being pointed to in this file (even possibly committed deleted
1233   * data).
1234   */

1235  synchronized void removeRootLock() {
1236    if (!is_closed) {
1237      system.stats().decrement(root_lock_key);
1238      if (root_lock == 0) {
1239        throw new Error JavaDoc("Too many root locks removed!");
1240      }
1241      --root_lock;
1242      // If the last lock is removed, schedule a possible collection.
1243
if (root_lock == 0) {
1244        checkForCleanup();
1245      }
1246    }
1247  }
1248
1249  /**
1250   * Returns true if the table is currently under a root lock (has 1 or more
1251   * root locks on it).
1252   */

1253  synchronized boolean isRootLocked() {
1254    return root_lock > 0;
1255  }
1256
1257  /**
1258   * Clears all root locks on the table. Should only be used during cleanup
1259   * of the table and will by definition invalidate the table.
1260   */

1261  protected synchronized void clearAllRootLocks() {
1262    root_lock = 0;
1263  }
1264
1265  /**
1266   * Checks to determine if it is safe to clean up any resources in the
1267   * table, and if it is safe to do so, the space is reclaimed.
1268   */

1269  abstract void checkForCleanup();
1270
1271  
1272  synchronized String JavaDoc transactionChangeString() {
1273    return table_indices.transactionChangeString();
1274  }
1275
1276  /**
1277   * Returns true if this table has any journal modifications that have not
1278   * yet been incorporated into master index.
1279   */

1280  synchronized boolean hasTransactionChangesPending() {
1281    return table_indices.hasTransactionChangesPending();
1282  }
1283
1284
1285
1286
1287
1288  // ---------- Inner classes ----------
1289

1290  /**
1291   * A RawDiagnosticTable implementation that provides direct access to the
1292   * root data of this table source bypassing any indexing schemes. This
1293   * interface allows for the inspection and repair of data files.
1294   */

1295  private final class MRawDiagnosticTable implements RawDiagnosticTable {
1296
1297    // ---------- Implemented from RawDiagnosticTable -----------
1298

1299    public int physicalRecordCount() {
1300      try {
1301        return rawRowCount();
1302      }
1303      catch (IOException e) {
1304        throw new Error JavaDoc(e.getMessage());
1305      }
1306    }
1307
1308    public DataTableDef getDataTableDef() {
1309      return MasterTableDataSource.this.getDataTableDef();
1310    }
1311
1312    public int recordState(int record_index) {
1313      try {
1314        return recordTypeInfo(record_index);
1315      }
1316      catch (IOException e) {
1317        throw new Error JavaDoc(e.getMessage());
1318      }
1319    }
1320
1321    public int recordSize(int record_index) {
1322      return -1;
1323    }
1324
1325    public TObject getCellContents(int column, int record_index) {
1326      return MasterTableDataSource.this.getCellContents(column,
1327                                                                record_index);
1328    }
1329
1330    public String JavaDoc recordMiscInformation(int record_index) {
1331      return null;
1332    }
1333
1334  }
1335
1336  /**
1337   * A MutableTableDataSource object as returned by the
1338   * 'createTableDataSourceAtCommit' method.
1339   * <p>
1340   * NOTE: This object is NOT thread-safe and it is assumed any use of this
1341   * object will be thread exclusive. This is okay because multiple
1342   * instances of this object can be created on the same MasterTableDataSource
1343   * if multi-thread access to a MasterTableDataSource is desirable.
1344   */

1345  private final class MMutableTableDataSource
1346                                           implements MutableTableDataSource {
1347
1348    /**
1349     * The Transaction object that this MutableTableDataSource was
1350     * generated from. This reference should be used only to query
1351     * database constraint information.
1352     */

1353    private SimpleTransaction transaction;
1354
1355    /**
1356     * True if the transaction is read-only.
1357     */

1358    private boolean tran_read_only;
1359    
1360    /**
1361     * The name of this table.
1362     */

1363    private TableName table_name;
1364    
1365    /**
1366     * The 'recovery point' to which the row index in this table source has
1367     * rebuilt to.
1368     */

1369    private int row_list_rebuild;
1370
1371    /**
1372     * The index that represents the rows that are within this
1373     * table data source within this transaction.
1374     */

1375    private IntegerListInterface row_list;
1376
1377    /**
1378     * The 'recovery point' to which the schemes in this table source have
1379     * rebuilt to.
1380     */

1381    private int[] scheme_rebuilds;
1382
1383    /**
1384     * The IndexSet for this mutable table source.
1385     */

1386    private IndexSet index_set;
1387
1388    /**
1389     * The SelectableScheme array that represents the schemes for the
1390     * columns within this transaction.
1391     */

1392    private SelectableScheme[] column_schemes;
1393
1394    /**
1395     * A journal of changes to this source since it was created.
1396     */

1397    private MasterTableJournal table_journal;
1398
1399    /**
1400     * The last time any changes to the journal were check for referential
1401     * integrity violations.
1402     */

1403    private int last_entry_ri_check;
1404
1405    /**
1406     * Constructs the data source.
1407     */

1408    public MMutableTableDataSource(SimpleTransaction transaction,
1409                                   MasterTableJournal journal) {
1410      this.transaction = transaction;
1411      this.index_set =
1412              transaction.getIndexSetForTable(MasterTableDataSource.this);
1413      int col_count = getDataTableDef().columnCount();
1414      this.table_name = getDataTableDef().getTableName();
1415      this.tran_read_only = transaction.isReadOnly();
1416      row_list_rebuild = 0;
1417      scheme_rebuilds = new int[col_count];
1418      column_schemes = new SelectableScheme[col_count];
1419      table_journal = journal;
1420      last_entry_ri_check = table_journal.entries();
1421    }
1422
1423    /**
1424     * Executes an update referential action. If the update action is
1425     * "NO ACTION", and the constraint is INITIALLY_IMMEDIATE, and the new key
1426     * doesn't exist in the referral table, an exception is thrown.
1427     */

1428    private void executeUpdateReferentialAction(
1429                              Transaction.ColumnGroupReference constraint,
1430                              TObject[] original_key, TObject[] new_key,
1431                              QueryContext context) {
1432
1433      final String JavaDoc update_rule = constraint.update_rule;
1434      if (update_rule.equals("NO ACTION") &&
1435          constraint.deferred != Transaction.INITIALLY_IMMEDIATE) {
1436        // Constraint check is deferred
1437
return;
1438      }
1439
1440      // So either update rule is not NO ACTION, or if it is we are initially
1441
// immediate.
1442
MutableTableDataSource key_table =
1443                               transaction.getTable(constraint.key_table_name);
1444      DataTableDef table_def = key_table.getDataTableDef();
1445      int[] key_cols = TableDataConglomerate.findColumnIndices(
1446                                            table_def, constraint.key_columns);
1447      IntegerVector key_entries =
1448             TableDataConglomerate.findKeys(key_table, key_cols, original_key);
1449
1450      // Are there keys effected?
1451
if (key_entries.size() > 0) {
1452        if (update_rule.equals("NO ACTION")) {
1453          // Throw an exception;
1454
throw new DatabaseConstraintViolationException(
1455              DatabaseConstraintViolationException.FOREIGN_KEY_VIOLATION,
1456              TableDataConglomerate.deferredString(constraint.deferred) +
1457              " foreign key constraint violation on update (" +
1458              constraint.name + ") Columns = " +
1459              constraint.key_table_name.toString() + "( " +
1460              TableDataConglomerate.stringColumnList(constraint.key_columns) +
1461              " ) -> " + constraint.ref_table_name.toString() + "( " +
1462              TableDataConglomerate.stringColumnList(constraint.ref_columns) +
1463              " )");
1464        }
1465        else {
1466          // Perform a referential action on each updated key
1467
int sz = key_entries.size();
1468          for (int i = 0; i < sz; ++i) {
1469            int row_index = key_entries.intAt(i);
1470            RowData row_data = new RowData(key_table);
1471            row_data.setFromRow(row_index);
1472            if (update_rule.equals("CASCADE")) {
1473              // Update the keys
1474
for (int n = 0; n < key_cols.length; ++n) {
1475                row_data.setColumnData(key_cols[n], new_key[n]);
1476              }
1477              key_table.updateRow(row_index, row_data);
1478            }
1479            else if (update_rule.equals("SET NULL")) {
1480              for (int n = 0; n < key_cols.length; ++n) {
1481                row_data.setColumnToNull(key_cols[n]);
1482              }
1483              key_table.updateRow(row_index, row_data);
1484            }
1485            else if (update_rule.equals("SET DEFAULT")) {
1486              for (int n = 0; n < key_cols.length; ++n) {
1487                row_data.setColumnToDefault(key_cols[n], context);
1488              }
1489              key_table.updateRow(row_index, row_data);
1490            }
1491            else {
1492              throw new RuntimeException JavaDoc(
1493                       "Do not understand referential action: " + update_rule);
1494            }
1495          }
1496          // Check referential integrity of modified table,
1497
key_table.constraintIntegrityCheck();
1498        }
1499      }
1500    }
1501
1502    /**
1503     * Executes a delete referential action. If the delete action is
1504     * "NO ACTION", and the constraint is INITIALLY_IMMEDIATE, and the new key
1505     * doesn't exist in the referral table, an exception is thrown.
1506     */

1507    private void executeDeleteReferentialAction(
1508                              Transaction.ColumnGroupReference constraint,
1509                              TObject[] original_key, QueryContext context) {
1510
1511      final String JavaDoc delete_rule = constraint.delete_rule;
1512      if (delete_rule.equals("NO ACTION") &&
1513          constraint.deferred != Transaction.INITIALLY_IMMEDIATE) {
1514        // Constraint check is deferred
1515
return;
1516      }
1517
1518      // So either delete rule is not NO ACTION, or if it is we are initially
1519
// immediate.
1520
MutableTableDataSource key_table =
1521                               transaction.getTable(constraint.key_table_name);
1522      DataTableDef table_def = key_table.getDataTableDef();
1523      int[] key_cols = TableDataConglomerate.findColumnIndices(
1524                                            table_def, constraint.key_columns);
1525      IntegerVector key_entries =
1526             TableDataConglomerate.findKeys(key_table, key_cols, original_key);
1527
1528      // Are there keys effected?
1529
if (key_entries.size() > 0) {
1530        if (delete_rule.equals("NO ACTION")) {
1531          // Throw an exception;
1532
throw new DatabaseConstraintViolationException(
1533              DatabaseConstraintViolationException.FOREIGN_KEY_VIOLATION,
1534              TableDataConglomerate.deferredString(constraint.deferred) +
1535              " foreign key constraint violation on delete (" +
1536              constraint.name + ") Columns = " +
1537              constraint.key_table_name.toString() + "( " +
1538              TableDataConglomerate.stringColumnList(constraint.key_columns) +
1539              " ) -> " + constraint.ref_table_name.toString() + "( " +
1540              TableDataConglomerate.stringColumnList(constraint.ref_columns) +
1541              " )");
1542        }
1543        else {
1544          // Perform a referential action on each updated key
1545
int sz = key_entries.size();
1546          for (int i = 0; i < sz; ++i) {
1547            int row_index = key_entries.intAt(i);
1548            RowData row_data = new RowData(key_table);
1549            row_data.setFromRow(row_index);
1550            if (delete_rule.equals("CASCADE")) {
1551              // Cascade the removal of the referenced rows
1552
key_table.removeRow(row_index);
1553            }
1554            else if (delete_rule.equals("SET NULL")) {
1555              for (int n = 0; n < key_cols.length; ++n) {
1556                row_data.setColumnToNull(key_cols[n]);
1557              }
1558              key_table.updateRow(row_index, row_data);
1559            }
1560            else if (delete_rule.equals("SET DEFAULT")) {
1561              for (int n = 0; n < key_cols.length; ++n) {
1562                row_data.setColumnToDefault(key_cols[n], context);
1563              }
1564              key_table.updateRow(row_index, row_data);
1565            }
1566            else {
1567              throw new RuntimeException JavaDoc(
1568                       "Do not understand referential action: " + delete_rule);
1569            }
1570          }
1571          // Check referential integrity of modified table,
1572
key_table.constraintIntegrityCheck();
1573        }
1574      }
1575    }
1576    
1577    /**
1578     * Returns the entire row list for this table. This will request this
1579     * information from the master source.
1580     */

1581    private IntegerListInterface getRowIndexList() {
1582      if (row_list == null) {
1583        row_list = index_set.getIndex(0);
1584      }
1585      return row_list;
1586    }
1587
1588    /**
1589     * Ensures that the row list is as current as the latest journal change.
1590     * We can be assured that when this is called, no journal changes will
1591     * occur concurrently. However we still need to synchronize because
1592     * multiple reads are valid.
1593     */

1594    private void ensureRowIndexListCurrent() {
1595      int rebuild_index = row_list_rebuild;
1596      int journal_count = table_journal.entries();
1597      while (rebuild_index < journal_count) {
1598        byte command = table_journal.getCommand(rebuild_index);
1599        int row_index = table_journal.getRowIndex(rebuild_index);
1600        if (MasterTableJournal.isAddCommand(command)) {
1601          // Add to 'row_list'.
1602
boolean b = getRowIndexList().uniqueInsertSort(row_index);
1603          if (b == false) {
1604            throw new Error JavaDoc(
1605                  "Row index already used in this table (" + row_index + ")");
1606          }
1607        }
1608        else if (MasterTableJournal.isRemoveCommand(command)) {
1609          // Remove from 'row_list'
1610
boolean b = getRowIndexList().removeSort(row_index);
1611          if (b == false) {
1612            throw new Error JavaDoc("Row index removed that wasn't in this table!");
1613          }
1614        }
1615        else {
1616          throw new Error JavaDoc("Unrecognised journal command.");
1617        }
1618        ++rebuild_index;
1619      }
1620      // It's now current (row_list_rebuild == journal_count);
1621
row_list_rebuild = rebuild_index;
1622    }
1623
1624    /**
1625     * Ensures that the scheme column index is as current as the latest
1626     * journal change.
1627     */

1628    private void ensureColumnSchemeCurrent(int column) {
1629      SelectableScheme scheme = column_schemes[column];
1630      // NOTE: We should be assured that no write operations can occur over
1631
// this section of code because writes are exclusive operations
1632
// within a transaction.
1633
// Are there journal entries pending on this scheme since?
1634
int rebuild_index = scheme_rebuilds[column];
1635      int journal_count = table_journal.entries();
1636      while (rebuild_index < journal_count) {
1637        byte command = table_journal.getCommand(rebuild_index);
1638        int row_index = table_journal.getRowIndex(rebuild_index);
1639        if (MasterTableJournal.isAddCommand(command)) {
1640          scheme.insert(row_index);
1641        }
1642        else if (MasterTableJournal.isRemoveCommand(command)) {
1643          scheme.remove(row_index);
1644        }
1645        else {
1646          throw new Error JavaDoc("Unrecognised journal command.");
1647        }
1648        ++rebuild_index;
1649      }
1650      scheme_rebuilds[column] = rebuild_index;
1651    }
1652
1653    // ---------- Implemented from MutableTableDataSource ----------
1654

1655    public TransactionSystem getSystem() {
1656      return MasterTableDataSource.this.getSystem();
1657    }
1658
1659    public DataTableDef getDataTableDef() {
1660      return MasterTableDataSource.this.getDataTableDef();
1661    }
1662
1663    public int getRowCount() {
1664      // Ensure the row list is up to date.
1665
ensureRowIndexListCurrent();
1666      return getRowIndexList().size();
1667    }
1668
1669    public RowEnumeration rowEnumeration() {
1670      // Ensure the row list is up to date.
1671
ensureRowIndexListCurrent();
1672      // Get an iterator across the row list.
1673
final IntegerIterator iterator = getRowIndexList().iterator();
1674      // Wrap it around a RowEnumeration object.
1675
return new RowEnumeration() {
1676        public boolean hasMoreRows() {
1677          return iterator.hasNext();
1678        }
1679        public int nextRowIndex() {
1680          return iterator.next();
1681        }
1682      };
1683    }
1684
1685    public TObject getCellContents(int column, int row) {
1686      return MasterTableDataSource.this.getCellContents(column, row);
1687    }
1688
1689    // NOTE: Returns an immutable version of the scheme...
1690
public SelectableScheme getColumnScheme(int column) {
1691      SelectableScheme scheme = column_schemes[column];
1692      // Cache the scheme in this object.
1693
if (scheme == null) {
1694        scheme = createSelectableSchemeForColumn(index_set, this, column);
1695        column_schemes[column] = scheme;
1696      }
1697
1698      // Update the underlying scheme to the most current version.
1699
ensureColumnSchemeCurrent(column);
1700      
1701      return scheme;
1702    }
1703
1704    // ---------- Table Modification ----------
1705

1706    public int addRow(RowData row_data) {
1707
1708      // Check the transaction isn't read only.
1709
if (tran_read_only) {
1710        throw new RuntimeException JavaDoc("Transaction is read only.");
1711      }
1712
1713      // Check this isn't a read only source
1714
if (isReadOnly()) {
1715        throw new Error JavaDoc("Can not add row - table is read only.");
1716      }
1717
1718      // Add to the master.
1719
int row_index;
1720      try {
1721        row_index = MasterTableDataSource.this.addRow(row_data);
1722      }
1723      catch (IOException e) {
1724        Debug().writeException(e);
1725        throw new Error JavaDoc("IO Error: " + e.getMessage());
1726      }
1727
1728      // Note this doesn't need to be synchronized because we are exclusive on
1729
// this table.
1730
// Add this change to the table journal.
1731
table_journal.addEntry(MasterTableJournal.TABLE_ADD, row_index);
1732
1733      return row_index;
1734    }
1735
1736    public void removeRow(int row_index) {
1737
1738      // Check the transaction isn't read only.
1739
if (tran_read_only) {
1740        throw new RuntimeException JavaDoc("Transaction is read only.");
1741      }
1742
1743      // Check this isn't a read only source
1744
if (isReadOnly()) {
1745        throw new Error JavaDoc("Can not remove row - table is read only.");
1746      }
1747
1748      // NOTE: This must <b>NOT</b> call 'removeRow' in MasterTableDataSource.
1749
// We do not want to delete a row permanently from the underlying
1750
// file because the transaction using this data source may yet decide
1751
// to roll back the change and not delete the row.
1752

1753      // Note this doesn't need to be synchronized because we are exclusive on
1754
// this table.
1755
// Add this change to the table journal.
1756
table_journal.addEntry(MasterTableJournal.TABLE_REMOVE, row_index);
1757
1758    }
1759
1760    public int updateRow(int row_index, RowData row_data) {
1761
1762      // Check the transaction isn't read only.
1763
if (tran_read_only) {
1764        throw new RuntimeException JavaDoc("Transaction is read only.");
1765      }
1766
1767      // Check this isn't a read only source
1768
if (isReadOnly()) {
1769        throw new Error JavaDoc("Can not update row - table is read only.");
1770      }
1771
1772      // Note this doesn't need to be synchronized because we are exclusive on
1773
// this table.
1774
// Add this change to the table journal.
1775
table_journal.addEntry(MasterTableJournal.TABLE_UPDATE_REMOVE, row_index);
1776
1777      // Add to the master.
1778
int new_row_index;
1779      try {
1780        new_row_index = MasterTableDataSource.this.addRow(row_data);
1781      }
1782      catch (IOException e) {
1783        Debug().writeException(e);
1784        throw new Error JavaDoc("IO Error: " + e.getMessage());
1785      }
1786
1787      // Note this doesn't need to be synchronized because we are exclusive on
1788
// this table.
1789
// Add this change to the table journal.
1790
table_journal.addEntry(MasterTableJournal.TABLE_UPDATE_ADD, new_row_index);
1791
1792      return new_row_index;
1793    }
1794
1795
1796    public void flushIndexChanges() {
1797      ensureRowIndexListCurrent();
1798      // This will flush all of the column schemes
1799
for (int i = 0; i < column_schemes.length; ++i) {
1800        getColumnScheme(i);
1801      }
1802    }
1803    
1804    public void constraintIntegrityCheck() {
1805      try {
1806
1807        // Early exit condition
1808
if (last_entry_ri_check == table_journal.entries()) {
1809          return;
1810        }
1811        
1812        // This table name
1813
DataTableDef table_def = getDataTableDef();
1814        TableName table_name = table_def.getTableName();
1815        QueryContext context =
1816                   new SystemQueryContext(transaction, table_name.getSchema());
1817
1818        // Are there any added, deleted or updated entries in the journal since
1819
// we last checked?
1820
IntegerVector rows_updated = new IntegerVector();
1821        IntegerVector rows_deleted = new IntegerVector();
1822        IntegerVector rows_added = new IntegerVector();
1823
1824        int size = table_journal.entries();
1825        for (int i = last_entry_ri_check; i < size; ++i) {
1826          byte tc = table_journal.getCommand(i);
1827          int row_index = table_journal.getRowIndex(i);
1828          if (tc == MasterTableJournal.TABLE_REMOVE ||
1829              tc == MasterTableJournal.TABLE_UPDATE_REMOVE) {
1830            rows_deleted.addInt(row_index);
1831            // If this is in the rows_added list, remove it from rows_added
1832
int ra_i = rows_added.indexOf(row_index);
1833            if (ra_i != -1) {
1834              rows_added.removeIntAt(ra_i);
1835            }
1836          }
1837          else if (tc == MasterTableJournal.TABLE_ADD ||
1838                   tc == MasterTableJournal.TABLE_UPDATE_ADD) {
1839            rows_added.addInt(row_index);
1840          }
1841          
1842          if (tc == MasterTableJournal.TABLE_UPDATE_REMOVE) {
1843            rows_updated.addInt(row_index);
1844          }
1845          else if (tc == MasterTableJournal.TABLE_UPDATE_ADD) {
1846            rows_updated.addInt(row_index);
1847          }
1848        }
1849
1850        // Were there any updates or deletes?
1851
if (rows_deleted.size() > 0) {
1852          // Get all references on this table
1853
Transaction.ColumnGroupReference[] foreign_constraints =
1854               Transaction.queryTableImportedForeignKeyReferences(transaction,
1855                                                                  table_name);
1856
1857          // For each foreign constraint
1858
for (int n = 0; n < foreign_constraints.length; ++n) {
1859            Transaction.ColumnGroupReference constraint =
1860                                                       foreign_constraints[n];
1861            // For each deleted/updated record in the table,
1862
for (int i = 0; i < rows_deleted.size(); ++i) {
1863              int row_index = rows_deleted.intAt(i);
1864              // What was the key before it was updated/deleted
1865
int[] cols = TableDataConglomerate.findColumnIndices(
1866                                            table_def, constraint.ref_columns);
1867              TObject[] original_key = new TObject[cols.length];
1868              int null_count = 0;
1869              for (int p = 0; p < cols.length; ++p) {
1870                original_key[p] = getCellContents(cols[p], row_index);
1871                if (original_key[p].isNull()) {
1872                  ++null_count;
1873                }
1874              }
1875              // Check the original key isn't null
1876
if (null_count != cols.length) {
1877                // Is is an update?
1878
int update_index = rows_updated.indexOf(row_index);
1879                if (update_index != -1) {
1880                  // Yes, this is an update
1881
int row_index_add = rows_updated.intAt(update_index + 1);
1882                  // It must be an update, so first see if the change caused any
1883
// of the keys to change.
1884
boolean key_changed = false;
1885                  TObject[] key_updated_to = new TObject[cols.length];
1886                  for (int p = 0; p < cols.length; ++p) {
1887                    key_updated_to[p] = getCellContents(cols[p], row_index_add);
1888                    if (original_key[p].compareTo(key_updated_to[p]) != 0) {
1889                      key_changed = true;
1890                    }
1891                  }
1892                  if (key_changed) {
1893                    // Allow the delete, and execute the action,
1894
// What did the key update to?
1895
executeUpdateReferentialAction(constraint,
1896                                        original_key, key_updated_to, context);
1897                  }
1898                  // If the key didn't change, we don't need to do anything.
1899
}
1900                else {
1901                  // No, so it must be a delete,
1902
// This will look at the referencee table and if it contains
1903
// the key, work out what to do with it.
1904
executeDeleteReferentialAction(constraint, original_key,
1905                                                 context);
1906                }
1907
1908              } // If the key isn't null
1909

1910            } // for each deleted rows
1911

1912          } // for each foreign key reference to this table
1913

1914        }
1915
1916        // Were there any rows added (that weren't deleted)?
1917
if (rows_added.size() > 0) {
1918          int[] row_indices = rows_added.toIntArray();
1919
1920          // Check for any field constraint violations in the added rows
1921
TableDataConglomerate.checkFieldConstraintViolations(
1922                                               transaction, this, row_indices);
1923          // Check this table, adding the given row_index, immediate
1924
TableDataConglomerate.checkAddConstraintViolations(
1925              transaction, this,
1926              row_indices, Transaction.INITIALLY_IMMEDIATE);
1927        }
1928
1929      }
1930      catch (DatabaseConstraintViolationException e) {
1931
1932        // If a constraint violation, roll back the changes since the last
1933
// check.
1934
int rollback_point = table_journal.entries() - last_entry_ri_check;
1935        if (row_list_rebuild <= rollback_point) {
1936          table_journal.rollbackEntries(rollback_point);
1937        }
1938        else {
1939          System.out.println(
1940             "WARNING: rebuild_pointer is after rollback point so we can't " +
1941             "rollback to the point before the constraint violation.");
1942        }
1943
1944        throw e;
1945
1946      }
1947      finally {
1948        // Make sure we update the 'last_entry_ri_check' variable
1949
last_entry_ri_check = table_journal.entries();
1950      }
1951
1952    }
1953    
1954    public MasterTableJournal getJournal() {
1955      return table_journal;
1956    }
1957
1958    public void dispose() {
1959      // Dispose and invalidate the schemes
1960
// This is really a safety measure to ensure the schemes can't be
1961
// used outside the scope of the lifetime of this object.
1962
for (int i = 0; i < column_schemes.length; ++i) {
1963        SelectableScheme scheme = column_schemes[i];
1964        if (scheme != null) {
1965          scheme.dispose();
1966          column_schemes[i] = null;
1967        }
1968      }
1969      row_list = null;
1970      table_journal = null;
1971      scheme_rebuilds = null;
1972      index_set = null;
1973      transaction = null;
1974    }
1975
1976    public void addRootLock() {
1977      MasterTableDataSource.this.addRootLock();
1978    }
1979
1980    public void removeRootLock() {
1981      MasterTableDataSource.this.removeRootLock();
1982    }
1983
1984  }
1985
1986}
1987
Popular Tags