001: /*
002: * $Id: BaseTable.java,v 1.28 2005/12/22 09:02:31 ahimanikya Exp $
003: * =======================================================================
004: * Copyright (c) 2002-2005 Axion Development Team. All rights reserved.
005: *
006: * Redistribution and use in source and binary forms, with or without
007: * modification, are permitted provided that the following conditions
008: * are met:
009: *
010: * 1. Redistributions of source code must retain the above
011: * copyright notice, this list of conditions and the following
012: * disclaimer.
013: *
014: * 2. Redistributions in binary form must reproduce the above copyright
015: * notice, this list of conditions and the following disclaimer in
016: * the documentation and/or other materials provided with the
017: * distribution.
018: *
019: * 3. The names "Tigris", "Axion", nor the names of its contributors may
020: * not be used to endorse or promote products derived from this
021: * software without specific prior written permission.
022: *
023: * 4. Products derived from this software may not be called "Axion", nor
024: * may "Tigris" or "Axion" appear in their names without specific prior
025: * written permission.
026: *
027: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
028: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
029: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
030: * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
031: * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
032: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
033: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
034: * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
035: * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
036: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
037: * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
038: * =======================================================================
039: */
040:
041: package org.axiondb.engine.tables;
042:
043: import java.io.File;
044: import java.io.IOException;
045: import java.io.ObjectInputStream;
046: import java.io.ObjectOutputStream;
047: import java.util.ArrayList;
048: import java.util.Collections;
049: import java.util.HashMap;
050: import java.util.Iterator;
051: import java.util.List;
052: import java.util.Map;
053:
054: import org.apache.commons.collections.primitives.IntCollection;
055: import org.apache.commons.collections.primitives.IntCollections;
056: import org.apache.commons.collections.primitives.IntIterator;
057: import org.apache.commons.logging.Log;
058: import org.apache.commons.logging.LogFactory;
059: import org.axiondb.AxionException;
060: import org.axiondb.BindVariable;
061: import org.axiondb.Column;
062: import org.axiondb.ColumnIdentifier;
063: import org.axiondb.Constraint;
064: import org.axiondb.ConstraintViolationException;
065: import org.axiondb.Database;
066: import org.axiondb.Function;
067: import org.axiondb.Index;
068: import org.axiondb.Literal;
069: import org.axiondb.Row;
070: import org.axiondb.RowCollection;
071: import org.axiondb.RowDecorator;
072: import org.axiondb.RowIterator;
073: import org.axiondb.RowSource;
074: import org.axiondb.Selectable;
075: import org.axiondb.Sequence;
076: import org.axiondb.Table;
077: import org.axiondb.TableIdentifier;
078: import org.axiondb.TransactableTable;
079: import org.axiondb.constraints.ForeignKeyConstraint;
080: import org.axiondb.constraints.PrimaryKeyConstraint;
081: import org.axiondb.constraints.UniqueConstraint;
082: import org.axiondb.engine.TransactableTableImpl;
083: import org.axiondb.engine.rowcollection.RowCollections;
084: import org.axiondb.engine.rowiterators.RebindableIndexedRowIterator;
085: import org.axiondb.engine.rowiterators.UnmodifiableRowIterator;
086: import org.axiondb.event.ColumnEvent;
087: import org.axiondb.event.ConstraintEvent;
088: import org.axiondb.event.RowDeletedEvent;
089: import org.axiondb.event.RowEvent;
090: import org.axiondb.event.RowInsertedEvent;
091: import org.axiondb.event.RowUpdatedEvent;
092: import org.axiondb.event.TableModificationListener;
093: import org.axiondb.functions.ComparisonFunction;
094: import org.axiondb.types.LOBType;
095: import org.axiondb.util.ValuePool;
096:
097: /**
098: * An abstract base implementation of {@link Table}.
099: *
100: * @version $Revision: 1.28 $ $Date: 2005/12/22 09:02:31 $
101: * @author Chuck Burdick
102: * @author Rodney Waldhoff
103: * @author Ahimanikya Satapathy
104: * @author Jonathan Giron
105: */
106: public abstract class BaseTable extends AbstractBaseTable implements
107: Table {
108: // having these interface methods defined here as abstract seems to keep some j2me
109: // implementations happy
110: public abstract void applyDeletes(IntCollection rowids)
111: throws AxionException;
112:
113: public abstract void applyInserts(RowCollection rows)
114: throws AxionException;
115:
116: public abstract void applyUpdates(RowCollection rows)
117: throws AxionException;
118:
119: public abstract void freeRowId(int id);
120:
121: public abstract int getNextRowId();
122:
123: public abstract int getRowCount();
124:
125: public abstract void populateIndex(Index index)
126: throws AxionException;
127:
128: public abstract Row getRow(int id) throws AxionException;
129:
130: protected abstract RowIterator getRowIterator()
131: throws AxionException;
132:
133: public BaseTable(String name) {
134: _name = (name + "").toUpperCase();
135: setType(REGULAR_TABLE_TYPE);
136: }
137:
138: public RowIterator getRowIterator(boolean readOnly)
139: throws AxionException {
140: if (readOnly) {
141: return UnmodifiableRowIterator.wrap(getRowIterator());
142: }
143: return getRowIterator();
144: }
145:
146: public void addRow(Row row) throws AxionException {
147: int rowid = getNextRowId();
148: row.setIdentifier(rowid);
149: RowInsertedEvent event = new RowInsertedEvent(this , null, row);
150: try {
151: checkConstraints(event, makeRowDecorator());
152: } catch (AxionException e) {
153: freeRowId(rowid);
154: throw e;
155: }
156: applyInserts(RowCollections.singletonList(row));
157: }
158:
159: public void checkpoint() throws AxionException {
160: }
161:
162: public void setSequence(Sequence seq) throws AxionException {
163: _sequence = seq;
164: }
165:
166: public final Sequence getSequence() {
167: return _sequence;
168: }
169:
170: public void deleteRow(Row row) throws AxionException {
171: RowDeletedEvent event = new RowDeletedEvent(this , row, null);
172: checkConstraints(event, makeRowDecorator());
173: applyDeletes(IntCollections.singletonIntList(row
174: .getIdentifier()));
175: }
176:
177: public void updateRow(Row oldrow, Row newrow) throws AxionException {
178: newrow.setIdentifier(oldrow.getIdentifier());
179: RowUpdatedEvent event = new RowUpdatedEvent(this , oldrow,
180: newrow);
181: checkConstraints(event, makeRowDecorator());
182: applyUpdates(RowCollections.singletonList(newrow));
183: }
184:
185: protected void truncateIndices() throws AxionException {
186: for (int i = 0, I = _indices.size(); i < I; i++) {
187: ((Index) _indices.get(i)).truncate();
188: }
189: }
190:
191: protected void recreateIndices() throws AxionException {
192: for (int i = 0, I = _indices.size(); i < I; i++) {
193: populateIndex((Index) _indices.get(i));
194: }
195: }
196:
197: public String toString() {
198: return getName();
199: }
200:
201: public final String getName() {
202: return _name;
203: }
204:
205: public final String getType() {
206: return _type;
207: }
208:
209: protected void setType(String type) {
210: _type = type;
211: }
212:
213: protected void setName(String name) {
214: _name = name;
215: }
216:
217: public void addConstraint(Constraint constraint)
218: throws AxionException {
219: addConstraint(constraint, true);
220: }
221:
222: private void addConstraint(Constraint constraint,
223: boolean checkExistingRows) throws AxionException {
224: if (constraint instanceof PrimaryKeyConstraint
225: && null != getPrimaryKey()) {
226: throw new AxionException(
227: "This table already has a primary key");
228: } else if (_constraints.containsKey(constraint.getName())) {
229: throw new AxionException("A constraint named "
230: + constraint.getName() + " already exists.");
231: } else {
232: if (checkExistingRows) {
233: RowDecorator dec = makeRowDecorator();
234: for (RowIterator iter = getRowIterator(); iter
235: .hasNext();) {
236: Row current = iter.next();
237: RowEvent event = new RowUpdatedEvent(this , current,
238: current);
239: if (!constraint.evaluate(event, dec)) {
240: throw new ConstraintViolationException(
241: constraint);
242: }
243: }
244: }
245: _constraints.put(constraint.getName(), constraint);
246:
247: Iterator iter = getTableModificationListeners();
248: while (iter.hasNext()) {
249: TableModificationListener listener = (TableModificationListener) (iter
250: .next());
251: listener.constraintAdded(new ConstraintEvent(this ,
252: constraint));
253: }
254: }
255: }
256:
257: public final Constraint getConstraint(String name) {
258: return (Constraint) _constraints.get(name);
259: }
260:
261: public Constraint removeConstraint(String name) {
262: if (name != null) {
263: if ("PRIMARYKEY".equals(name)) {
264: Constraint pk = getPrimaryKey();
265: if (pk != null) {
266: name = pk.getName();
267: } else {
268: name = null;
269: }
270: }
271: }
272: if (_constraints.containsKey(name)) {
273: Constraint constraint = (Constraint) _constraints.get(name);
274: Iterator iter = getTableModificationListeners();
275: while (iter.hasNext()) {
276: TableModificationListener listener = (TableModificationListener) (iter
277: .next());
278: try {
279: listener.constraintRemoved(new ConstraintEvent(
280: this , constraint));
281: } catch (AxionException e) {
282: _log
283: .error(
284: "Unable to publish constraint removed event",
285: e);
286: }
287: }
288: return (Constraint) _constraints.remove(name);
289: }
290: return null;
291: }
292:
293: /**
294: * check if unique constraint exists on a column
295: *
296: * @param columnName name of the columm
297: * @return true if uniqueConstraint exists on the column
298: */
299: public boolean isUniqueConstraintExists(String columnName) {
300: boolean result = false;
301: for (Iterator iter = _constraints.values().iterator(); iter
302: .hasNext();) {
303: Object constraint = iter.next();
304: if (constraint instanceof UniqueConstraint) {
305: UniqueConstraint uk = (UniqueConstraint) constraint;
306: if (uk.getSelectableCount() == 1) {
307: ColumnIdentifier cid = (ColumnIdentifier) (uk
308: .getSelectableList().get(0));
309: if (columnName.equals(cid.getName())) {
310: result = true;
311: }
312: }
313: }
314: }
315: return result;
316: }
317:
318: /**
319: * check if primary constraint exists on a column
320: *
321: * @param ColumnName name of the column
322: * @return if PrimaryKeyConstraint exists on the column
323: */
324: // TODO: keep Pk as memerber variable ?
325: public boolean isPrimaryKeyConstraintExists(String columnName) {
326: boolean result = false;
327: for (Iterator iter = _constraints.values().iterator(); iter
328: .hasNext();) {
329: Object constraint = iter.next();
330: if (constraint instanceof PrimaryKeyConstraint) {
331: UniqueConstraint uk = (UniqueConstraint) constraint;
332: if (uk.getSelectableCount() == 1) {
333: ColumnIdentifier cid = (ColumnIdentifier) (uk
334: .getSelectableList().get(0));
335: if (columnName.equals(cid.getName())) {
336: result = true;
337: }
338: }
339: }
340: }
341: return result;
342: }
343:
344: private PrimaryKeyConstraint getPrimaryKey() {
345: for (Iterator iter = _constraints.values().iterator(); iter
346: .hasNext();) {
347: Constraint constraint = (Constraint) (iter.next());
348: if (constraint instanceof PrimaryKeyConstraint) {
349: return (PrimaryKeyConstraint) (constraint);
350: }
351: }
352: return null;
353: }
354:
355: public Iterator getConstraints() {
356: return _constraints.values().iterator();
357: }
358:
359: public void addIndex(Index index) throws AxionException {
360: _indices.add(index);
361: addTableModificationListener(index);
362: }
363:
364: public void removeIndex(Index index) throws AxionException {
365: _indices.remove(index);
366: this .removeTableModificationListener(index);
367: }
368:
369: public Index getIndexForColumn(Column column) {
370: for (int i = 0, I = _indices.size(); i < I; i++) {
371: Index index = (Index) _indices.get(i);
372: if (column.equals(index.getIndexedColumn())) {
373: return index;
374: }
375: }
376: return null;
377: }
378:
379: public boolean isColumnIndexed(Column column) {
380: for (int i = 0, I = _indices.size(); i < I; i++) {
381: Index index = (Index) _indices.get(i);
382: if (column.equals(index.getIndexedColumn())) {
383: return true;
384: }
385: }
386: return false;
387: }
388:
389: public RowIterator getIndexedRows(Selectable node, boolean readOnly)
390: throws AxionException {
391: return getIndexedRows(this , node, readOnly);
392: }
393:
394: public RowIterator getIndexedRows(RowSource source,
395: Selectable node, boolean readOnly) throws AxionException {
396: if (readOnly) {
397: return UnmodifiableRowIterator.wrap(getIndexedRows(source,
398: node));
399: }
400: return getIndexedRows(source, node);
401: }
402:
403: /**
404: * Add the given {@link Column}to this table. This implementation throws an
405: * {@link AxionException}if rows have already been added to the table.
406: */
407: public void addColumn(Column col) throws AxionException {
408: if (getRowCount() > 0) {
409: throw new AxionException(
410: "Cannot add column because table already contains rows.");
411: }
412:
413: // FIXME: ?
414: if (col.getDataType() instanceof LOBType) {
415: LOBType lob = (LOBType) (col.getDataType());
416: if (null == lob.getLobDir()) {
417: File lobDir = new File(System.getProperty(
418: "axiondb.lobdir", ".").toUpperCase(), col
419: .getName().toUpperCase());
420: lob.setLobDir(lobDir);
421: }
422: }
423: _cols.add(col);
424: clearCache();
425: publishEvent(new ColumnEvent(this , col));
426: }
427:
428: protected final void clearCache() {
429: _colIndexToColIdMap = null;
430: }
431:
432: public boolean hasColumn(ColumnIdentifier id) {
433: boolean result = false;
434: String tableName = id.getTableName();
435: if (tableName == null || tableName.equals(getName())) {
436: result = (getColumn(id.getName()) != null);
437: }
438: return result;
439: }
440:
441: public final Column getColumn(int index) {
442: return (Column) (_cols.get(index));
443: }
444:
445: public Column getColumn(String name) {
446: for (int i = 0, I = _cols.size(); i < I; i++) {
447: Column col = (Column) (_cols.get(i));
448: if (col.getName().equalsIgnoreCase(name)) {
449: return col;
450: }
451: }
452: return null;
453: }
454:
455: public int getColumnIndex(String name) throws AxionException {
456: for (int i = 0, I = _cols.size(); i < I; i++) {
457: Column col = (Column) (_cols.get(i));
458: if (col.getName().equalsIgnoreCase(name)) {
459: return i;
460: }
461: }
462: throw new AxionException("Column " + name + " not found.");
463: }
464:
465: public List getColumnIdentifiers() {
466: List colids = new ArrayList();
467: for (int i = 0, I = _cols.size(); i < I; i++) {
468: Column col = (Column) (_cols.get(i));
469: colids
470: .add(new ColumnIdentifier(new TableIdentifier(
471: getName()), col.getName(), null, col
472: .getDataType()));
473: }
474: return Collections.unmodifiableList(colids);
475: }
476:
477: public final int getColumnCount() {
478: return _cols.size();
479: }
480:
481: public void drop() throws AxionException {
482: }
483:
484: public void remount(File dir, boolean datafilesonly)
485: throws AxionException {
486: }
487:
488: public void rename(String oldName, String newName)
489: throws AxionException {
490: setName(newName);
491: clearCache();
492: }
493:
494: public void shutdown() throws AxionException {
495: }
496:
497: public RowDecorator makeRowDecorator() {
498: if (null == _colIndexToColIdMap) {
499: int size = _cols.size();
500: Map map = new HashMap(size);
501: for (int i = 0; i < size; i++) {
502: Column col = (Column) (_cols.get(i));
503: ColumnIdentifier colid = new ColumnIdentifier(
504: new TableIdentifier(getName()), col.getName(),
505: null, col.getDataType());
506: map.put(colid, ValuePool.getInt(i));
507: }
508: _colIndexToColIdMap = map;
509: }
510: return new RowDecorator(_colIndexToColIdMap);
511: }
512:
513: public TransactableTable makeTransactableTable() {
514: return new TransactableTableImpl(this );
515: }
516:
517: public Iterator getIndices() {
518: return _indices.iterator();
519: }
520:
521: public boolean hasIndex(String name) {
522: String upperName = name.toUpperCase();
523: for (int i = 0, I = _indices.size(); i < I; i++) {
524: Index index = (Index) _indices.get(i);
525: if (upperName.equals(index.getName())) {
526: return true;
527: }
528: }
529: return false;
530: }
531:
532: protected void notifyColumnsOfNewLobDir(File directory)
533: throws AxionException {
534: for (int i = 0, I = _cols.size(); i < I; i++) {
535: Column col = (Column) (_cols.get(i));
536: if (col.getDataType() instanceof LOBType) {
537: LOBType lob = (LOBType) (col.getDataType());
538: lob.setLobDir(new File(directory, col.getName()));
539: }
540: }
541: }
542:
543: protected void writeColumns(ObjectOutputStream out)
544: throws IOException {
545: out.writeObject(_cols);
546: }
547:
548: protected void readColumns(ObjectInputStream in)
549: throws IOException, ClassNotFoundException {
550: _cols = (List) (in.readObject());
551: }
552:
553: protected void writeConstraints(ObjectOutputStream out)
554: throws IOException {
555: out.writeObject(_constraints);
556: }
557:
558: protected void readConstraints(ObjectInputStream in, Database db)
559: throws IOException, ClassNotFoundException, AxionException {
560: _constraints = (Map) (in.readObject());
561:
562: for (Iterator iter = _constraints.values().iterator(); iter
563: .hasNext();) {
564: Constraint constraint = (Constraint) (iter.next());
565: if (constraint instanceof ForeignKeyConstraint) {
566: ForeignKeyConstraint fk = (ForeignKeyConstraint) constraint;
567: fk.setParentTable(db.getTable(fk.getParentTableName()));
568: fk.setChildTable(db.getTable(fk.getChildTableName()));
569: }
570: }
571: }
572:
573: // XXX: Index can be applied in batch ?
574: protected void applyDeletesToIndices(IntCollection rowIds)
575: throws AxionException {
576: for (IntIterator ids = rowIds.iterator(); ids.hasNext();) {
577: Row deleted = getRow(ids.next());
578: if (deleted != null) {
579: RowEvent event = new RowDeletedEvent(this , deleted,
580: null);
581: for (int i = 0, I = _indices.size(); i < I; i++) {
582: Index index = (Index) _indices.get(i);
583: index.rowDeleted(event);
584: }
585: }
586: }
587: }
588:
589: protected void applyUpdatesToIndices(RowCollection rows)
590: throws AxionException {
591: for (RowIterator iter = rows.rowIterator(); iter.hasNext();) {
592: Row newrow = iter.next();
593: Row oldrow = getRow(newrow.getIdentifier());
594: if (oldrow != null) {
595: RowEvent event = new RowUpdatedEvent(this , oldrow,
596: newrow);
597: for (int i = 0, I = _indices.size(); i < I; i++) {
598: Index index = (Index) _indices.get(i);
599: index.rowUpdated(event);
600: }
601: }
602: }
603: }
604:
605: protected void applyInsertsToIndices(RowCollection rows)
606: throws AxionException {
607: for (RowIterator iter = rows.rowIterator(); iter.hasNext();) {
608: Row row = iter.next();
609: RowEvent event = new RowInsertedEvent(this , null, row);
610: for (int i = 0, I = _indices.size(); i < I; i++) {
611: Index index = (Index) _indices.get(i);
612: index.rowInserted(event);
613: }
614: }
615: }
616:
617: private RowIterator getIndexedRows(RowSource source, Selectable node)
618: throws AxionException {
619: if (node instanceof ComparisonFunction) {
620: // attempting to map comparison function to existing index
621: ComparisonFunction function = (ComparisonFunction) node;
622:
623: Column column = null;
624: Literal literal = null;
625: Selectable left = function.getArgument(0);
626: Selectable right = function.getArgument(1);
627: if (left instanceof ColumnIdentifier
628: && right instanceof Literal) {
629: column = getColumn(((ColumnIdentifier) left).getName());
630: literal = (Literal) (right);
631: } else if (left instanceof Literal
632: && right instanceof ColumnIdentifier) {
633: column = getColumn(((ColumnIdentifier) right).getName());
634: literal = (Literal) (left);
635: function = function.flip();
636: } else {
637: return null;
638: }
639:
640: if (!isColumnIndexed(column)) {
641: // no index for column
642: return null;
643: }
644:
645: Index index = getIndexForColumn(column);
646: if (!index.supportsFunction(function)) {
647: // index does not support required function
648: return null;
649: } else if (literal instanceof BindVariable) {
650: // index found
651: return new RebindableIndexedRowIterator(index, source,
652: function, (BindVariable) literal);
653: } else {
654: // index found
655: return index.getRowIterator(source, function, literal
656: .evaluate(null));
657: }
658: } else if (node instanceof ColumnIdentifier) {
659: Column column = getColumn(((ColumnIdentifier) node)
660: .getName());
661: Index index = getIndexForColumn(column);
662: if (index != null) {
663: return index.getInorderRowIterator(source);
664: }
665: } else if (node instanceof Function) { // IS NULL and IS NOT NULL
666: Function function = (Function) node;
667: if (function.getArgumentCount() != 1) {
668: return null;
669: }
670:
671: Selectable colid = function.getArgument(0);
672: if (colid instanceof ColumnIdentifier) {
673: Column column = getColumn(((ColumnIdentifier) colid)
674: .getName());
675: if (!isColumnIndexed(column)) {
676: // no index for column
677: return null;
678: }
679:
680: Index index = getIndexForColumn(column);
681: if (!index.supportsFunction(function)) {
682: // index does not support required function
683: return null;
684: } else {
685: // index found
686: return index.getRowIterator(source, function, null);
687: }
688: }
689: }
690:
691: return null; // No matching index found
692: }
693:
694: private String _name;
695: private String _type;
696: private List _cols = new ArrayList();
697: private List _indices = new ArrayList(4);
698: private Map _constraints = new HashMap(4);
699: private Map _colIndexToColIdMap;
700:
701: // as per ANSI at most we can have one internal sequence per table,
702: // hence one identity column per table
703: private Sequence _sequence;
704:
705: private static Log _log = LogFactory.getLog(BaseTable.class);
706: }
|