001: /**
002: * com.mckoi.database.TransactionJournal 19 Nov 2000
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.util.IntegerVector;
026: import com.mckoi.util.BlockIntegerList;
027: import java.util.ArrayList;
028:
029: /**
030: * The list of all primitive operations to the database that a transaction
031: * performed. It includes the list of all rows added or removed to all tables,
032: * and the tables created and dropped and any table that had constraint
033: * modifications.
034: * <p>
035: * This journal is updated inside a Transaction. When the transaction is
036: * completed, this journal is used both to determine if the transaction
037: * can be committed, and also to update the changes to the data that a
038: * transaction has made.
039: * <p>
040: * THREADING: The journal update commands are synchronized because they need
041: * to be atomic operations and can be accessed by multiple threads.
042: *
043: * @author Tobias Downer
044: */
045:
046: final class TransactionJournal {
047:
048: /**
049: * Journal commands.
050: */
051: static byte TABLE_ADD = 1; // Add a row to a table.
052: // (params: table_id, row_index)
053: static byte TABLE_REMOVE = 2; // Remove a row from a table.
054: // (params: table_id, row_index)
055: static byte TABLE_CREATE = 3; // Create a new table.
056: // (params: table_id)
057: static byte TABLE_DROP = 4; // Drop a table.
058: // (params: table_id)
059: static byte TABLE_CONSTRAINT_ALTER = 5; // Alter constraints of a table.
060: // (params: table_id)
061:
062: /**
063: * The number of entries in this journal.
064: */
065: private int journal_entries;
066:
067: /**
068: * The list of table's that have been touched by this transaction. A table
069: * is touched if the 'getTable' method in the transaction is used to
070: * get the table. This means even if a table is just read from, the
071: * journal will record that the table was touched.
072: * <p>
073: * This object records the 'table_id' of the touched tables in a sorted
074: * list.
075: */
076: private IntegerVector touched_tables;
077:
078: /**
079: * A byte[] array that represents the set of commands a transaction
080: * performed on a table.
081: */
082: private byte[] command_journal;
083:
084: /**
085: * An IntegerVector that is filled with parameters from the command journal.
086: * For example, a 'TABLE_ADD' journal log will have as parameters the
087: * table id the row was added to, and the row_index that was added.
088: */
089: private IntegerVector command_parameters;
090:
091: /**
092: * Optimization, these flags are set to true when various types of journal
093: * entries are made to the transaction journal.
094: */
095: private boolean has_added_table_rows, has_removed_table_rows,
096: has_created_tables, has_dropped_tables,
097: has_constraint_alterations;
098:
099: /**
100: * Constructs a blank journal.
101: */
102: TransactionJournal() {
103: journal_entries = 0;
104: command_journal = new byte[16];
105: command_parameters = new IntegerVector(32);
106: touched_tables = new IntegerVector(8);
107:
108: has_added_table_rows = false;
109: has_removed_table_rows = false;
110: has_created_tables = false;
111: has_dropped_tables = false;
112: has_constraint_alterations = false;
113: }
114:
115: /**
116: * Adds a command to the journal.
117: */
118: private void addCommand(byte command) {
119: if (journal_entries >= command_journal.length) {
120: // Resize command array.
121: int grow_size = Math.min(4000, journal_entries);
122: byte[] new_command_journal = new byte[journal_entries
123: + grow_size];
124: System.arraycopy(command_journal, 0, new_command_journal,
125: 0, journal_entries);
126: command_journal = new_command_journal;
127: }
128:
129: command_journal[journal_entries] = command;
130: ++journal_entries;
131: }
132:
133: /**
134: * Adds a parameter to the journal command parameters.
135: */
136: private void addParameter(int param) {
137: command_parameters.addInt(param);
138: }
139:
140: /**
141: * Logs in this journal that the transaction touched the given table id.
142: */
143: synchronized void entryAddTouchedTable(int table_id) {
144: int pos = touched_tables.sortedIndexOf(table_id);
145: // If table_id already in the touched table list.
146: if (pos < touched_tables.size()
147: && touched_tables.intAt(pos) == table_id) {
148: return;
149: }
150: // If position to insert >= size of the touched tables set then add to
151: // the end of the set.
152: if (pos >= touched_tables.size()) {
153: touched_tables.addInt(table_id);
154: } else {
155: // Otherwise, insert into sorted order.
156: touched_tables.insertIntAt(table_id, pos);
157: }
158: }
159:
160: /**
161: * Makes a journal entry that a table entry has been added to the table with
162: * the given id.
163: */
164: synchronized void entryAddTableRow(int table_id, int row_index) {
165: // has_added_table_rows = true;
166: addCommand(TABLE_ADD);
167: addParameter(table_id);
168: addParameter(row_index);
169: }
170:
171: /**
172: * Makes a journal entry that a table entry has been removed from the table
173: * with the given id.
174: */
175: synchronized void entryRemoveTableRow(int table_id, int row_index) {
176: // has_removed_table_rows = true;
177: addCommand(TABLE_REMOVE);
178: addParameter(table_id);
179: addParameter(row_index);
180: }
181:
182: /**
183: * Makes a journal entry that a table with the given 'table_id' has been
184: * created by this transaction.
185: */
186: synchronized void entryTableCreate(int table_id) {
187: has_created_tables = true;
188: addCommand(TABLE_CREATE);
189: addParameter(table_id);
190: }
191:
192: /**
193: * Makes a journal entry that a table with the given 'table_id' has been
194: * dropped by this transaction.
195: */
196: synchronized void entryTableDrop(int table_id) {
197: has_dropped_tables = true;
198: addCommand(TABLE_DROP);
199: addParameter(table_id);
200: }
201:
202: /**
203: * Makes a journal entry that a table with the given 'table_id' has been
204: * altered by this transaction.
205: */
206: synchronized void entryTableConstraintAlter(int table_id) {
207: has_constraint_alterations = true;
208: addCommand(TABLE_CONSTRAINT_ALTER);
209: addParameter(table_id);
210: }
211:
212: /**
213: * Generates an array of MasterTableJournal objects that specify the
214: * changes that occur to each table affected by this transaction. Each array
215: * element represents a change to an individual table in the conglomerate
216: * that changed as a result of this transaction.
217: * <p>
218: * This is used when a transaction successfully commits and we need to log
219: * the transaction changes with the master table.
220: * <p>
221: * If no changes occurred to a table, then no entry is returned here.
222: */
223: MasterTableJournal[] makeMasterTableJournals() {
224: ArrayList table_journals = new ArrayList();
225: int param_index = 0;
226:
227: MasterTableJournal master_journal = null;
228:
229: for (int i = 0; i < journal_entries; ++i) {
230: byte c = command_journal[i];
231: if (c == TABLE_ADD || c == TABLE_REMOVE) {
232: int table_id = command_parameters.intAt(param_index);
233: int row_index = command_parameters
234: .intAt(param_index + 1);
235: param_index += 2;
236:
237: // Do we already have this table journal?
238: if (master_journal == null
239: || master_journal.getTableID() != table_id) {
240: // Try to find the journal in the list.
241: int size = table_journals.size();
242: master_journal = null;
243: for (int n = 0; n < size && master_journal == null; ++n) {
244: MasterTableJournal test_journal = (MasterTableJournal) table_journals
245: .get(n);
246: if (test_journal.getTableID() == table_id) {
247: master_journal = test_journal;
248: }
249: }
250:
251: // Not found so add to list.
252: if (master_journal == null) {
253: master_journal = new MasterTableJournal(
254: table_id);
255: table_journals.add(master_journal);
256: }
257:
258: }
259:
260: // Add this change to the table journal.
261: master_journal.addEntry(c, row_index);
262:
263: } else if (c == TABLE_CREATE || c == TABLE_DROP
264: || c == TABLE_CONSTRAINT_ALTER) {
265: param_index += 1;
266: } else {
267: throw new Error("Unknown journal command.");
268: }
269: }
270:
271: // Return the array.
272: return (MasterTableJournal[]) table_journals
273: .toArray(new MasterTableJournal[table_journals.size()]);
274:
275: }
276:
277: /**
278: * Returns the list of tables id's that were dropped by this journal.
279: */
280: IntegerVector getTablesDropped() {
281: IntegerVector dropped_tables = new IntegerVector();
282: // Optimization, quickly return empty set if we know there are no tables.
283: if (!has_dropped_tables) {
284: return dropped_tables;
285: }
286:
287: int param_index = 0;
288: for (int i = 0; i < journal_entries; ++i) {
289: byte c = command_journal[i];
290: if (c == TABLE_ADD || c == TABLE_REMOVE) {
291: param_index += 2;
292: } else if (c == TABLE_CREATE || c == TABLE_CONSTRAINT_ALTER) {
293: param_index += 1;
294: } else if (c == TABLE_DROP) {
295: dropped_tables.addInt(command_parameters
296: .intAt(param_index));
297: param_index += 1;
298: } else {
299: throw new Error("Unknown journal command.");
300: }
301: }
302:
303: return dropped_tables;
304: }
305:
306: /**
307: * Returns the list of tables id's that were created by this journal.
308: */
309: IntegerVector getTablesCreated() {
310: IntegerVector created_tables = new IntegerVector();
311: // Optimization, quickly return empty set if we know there are no tables.
312: if (!has_created_tables) {
313: return created_tables;
314: }
315:
316: int param_index = 0;
317: for (int i = 0; i < journal_entries; ++i) {
318: byte c = command_journal[i];
319: if (c == TABLE_ADD || c == TABLE_REMOVE) {
320: param_index += 2;
321: } else if (c == TABLE_DROP || c == TABLE_CONSTRAINT_ALTER) {
322: param_index += 1;
323: } else if (c == TABLE_CREATE) {
324: created_tables.addInt(command_parameters
325: .intAt(param_index));
326: param_index += 1;
327: } else {
328: throw new Error("Unknown journal command.");
329: }
330: }
331:
332: return created_tables;
333: }
334:
335: /**
336: * Returns the list of tables id's that were constraint altered by this
337: * journal.
338: */
339: IntegerVector getTablesConstraintAltered() {
340: IntegerVector caltered_tables = new IntegerVector();
341: // Optimization, quickly return empty set if we know there are no tables.
342: if (!has_constraint_alterations) {
343: return caltered_tables;
344: }
345:
346: int param_index = 0;
347: for (int i = 0; i < journal_entries; ++i) {
348: byte c = command_journal[i];
349: if (c == TABLE_ADD || c == TABLE_REMOVE) {
350: param_index += 2;
351: } else if (c == TABLE_DROP || c == TABLE_CREATE) {
352: param_index += 1;
353: } else if (c == TABLE_CONSTRAINT_ALTER) {
354: caltered_tables.addInt(command_parameters
355: .intAt(param_index));
356: param_index += 1;
357: } else {
358: throw new Error("Unknown journal command.");
359: }
360: }
361:
362: return caltered_tables;
363: }
364:
365: }
|