001: /**
002: * com.mckoi.database.DataTable 08 Mar 1998
003: *
004: * Mckoi SQL Database ( http://www.mckoi.com/database )
005: * Copyright (C) 2000, 2001, 2002 Diehl and Associates, Inc.
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License
009: * Version 2 as published by the Free Software Foundation.
010: *
011: * This program is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014: * GNU General Public License Version 2 for more details.
015: *
016: * You should have received a copy of the GNU General Public License
017: * Version 2 along with this program; if not, write to the Free Software
018: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
019: *
020: * Change Log:
021: *
022: *
023: */package com.mckoi.database;
024:
025: import com.mckoi.database.global.SQLTypes;
026: import com.mckoi.util.IntegerVector;
027: import com.mckoi.debug.*;
028: import java.math.BigDecimal;
029: import java.util.Vector;
030: import java.util.ArrayList;
031: import java.io.IOException;
032: import java.io.OutputStream;
033: import java.io.InputStream;
034: import java.io.File;
035:
036: /**
037: * DataTable is a wrapper for a MutableTableDataSource that fits into the
038: * query hierarchy level. A DataTable represents a table within a
039: * transaction. Adding, removing rows to a DataTable will change the
040: * contents only with the context of the transaction the table was created in.
041: * <p>
042: * @author Tobias Downer
043: */
044:
045: public final class DataTable extends DefaultDataTable {
046:
047: /**
048: * The DatabaseConnection object that is the parent of this DataTable.
049: */
050: private DatabaseConnection connection;
051:
052: /**
053: * A low level access to the underlying transactional data source.
054: */
055: private MutableTableDataSource data_source;
056:
057: /**
058: * ------
059: * NOTE: Following values are only kept for lock debugging reasons. These
060: * is no technical reason why they shouldn't be removed. They allow us
061: * to check that a data table is locked correctly when accesses are
062: * performed on it.
063: * ------
064: */
065:
066: final static boolean LOCK_DEBUG = true;
067:
068: /**
069: * The number of read locks we have on this table.
070: */
071: private int debug_read_lock_count = 0;
072:
073: /**
074: * The number of write locks we have on this table (this should only ever be
075: * 0 or 1).
076: */
077: private int debug_write_lock_count = 0;
078:
079: /**
080: * Cosntructs the data table.
081: */
082: DataTable(DatabaseConnection connection,
083: MutableTableDataSource data_source)
084: throws DatabaseException {
085: super (connection.getDatabase());
086: this .connection = connection;
087: this .data_source = data_source;
088: }
089:
090: /**
091: * Convenience - used to log debug messages.
092: */
093: public final DebugLogger Debug() {
094: return connection.getSystem().Debug();
095: }
096:
097: /**
098: * Overwritten from DefaultDataTable to do nothing. All selectable
099: * schemes are handled within the DataTableManager now.
100: */
101: protected void blankSelectableSchemes(int type) {
102: }
103:
104: /**
105: * Returns the SelectableScheme for the given column.
106: * (Overridden from DefaultDataTable). If the schemes are not in memory then
107: * they are loaded now. This will synchronize over the 'table_manager'
108: * which will effectively block this table at the lowest layer until the
109: * indices are loaded into memory.
110: */
111: protected SelectableScheme getRootColumnScheme(int column) {
112: checkReadLock(); // Read op
113:
114: return data_source.getColumnScheme(column);
115: }
116:
117: /**
118: * We can declare a DataTable as a new type. This means, instead of
119: * referencing a column as 'Customer.CustomerID' we can change the 'Customer'
120: * part to anything we wish such as 'C1'.
121: */
122: public ReferenceTable declareAs(TableName new_name) {
123: return new ReferenceTable(this , new_name);
124: }
125:
126: /**
127: * Generates an empty RowData object for 'addRow'ing into the Table.
128: * We must first call this method to retrieve a blank RowData object,
129: * fill it in with the required information, and then call 'addRow'
130: */
131: public final RowData createRowDataObject(QueryContext context) {
132: checkSafeOperation(); // safe op
133: return new RowData(this );
134: }
135:
136: /**
137: * Returns the current row count. This queries the DataTableManager for
138: * the real value.
139: */
140: public int getRowCount() {
141: checkReadLock(); // read op
142:
143: return data_source.getRowCount();
144: }
145:
146: /**
147: * Adds a given 'RowData' object to the table. This should be used for
148: * any rows added to the table. The order that rows are added into a table
149: * is not important.
150: * <p>
151: * This method performs some checking of the cells in the table. It first
152: * checks that all columns declared as 'not null' have a value that is not
153: * null. It then checks that a the added row will not cause any duplicates
154: * in a column declared as unique.
155: * <p>
156: * It then uses the low level io manager to store the data.
157: * <p>
158: * SYNCHRONIZATION ISSUE: We are assuming this is running in a synchronized
159: * environment that is unable to add or alter rows in this object within
160: * the lifetime of this method.
161: */
162: public final void add(RowData row_data) throws DatabaseException {
163: checkReadWriteLock(); // write op
164:
165: if (!row_data.isSameTable(this )) {
166: throw new DatabaseException(
167: "Internal Error: Using RowData from different table");
168: }
169:
170: // Checks passed, so add to table.
171: addRow(row_data);
172:
173: // Perform a referential integrity check on any changes to the table.
174: data_source.constraintIntegrityCheck();
175: }
176:
177: /**
178: * Adds an array of 'RowData' objects to the table. This should be used for
179: * adding a group of rows to the table. The order that rows are added into
180: * a table is not important.
181: * <p>
182: * This method performs some checking of the cells in the table. It first
183: * checks that all columns declared as 'not null' have a value that is not
184: * null. It then checks that a the added row will not cause any duplicates
185: * in a column declared as unique.
186: * <p>
187: * It then uses the low level io manager to store the data.
188: * <p>
189: * SYNCHRONIZATION ISSUE: We are assuming this is running in a synchronized
190: * environment that is unable to add or alter rows in this object within
191: * the lifetime of this method.
192: */
193: public final void add(RowData[] row_data_arr)
194: throws DatabaseException {
195: checkReadWriteLock(); // write op
196:
197: for (int i = 0; i < row_data_arr.length; ++i) {
198: RowData row_data = row_data_arr[i];
199: if (!row_data.isSameTable(this )) {
200: throw new DatabaseException(
201: "Internal Error: Using RowData from different table");
202: }
203: addRow(row_data);
204: }
205:
206: // Perform a referential integrity check on any changes to the table.
207: data_source.constraintIntegrityCheck();
208: }
209:
210: /**
211: * Adds a new row of data to the table. First of all, this tells the
212: * underlying database mechanism to add the data to this table. It then
213: * add the row information to each SelectableScheme.
214: */
215: private void addRow(RowData row) throws DatabaseException {
216:
217: // This table name (for event notification)
218: TableName table_name = getTableName();
219:
220: // Fire the 'before' trigger for an insert on this table
221: connection.fireTableEvent(new TableModificationEvent(
222: connection, table_name, row, true));
223:
224: // Add the row to the underlying file system
225: int row_number = data_source.addRow(row);
226:
227: // Fire the 'after' trigger for an insert on this table
228: connection.fireTableEvent(new TableModificationEvent(
229: connection, table_name, row, false));
230:
231: // NOTE: currently nothing being done with 'row_number' after it's added.
232: // The underlying table data source manages the row index.
233:
234: }
235:
236: /**
237: * Removes the given row from the table. This is called just before the
238: * row is actually deleted. The method is provided to allow for some
239: * maintenance of any search structures such as B-Trees. This is called
240: * from the 'delete' method in Table.
241: */
242: private void removeRow(int row_number) throws DatabaseException {
243:
244: // This table name (for event notification)
245: TableName table_name = getTableName();
246:
247: // Fire the 'before' trigger for the delete on this table
248: connection.fireTableEvent(new TableModificationEvent(
249: connection, table_name, row_number, true));
250:
251: // Delete the row from the underlying database
252: data_source.removeRow(row_number);
253:
254: // Fire the 'after' trigger for the delete on this table
255: connection.fireTableEvent(new TableModificationEvent(
256: connection, table_name, row_number, false));
257:
258: }
259:
260: /**
261: * Updates the given row with the given data in this table. This method
262: * will likely add the modified data to a new row and delete the old
263: * version of the row.
264: */
265: private void updateRow(int row_number, RowData row)
266: throws DatabaseException {
267:
268: // This table name (for event notification)
269: TableName table_name = getTableName();
270:
271: // Fire the 'before' trigger for the update on this table
272: connection.fireTableEvent(new TableModificationEvent(
273: connection, table_name, row_number, row, true));
274:
275: // Update the row in the underlying database
276: data_source.updateRow(row_number, row);
277:
278: // Fire the 'after' trigger for the update on this table
279: connection.fireTableEvent(new TableModificationEvent(
280: connection, table_name, row_number, row, false));
281:
282: }
283:
284: /**
285: * This is the public method for removing a given result set from this
286: * table. Given a Table object, this will remove from this table any row
287: * that are in the given table. The given Table must have this object as
288: * its distant ancestor. If it does not then it will throw an exception.
289: * Examples: table.delete(table) -- delete the entire table.
290: * table.delete(table.select( < some condition > ));
291: * It returns the number of rows that were deleted.
292: * <p>
293: * <strong>INTERNAL NOTE:</strong> The 'table' parameter may be the result
294: * of joins. This may cause the same row in this table to be referenced
295: * more than once. We must make sure that we delete any given row only
296: * once by using the 'distinct' function.
297: * <p>
298: * 'limit' dictates how many rows will be deleted. If 'limit' is less than
299: * 0 then this indicates there is no limit. Keep in mind that rows are
300: * picked out from top to bottom in the 'table' object. Normally the
301: * input table will be the result of an un-ordered 'where' clause so using
302: * a limit does not permit deletes in a deterministic manner.
303: * <p>
304: * ASSUMPTION: There are no duplicate rows in the input set.
305: */
306: public int delete(Table table, int limit) throws DatabaseException {
307: checkReadWriteLock(); // write op
308:
309: IntegerVector row_set = new IntegerVector(table.getRowCount());
310: RowEnumeration e = table.rowEnumeration();
311: while (e.hasMoreRows()) {
312: row_set.addInt(e.nextRowIndex());
313: }
314: e = null;
315:
316: // HACKY: Find the first column of this table in the search table. This
317: // will allow us to generate a row set of only the rows in the search
318: // table.
319: int first_column = table.findFieldName(getResolvedVariable(0));
320:
321: if (first_column == -1) {
322: throw new DatabaseException(
323: "Search table does not contain any "
324: + "reference to table being deleted from");
325: }
326:
327: // Generate a row set that is in this tables domain.
328: table.setToRowTableDomain(first_column, row_set, this );
329:
330: // row_set may contain duplicate row indices, therefore we must sort so
331: // any duplicates are grouped and therefore easier to find.
332: row_set.quickSort();
333:
334: // If limit less than zero then limit is whole set.
335: if (limit < 0) {
336: limit = Integer.MAX_VALUE;
337: }
338:
339: // Remove each row in row set in turn. Make sure we don't remove the
340: // same row index twice.
341: int len = Math.min(row_set.size(), limit);
342: int last_removed = -1;
343: int remove_count = 0;
344: for (int i = 0; i < len; ++i) {
345: int to_remove = row_set.intAt(i);
346: if (to_remove < last_removed) {
347: throw new DatabaseException(
348: "Internal error: row sorting error or row_set not in the range > 0");
349: }
350:
351: if (to_remove != last_removed) {
352: removeRow(to_remove);
353: last_removed = to_remove;
354: ++remove_count;
355: }
356:
357: }
358:
359: if (remove_count > 0) {
360: // Perform a referential integrity check on any changes to the table.
361: data_source.constraintIntegrityCheck();
362: }
363:
364: return remove_count;
365: }
366:
367: // Unlimited delete
368: public int delete(Table table) throws DatabaseException {
369: return delete(table, -1);
370: }
371:
372: /**
373: * Updates the table by applying the assignment operations over each row
374: * that is found in the input 'table' set. The input table must be a direct
375: * child of this DataTable.
376: * <p>
377: * This operation assumes that there is a WRITE lock on this table. A
378: * WRITE lock means no other thread may access this table while the
379: * operation is being performed. (However, a result set may still be
380: * downloading from this table).
381: * <p>
382: * 'limit' dictates how many rows will be updated. If 'limit' is less than
383: * 0 then this indicates there is no limit. Keep in mind that rows are
384: * picked out from top to bottom in the 'table' object. Normally the
385: * input table will be the result of an un-ordered 'where' clause so using
386: * a limit does not permit updates in a deterministic manner.
387: * <p>
388: * Returns the number of rows updated in this table.
389: * <p>
390: * NOTE: We assume there are no duplicate rows to the root set from the
391: * given 'table'.
392: */
393: public final int update(QueryContext context, Table table,
394: Assignment[] assign_list, int limit)
395: throws DatabaseException {
396: checkReadWriteLock(); // write op
397:
398: // Get the rows from the input table.
399: IntegerVector row_set = new IntegerVector();
400: RowEnumeration e = table.rowEnumeration();
401: while (e.hasMoreRows()) {
402: row_set.addInt(e.nextRowIndex());
403: }
404: e = null;
405:
406: // HACKY: Find the first column of this table in the search table. This
407: // will allow us to generate a row set of only the rows in the search
408: // table.
409: int first_column = table.findFieldName(getResolvedVariable(0));
410: if (first_column == -1) {
411: throw new DatabaseException(
412: "Search table does not contain any "
413: + "reference to table being updated from");
414: }
415:
416: // Convert the row_set to this table's domain.
417: table.setToRowTableDomain(first_column, row_set, this );
418:
419: // NOTE: Assume there's no duplicate rows.
420:
421: RowData original_data = createRowDataObject(context);
422: RowData row_data = createRowDataObject(context);
423:
424: // If limit less than zero then limit is whole set.
425: if (limit < 0) {
426: limit = Integer.MAX_VALUE;
427: }
428:
429: // Update each row in row set in turn up to the limit.
430: int len = Math.min(row_set.size(), limit);
431: int update_count = 0;
432: for (int i = 0; i < len; ++i) {
433: int to_update = row_set.intAt(i);
434:
435: // Make a RowData object from this row (plus keep the original intact
436: // incase we need to roll back to it).
437: original_data.setFromRow(to_update);
438: row_data.setFromRow(to_update);
439:
440: // Run each assignment on the RowData.
441: for (int n = 0; n < assign_list.length; ++n) {
442: Assignment assignment = assign_list[n];
443: row_data.evaluate(assignment, context);
444: }
445:
446: // Update the row
447: updateRow(to_update, row_data);
448:
449: ++update_count;
450: }
451:
452: if (update_count > 0) {
453: // Perform a referential integrity check on any changes to the table.
454: data_source.constraintIntegrityCheck();
455: }
456:
457: return update_count;
458:
459: }
460:
461: /**
462: * Returns the DataTableDef object for this table. This object describes
463: * how the table is made up.
464: * <p>
465: * <strong>NOTE:</strong> Do not keep references to this object. The
466: * DataTableDef is invalidated when a table is closed.
467: */
468: public DataTableDef getDataTableDef() {
469: checkSafeOperation(); // safe op
470:
471: return data_source.getDataTableDef();
472: }
473:
474: /**
475: * Returns the schema that this table is within.
476: */
477: public String getSchema() {
478: checkSafeOperation(); // safe op
479:
480: return getDataTableDef().getSchema();
481: }
482:
483: /**
484: * Adds a DataTableListener to the DataTable objects at the root of this
485: * table tree hierarchy. If this table represents the join of a number of
486: * tables then the DataTableListener is added to all the DataTable objects
487: * at the root.
488: * <p>
489: * A DataTableListener is notified of all modifications to the raw entries
490: * of the table. This listener can be used for detecting changes in VIEWs,
491: * for triggers or for caching of common queries.
492: */
493: public void addDataTableListener(DataTableListener listener) {
494: // Currently we do nothing with this info.
495: }
496:
497: /**
498: * Removes a DataTableListener from the DataTable objects at the root of
499: * this table tree hierarchy. If this table represents the join of a
500: * number of tables, then the DataTableListener is removed from all the
501: * DataTable objects at the root.
502: */
503: public void removeDataTableListener(DataTableListener listener) {
504: // Currently we do nothing with this info.
505: }
506:
507: // -------- Methods implemented for DefaultDataTable --------
508:
509: /**
510: * Given a set, this trickles down through the Table hierarchy resolving
511: * the given row_set to a form that the given ancestor understands.
512: * Say you give the set { 0, 1, 2, 3, 4, 5, 6 }, this function may check
513: * down three levels and return a new 7 element set with the rows fully
514: * resolved to the given ancestors domain.
515: */
516: void setToRowTableDomain(int column, IntegerVector row_set,
517: TableDataSource ancestor) {
518: checkReadLock(); // read op
519:
520: if (ancestor != this && ancestor != data_source) {
521: throw new RuntimeException(
522: "Method routed to incorrect table ancestor.");
523: }
524: }
525:
526: /**
527: * Returns an object that represents the information in the given cell
528: * in the table. This can be used to obtain information about the given
529: * table cells.
530: */
531: public TObject getCellContents(int column, int row) {
532: checkSafeOperation(); // safe op
533:
534: return data_source.getCellContents(column, row);
535: }
536:
537: /**
538: * Returns an Enumeration of the rows in this table.
539: * Each call to 'nextRowIndex' returns the next valid row index in the table.
540: */
541: public RowEnumeration rowEnumeration() {
542: checkReadLock(); // read op
543:
544: return data_source.rowEnumeration();
545: }
546:
547: /**
548: * Locks the root table(s) of this table so that it is impossible to
549: * overwrite the underlying rows that may appear in this table.
550: * This is used when cells in the table need to be accessed 'outside' the
551: * lock. So we may have late access to cells in the table.
552: * 'lock_key' is a given key that will also unlock the root table(s).
553: * <p>
554: * NOTE: This is nothing to do with the 'LockingMechanism' object.
555: */
556: public void lockRoot(int lock_key) {
557: checkSafeOperation(); // safe op
558:
559: data_source.addRootLock();
560: }
561:
562: /**
563: * Unlocks the root tables so that the underlying rows may
564: * once again be used if they are not locked and have been removed. This
565: * should be called some time after the rows have been locked.
566: */
567: public void unlockRoot(int lock_key) {
568: checkSafeOperation(); // safe op
569:
570: data_source.removeRootLock();
571: }
572:
573: /**
574: * Returns true if the table has its row roots locked (via the lockRoot(int)
575: * method.
576: */
577: public boolean hasRootsLocked() {
578: // There is no reason why we would need to know this information at
579: // this level.
580: // We need to deprecate this properly.
581: throw new Error("hasRootsLocked is deprecated.");
582: }
583:
584: // ------------ Lock debugging methods ----------
585:
586: /**
587: * This is called by the 'Lock' class to notify this DataTable that a read/
588: * write lock has been applied to this table. This is for lock debugging
589: * purposes only.
590: */
591: final void notifyAddRWLock(int lock_type) {
592: if (LOCK_DEBUG) {
593: if (lock_type == Lock.READ) {
594: ++debug_read_lock_count;
595: } else if (lock_type == Lock.WRITE) {
596: ++debug_write_lock_count;
597: if (debug_write_lock_count > 1) {
598: throw new Error(">1 write lock on table "
599: + getTableName());
600: }
601: } else {
602: throw new Error("Unknown lock type: " + lock_type);
603: }
604: }
605: }
606:
607: /**
608: * This is called by the 'Lock' class to notify this DataTable that a read/
609: * write lock has been released from this table. This is for lock debugging
610: * purposes only.
611: */
612: final void notifyReleaseRWLock(int lock_type) {
613: if (LOCK_DEBUG) {
614: if (lock_type == Lock.READ) {
615: --debug_read_lock_count;
616: } else if (lock_type == Lock.WRITE) {
617: --debug_write_lock_count;
618: } else {
619: Debug().writeException(
620: new RuntimeException("Unknown lock type: "
621: + lock_type));
622: }
623: }
624: }
625:
626: /**
627: * Returns true if the database is in exclusive mode.
628: */
629: private boolean isInExclusiveMode() {
630: // Check the connection locking mechanism is in exclusive mode
631: return connection.getLockingMechanism().isInExclusiveMode();
632: }
633:
634: /**
635: * Checks the database is in exclusive mode.
636: */
637: private void checkInExclusiveMode() {
638: if (!isInExclusiveMode()) {
639: Debug()
640: .writeException(
641: new RuntimeException(
642: "Performed exclusive operation on table and not in exclusive mode!"));
643: }
644: }
645:
646: /**
647: * Check that we can safely read from this table.
648: */
649: private void checkReadLock() {
650: if (LOCK_DEBUG) {
651: // All 'sUSR' tables are given read access because they may only be
652: // written under exclusive mode anyway.
653:
654: boolean is_internal_table = getTableName().getSchema()
655: .equals(Database.SYSTEM_SCHEMA);
656:
657: if (!(is_internal_table || debug_read_lock_count > 0
658: || debug_write_lock_count > 0 || isInExclusiveMode())) {
659:
660: System.err.println();
661: System.err.print(" is_internal_table = "
662: + is_internal_table);
663: System.err.print(" debug_read_lock_count = "
664: + debug_read_lock_count);
665: System.err.print(" debug_write_lock_count = "
666: + debug_write_lock_count);
667: System.err.println(" isInExclusiveMode = "
668: + isInExclusiveMode());
669:
670: Debug().writeException(
671: new Error("Invalid read access on table '"
672: + getTableName() + "'"));
673: }
674: }
675: }
676:
677: /**
678: * Check that we can safely read/write from this table. This should catch
679: * any synchronization concurrent issues.
680: */
681: private void checkReadWriteLock() {
682: if (LOCK_DEBUG) {
683: // We have to own exactly one write lock, or be in exclusive mode.
684: if (!(debug_write_lock_count == 1 || isInExclusiveMode())) {
685: Debug().writeException(
686: new Error(
687: "Invalid read/write access on table '"
688: + getTableName() + "'"));
689: }
690: }
691: }
692:
693: /**
694: * Check that we can run a safe operation.
695: */
696: private void checkSafeOperation() {
697: // no operation - nothing to check for...
698: }
699:
700: // ---------- Overwritten to output debug info ----------
701: // NOTE: These can all safely be commented out.
702:
703: public int getColumnCount() {
704: checkSafeOperation(); // safe op
705:
706: return super .getColumnCount();
707: }
708:
709: public Variable getResolvedVariable(int column) {
710: checkSafeOperation(); // safe op
711:
712: return super .getResolvedVariable(column);
713: }
714:
715: public int findFieldName(Variable v) {
716: checkSafeOperation(); // safe op
717:
718: return super .findFieldName(v);
719: }
720:
721: SelectableScheme getSelectableSchemeFor(int column,
722: int original_column, Table table) {
723: checkReadLock(); // read op
724:
725: return super .getSelectableSchemeFor(column, original_column,
726: table);
727: }
728:
729: RawTableInformation resolveToRawTable(RawTableInformation info) {
730: checkReadLock(); // read op
731:
732: return super.resolveToRawTable(info);
733: }
734:
735: }
|