KickJava   Java API By Example, From Geeks To Geeks.

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


1 /**
2  * com.mckoi.database.DataTable 08 Mar 1998
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 com.mckoi.database.global.SQLTypes;
28 import com.mckoi.util.IntegerVector;
29 import com.mckoi.debug.*;
30 import java.math.BigDecimal JavaDoc;
31 import java.util.Vector JavaDoc;
32 import java.util.ArrayList JavaDoc;
33 import java.io.IOException JavaDoc;
34 import java.io.OutputStream JavaDoc;
35 import java.io.InputStream JavaDoc;
36 import java.io.File JavaDoc;
37
38 /**
39  * DataTable is a wrapper for a MutableTableDataSource that fits into the
40  * query hierarchy level. A DataTable represents a table within a
41  * transaction. Adding, removing rows to a DataTable will change the
42  * contents only with the context of the transaction the table was created in.
43  * <p>
44  * @author Tobias Downer
45  */

46
47 public final class DataTable extends DefaultDataTable {
48
49   /**
50    * The DatabaseConnection object that is the parent of this DataTable.
51    */

52   private DatabaseConnection connection;
53
54   /**
55    * A low level access to the underlying transactional data source.
56    */

57   private MutableTableDataSource data_source;
58
59
60   /**
61    * ------
62    * NOTE: Following values are only kept for lock debugging reasons. These
63    * is no technical reason why they shouldn't be removed. They allow us
64    * to check that a data table is locked correctly when accesses are
65    * performed on it.
66    * ------
67    */

68
69   final static boolean LOCK_DEBUG = true;
70
71   /**
72    * The number of read locks we have on this table.
73    */

74   private int debug_read_lock_count = 0;
75
76   /**
77    * The number of write locks we have on this table (this should only ever be
78    * 0 or 1).
79    */

80   private int debug_write_lock_count = 0;
81
82
83
84
85
86   /**
87    * Cosntructs the data table.
88    */

89   DataTable(DatabaseConnection connection,
90             MutableTableDataSource data_source) throws DatabaseException {
91     super(connection.getDatabase());
92     this.connection = connection;
93     this.data_source = data_source;
94   }
95
96   /**
97    * Convenience - used to log debug messages.
98    */

99   public final DebugLogger Debug() {
100     return connection.getSystem().Debug();
101   }
102
103   /**
104    * Overwritten from DefaultDataTable to do nothing. All selectable
105    * schemes are handled within the DataTableManager now.
106    */

107   protected void blankSelectableSchemes(int type) {
108   }
109
110   /**
111    * Returns the SelectableScheme for the given column.
112    * (Overridden from DefaultDataTable). If the schemes are not in memory then
113    * they are loaded now. This will synchronize over the 'table_manager'
114    * which will effectively block this table at the lowest layer until the
115    * indices are loaded into memory.
116    */

117   protected SelectableScheme getRootColumnScheme(int column) {
118     checkReadLock(); // Read op
119

120     return data_source.getColumnScheme(column);
121   }
122
123   /**
124    * We can declare a DataTable as a new type. This means, instead of
125    * referencing a column as 'Customer.CustomerID' we can change the 'Customer'
126    * part to anything we wish such as 'C1'.
127    */

128   public ReferenceTable declareAs(TableName new_name) {
129     return new ReferenceTable(this, new_name);
130   }
131
132   /**
133    * Generates an empty RowData object for 'addRow'ing into the Table.
134    * We must first call this method to retrieve a blank RowData object,
135    * fill it in with the required information, and then call 'addRow'
136    */

137   public final RowData createRowDataObject(QueryContext context) {
138     checkSafeOperation(); // safe op
139
return new RowData(this);
140   }
141
142   /**
143    * Returns the current row count. This queries the DataTableManager for
144    * the real value.
145    */

146   public int getRowCount() {
147     checkReadLock(); // read op
148

149     return data_source.getRowCount();
150   }
151
152   /**
153    * Adds a given 'RowData' object to the table. This should be used for
154    * any rows added to the table. The order that rows are added into a table
155    * is not important.
156    * <p>
157    * This method performs some checking of the cells in the table. It first
158    * checks that all columns declared as 'not null' have a value that is not
159    * null. It then checks that a the added row will not cause any duplicates
160    * in a column declared as unique.
161    * <p>
162    * It then uses the low level io manager to store the data.
163    * <p>
164    * SYNCHRONIZATION ISSUE: We are assuming this is running in a synchronized
165    * environment that is unable to add or alter rows in this object within
166    * the lifetime of this method.
167    */

168   public final void add(RowData row_data) throws DatabaseException {
169     checkReadWriteLock(); // write op
170

171     if (!row_data.isSameTable(this)) {
172       throw new DatabaseException(
173                      "Internal Error: Using RowData from different table");
174     }
175
176     // Checks passed, so add to table.
177
addRow(row_data);
178
179     // Perform a referential integrity check on any changes to the table.
180
data_source.constraintIntegrityCheck();
181   }
182
183   /**
184    * Adds an array of 'RowData' objects to the table. This should be used for
185    * adding a group of rows to the table. The order that rows are added into
186    * a table is not important.
187    * <p>
188    * This method performs some checking of the cells in the table. It first
189    * checks that all columns declared as 'not null' have a value that is not
190    * null. It then checks that a the added row will not cause any duplicates
191    * in a column declared as unique.
192    * <p>
193    * It then uses the low level io manager to store the data.
194    * <p>
195    * SYNCHRONIZATION ISSUE: We are assuming this is running in a synchronized
196    * environment that is unable to add or alter rows in this object within
197    * the lifetime of this method.
198    */

199   public final void add(RowData[] row_data_arr) throws DatabaseException {
200     checkReadWriteLock(); // write op
201

202     for (int i = 0; i < row_data_arr.length; ++i) {
203       RowData row_data = row_data_arr[i];
204       if (!row_data.isSameTable(this)) {
205         throw new DatabaseException(
206                        "Internal Error: Using RowData from different table");
207       }
208       addRow(row_data);
209     }
210     
211     // Perform a referential integrity check on any changes to the table.
212
data_source.constraintIntegrityCheck();
213   }
214
215   /**
216    * Adds a new row of data to the table. First of all, this tells the
217    * underlying database mechanism to add the data to this table. It then
218    * add the row information to each SelectableScheme.
219    */

220   private void addRow(RowData row) throws DatabaseException {
221
222     // This table name (for event notification)
223
TableName table_name = getTableName();
224
225     // Fire the 'before' trigger for an insert on this table
226
connection.fireTableEvent(new TableModificationEvent(connection, table_name,
227                                                          row, true));
228
229     // Add the row to the underlying file system
230
int row_number = data_source.addRow(row);
231
232     // Fire the 'after' trigger for an insert on this table
233
connection.fireTableEvent(new TableModificationEvent(connection, table_name,
234                                                          row, false));
235
236     // NOTE: currently nothing being done with 'row_number' after it's added.
237
// The underlying table data source manages the row index.
238

239   }
240
241   /**
242    * Removes the given row from the table. This is called just before the
243    * row is actually deleted. The method is provided to allow for some
244    * maintenance of any search structures such as B-Trees. This is called
245    * from the 'delete' method in Table.
246    */

247   private void removeRow(int row_number) throws DatabaseException {
248
249     // This table name (for event notification)
250
TableName table_name = getTableName();
251
252     // Fire the 'before' trigger for the delete on this table
253
connection.fireTableEvent(new TableModificationEvent(connection, table_name,
254                                                          row_number, true));
255
256     // Delete the row from the underlying database
257
data_source.removeRow(row_number);
258
259     // Fire the 'after' trigger for the delete on this table
260
connection.fireTableEvent(new TableModificationEvent(connection, table_name,
261                                                          row_number, false));
262
263   }
264
265   /**
266    * Updates the given row with the given data in this table. This method
267    * will likely add the modified data to a new row and delete the old
268    * version of the row.
269    */

270   private void updateRow(int row_number, RowData row)
271                                                     throws DatabaseException {
272
273     // This table name (for event notification)
274
TableName table_name = getTableName();
275
276     // Fire the 'before' trigger for the update on this table
277
connection.fireTableEvent(
278          new TableModificationEvent(connection, table_name,
279                                     row_number, row, true));
280
281     // Update the row in the underlying database
282
data_source.updateRow(row_number, row);
283
284     // Fire the 'after' trigger for the update on this table
285
connection.fireTableEvent(
286          new TableModificationEvent(connection, table_name,
287                                     row_number, row, false));
288
289   }
290
291   /**
292    * This is the public method for removing a given result set from this
293    * table. Given a Table object, this will remove from this table any row
294    * that are in the given table. The given Table must have this object as
295    * its distant ancestor. If it does not then it will throw an exception.
296    * Examples: table.delete(table) -- delete the entire table.
297    * table.delete(table.select( < some condition > ));
298    * It returns the number of rows that were deleted.
299    * <p>
300    * <strong>INTERNAL NOTE:</strong> The 'table' parameter may be the result
301    * of joins. This may cause the same row in this table to be referenced
302    * more than once. We must make sure that we delete any given row only
303    * once by using the 'distinct' function.
304    * <p>
305    * 'limit' dictates how many rows will be deleted. If 'limit' is less than
306    * 0 then this indicates there is no limit. Keep in mind that rows are
307    * picked out from top to bottom in the 'table' object. Normally the
308    * input table will be the result of an un-ordered 'where' clause so using
309    * a limit does not permit deletes in a deterministic manner.
310    * <p>
311    * ASSUMPTION: There are no duplicate rows in the input set.
312    */

313   public int delete(Table table, int limit) throws DatabaseException {
314     checkReadWriteLock(); // write op
315

316     IntegerVector row_set = new IntegerVector(table.getRowCount());
317     RowEnumeration e = table.rowEnumeration();
318     while (e.hasMoreRows()) {
319       row_set.addInt(e.nextRowIndex());
320     }
321     e = null;
322
323     // HACKY: Find the first column of this table in the search table. This
324
// will allow us to generate a row set of only the rows in the search
325
// table.
326
int first_column = table.findFieldName(getResolvedVariable(0));
327
328     if (first_column == -1) {
329       throw new DatabaseException("Search table does not contain any " +
330                                   "reference to table being deleted from");
331     }
332
333     // Generate a row set that is in this tables domain.
334
table.setToRowTableDomain(first_column, row_set, this);
335
336     // row_set may contain duplicate row indices, therefore we must sort so
337
// any duplicates are grouped and therefore easier to find.
338
row_set.quickSort();
339
340     // If limit less than zero then limit is whole set.
341
if (limit < 0) {
342       limit = Integer.MAX_VALUE;
343     }
344
345     // Remove each row in row set in turn. Make sure we don't remove the
346
// same row index twice.
347
int len = Math.min(row_set.size(), limit);
348     int last_removed = -1;
349     int remove_count = 0;
350     for (int i = 0; i < len; ++i) {
351       int to_remove = row_set.intAt(i);
352       if (to_remove < last_removed) {
353         throw new DatabaseException(
354           "Internal error: row sorting error or row_set not in the range > 0");
355       }
356
357       if (to_remove != last_removed) {
358         removeRow(to_remove);
359         last_removed = to_remove;
360         ++remove_count;
361       }
362
363     }
364
365     if (remove_count > 0) {
366       // Perform a referential integrity check on any changes to the table.
367
data_source.constraintIntegrityCheck();
368     }
369
370     return remove_count;
371   }
372
373   // Unlimited delete
374
public int delete(Table table) throws DatabaseException {
375     return delete(table, -1);
376   }
377
378   /**
379    * Updates the table by applying the assignment operations over each row
380    * that is found in the input 'table' set. The input table must be a direct
381    * child of this DataTable.
382    * <p>
383    * This operation assumes that there is a WRITE lock on this table. A
384    * WRITE lock means no other thread may access this table while the
385    * operation is being performed. (However, a result set may still be
386    * downloading from this table).
387    * <p>
388    * 'limit' dictates how many rows will be updated. If 'limit' is less than
389    * 0 then this indicates there is no limit. Keep in mind that rows are
390    * picked out from top to bottom in the 'table' object. Normally the
391    * input table will be the result of an un-ordered 'where' clause so using
392    * a limit does not permit updates in a deterministic manner.
393    * <p>
394    * Returns the number of rows updated in this table.
395    * <p>
396    * NOTE: We assume there are no duplicate rows to the root set from the
397    * given 'table'.
398    */

399   public final int update(QueryContext context,
400                           Table table, Assignment[] assign_list, int limit)
401                                                     throws DatabaseException {
402     checkReadWriteLock(); // write op
403

404     // Get the rows from the input table.
405
IntegerVector row_set = new IntegerVector();
406     RowEnumeration e = table.rowEnumeration();
407     while (e.hasMoreRows()) {
408       row_set.addInt(e.nextRowIndex());
409     }
410     e = null;
411
412     // HACKY: Find the first column of this table in the search table. This
413
// will allow us to generate a row set of only the rows in the search
414
// table.
415
int first_column = table.findFieldName(getResolvedVariable(0));
416     if (first_column == -1) {
417       throw new DatabaseException("Search table does not contain any " +
418                                   "reference to table being updated from");
419     }
420
421     // Convert the row_set to this table's domain.
422
table.setToRowTableDomain(first_column, row_set, this);
423
424     // NOTE: Assume there's no duplicate rows.
425

426     RowData original_data = createRowDataObject(context);
427     RowData row_data = createRowDataObject(context);
428
429     // If limit less than zero then limit is whole set.
430
if (limit < 0) {
431       limit = Integer.MAX_VALUE;
432     }
433
434     // Update each row in row set in turn up to the limit.
435
int len = Math.min(row_set.size(), limit);
436     int update_count = 0;
437     for (int i = 0; i < len; ++i) {
438       int to_update = row_set.intAt(i);
439
440       // Make a RowData object from this row (plus keep the original intact
441
// incase we need to roll back to it).
442
original_data.setFromRow(to_update);
443       row_data.setFromRow(to_update);
444
445       // Run each assignment on the RowData.
446
for (int n = 0; n < assign_list.length; ++n) {
447         Assignment assignment = assign_list[n];
448         row_data.evaluate(assignment, context);
449       }
450
451       // Update the row
452
updateRow(to_update, row_data);
453
454       ++update_count;
455     }
456
457     if (update_count > 0) {
458       // Perform a referential integrity check on any changes to the table.
459
data_source.constraintIntegrityCheck();
460     }
461
462     return update_count;
463
464   }
465
466   /**
467    * Returns the DataTableDef object for this table. This object describes
468    * how the table is made up.
469    * <p>
470    * <strong>NOTE:</strong> Do not keep references to this object. The
471    * DataTableDef is invalidated when a table is closed.
472    */

473   public DataTableDef getDataTableDef() {
474     checkSafeOperation(); // safe op
475

476     return data_source.getDataTableDef();
477   }
478
479   /**
480    * Returns the schema that this table is within.
481    */

482   public String JavaDoc getSchema() {
483     checkSafeOperation(); // safe op
484

485     return getDataTableDef().getSchema();
486   }
487
488   /**
489    * Adds a DataTableListener to the DataTable objects at the root of this
490    * table tree hierarchy. If this table represents the join of a number of
491    * tables then the DataTableListener is added to all the DataTable objects
492    * at the root.
493    * <p>
494    * A DataTableListener is notified of all modifications to the raw entries
495    * of the table. This listener can be used for detecting changes in VIEWs,
496    * for triggers or for caching of common queries.
497    */

498   public void addDataTableListener(DataTableListener listener) {
499     // Currently we do nothing with this info.
500
}
501
502   /**
503    * Removes a DataTableListener from the DataTable objects at the root of
504    * this table tree hierarchy. If this table represents the join of a
505    * number of tables, then the DataTableListener is removed from all the
506    * DataTable objects at the root.
507    */

508   public void removeDataTableListener(DataTableListener listener) {
509     // Currently we do nothing with this info.
510
}
511
512
513
514
515   // -------- Methods implemented for DefaultDataTable --------
516

517   /**
518    * Given a set, this trickles down through the Table hierarchy resolving
519    * the given row_set to a form that the given ancestor understands.
520    * Say you give the set { 0, 1, 2, 3, 4, 5, 6 }, this function may check
521    * down three levels and return a new 7 element set with the rows fully
522    * resolved to the given ancestors domain.
523    */

524   void setToRowTableDomain(int column, IntegerVector row_set,
525                            TableDataSource ancestor) {
526     checkReadLock(); // read op
527

528     if (ancestor != this && ancestor != data_source) {
529       throw new RuntimeException JavaDoc("Method routed to incorrect table ancestor.");
530     }
531   }
532
533   /**
534    * Returns an object that represents the information in the given cell
535    * in the table. This can be used to obtain information about the given
536    * table cells.
537    */

538   public TObject getCellContents(int column, int row) {
539     checkSafeOperation(); // safe op
540

541     return data_source.getCellContents(column, row);
542   }
543
544   /**
545    * Returns an Enumeration of the rows in this table.
546    * Each call to 'nextRowIndex' returns the next valid row index in the table.
547    */

548   public RowEnumeration rowEnumeration() {
549     checkReadLock(); // read op
550

551     return data_source.rowEnumeration();
552   }
553
554
555   /**
556    * Locks the root table(s) of this table so that it is impossible to
557    * overwrite the underlying rows that may appear in this table.
558    * This is used when cells in the table need to be accessed 'outside' the
559    * lock. So we may have late access to cells in the table.
560    * 'lock_key' is a given key that will also unlock the root table(s).
561    * <p>
562    * NOTE: This is nothing to do with the 'LockingMechanism' object.
563    */

564   public void lockRoot(int lock_key) {
565     checkSafeOperation(); // safe op
566

567     data_source.addRootLock();
568   }
569
570   /**
571    * Unlocks the root tables so that the underlying rows may
572    * once again be used if they are not locked and have been removed. This
573    * should be called some time after the rows have been locked.
574    */

575   public void unlockRoot(int lock_key) {
576     checkSafeOperation(); // safe op
577

578     data_source.removeRootLock();
579   }
580
581   /**
582    * Returns true if the table has its row roots locked (via the lockRoot(int)
583    * method.
584    */

585   public boolean hasRootsLocked() {
586     // There is no reason why we would need to know this information at
587
// this level.
588
// We need to deprecate this properly.
589
throw new Error JavaDoc("hasRootsLocked is deprecated.");
590   }
591
592
593
594
595
596   // ------------ Lock debugging methods ----------
597

598   /**
599    * This is called by the 'Lock' class to notify this DataTable that a read/
600    * write lock has been applied to this table. This is for lock debugging
601    * purposes only.
602    */

603   final void notifyAddRWLock(int lock_type) {
604     if (LOCK_DEBUG) {
605       if (lock_type == Lock.READ) {
606         ++debug_read_lock_count;
607       }
608       else if (lock_type == Lock.WRITE) {
609         ++debug_write_lock_count;
610         if (debug_write_lock_count > 1) {
611           throw new Error JavaDoc(">1 write lock on table " + getTableName());
612         }
613       }
614       else {
615         throw new Error JavaDoc("Unknown lock type: " + lock_type);
616       }
617     }
618   }
619
620   /**
621    * This is called by the 'Lock' class to notify this DataTable that a read/
622    * write lock has been released from this table. This is for lock debugging
623    * purposes only.
624    */

625   final void notifyReleaseRWLock(int lock_type) {
626     if (LOCK_DEBUG) {
627       if (lock_type == Lock.READ) {
628         --debug_read_lock_count;
629       }
630       else if (lock_type == Lock.WRITE) {
631         --debug_write_lock_count;
632       }
633       else {
634         Debug().writeException(
635                     new RuntimeException JavaDoc("Unknown lock type: " + lock_type));
636       }
637     }
638   }
639
640   /**
641    * Returns true if the database is in exclusive mode.
642    */

643   private boolean isInExclusiveMode() {
644     // Check the connection locking mechanism is in exclusive mode
645
return connection.getLockingMechanism().isInExclusiveMode();
646   }
647
648   /**
649    * Checks the database is in exclusive mode.
650    */

651   private void checkInExclusiveMode() {
652     if (!isInExclusiveMode()) {
653       Debug().writeException(new RuntimeException JavaDoc(
654          "Performed exclusive operation on table and not in exclusive mode!"));
655     }
656   }
657
658   /**
659    * Check that we can safely read from this table.
660    */

661   private void checkReadLock() {
662     if (LOCK_DEBUG) {
663       // All 'sUSR' tables are given read access because they may only be
664
// written under exclusive mode anyway.
665

666       boolean is_internal_table =
667                     getTableName().getSchema().equals(Database.SYSTEM_SCHEMA);
668
669       if (!(is_internal_table ||
670             debug_read_lock_count > 0 ||
671             debug_write_lock_count > 0 ||
672             isInExclusiveMode())) {
673
674         System.err.println();
675         System.err.print(" is_internal_table = " + is_internal_table);
676         System.err.print(" debug_read_lock_count = " + debug_read_lock_count);
677         System.err.print(" debug_write_lock_count = " + debug_write_lock_count);
678         System.err.println(" isInExclusiveMode = " + isInExclusiveMode());
679
680         Debug().writeException(new Error JavaDoc(
681                     "Invalid read access on table '" + getTableName() + "'"));
682       }
683     }
684   }
685
686   /**
687    * Check that we can safely read/write from this table. This should catch
688    * any synchronization concurrent issues.
689    */

690   private void checkReadWriteLock() {
691     if (LOCK_DEBUG) {
692       // We have to own exactly one write lock, or be in exclusive mode.
693
if (!(debug_write_lock_count == 1 || isInExclusiveMode())) {
694         Debug().writeException(
695                new Error JavaDoc("Invalid read/write access on table '" +
696                          getTableName() + "'"));
697       }
698     }
699   }
700
701   /**
702    * Check that we can run a safe operation.
703    */

704   private void checkSafeOperation() {
705     // no operation - nothing to check for...
706
}
707
708
709
710   // ---------- Overwritten to output debug info ----------
711
// NOTE: These can all safely be commented out.
712

713   public int getColumnCount() {
714     checkSafeOperation(); // safe op
715

716     return super.getColumnCount();
717   }
718
719   public Variable getResolvedVariable(int column) {
720     checkSafeOperation(); // safe op
721

722     return super.getResolvedVariable(column);
723   }
724
725   public int findFieldName(Variable v) {
726     checkSafeOperation(); // safe op
727

728     return super.findFieldName(v);
729   }
730
731   SelectableScheme getSelectableSchemeFor(int column, int original_column,
732                                           Table table) {
733     checkReadLock(); // read op
734

735     return super.getSelectableSchemeFor(column, original_column, table);
736   }
737
738   RawTableInformation resolveToRawTable(RawTableInformation info) {
739     checkReadLock(); // read op
740

741     return super.resolveToRawTable(info);
742   }
743
744 }
745
Popular Tags