001: /*
002: * Copyright 2004-2008 H2 Group. Licensed under the H2 License, Version 1.0
003: * (http://h2database.com/html/license.html).
004: * Initial Developer: H2 Group
005: */
006: package org.h2.index;
007:
008: import java.sql.SQLException;
009: import org.h2.engine.Database;
010: import org.h2.engine.Session;
011: import org.h2.message.Message;
012: import org.h2.result.Row;
013: import org.h2.result.SearchRow;
014: import org.h2.schema.Schema;
015: import org.h2.table.Column;
016: import org.h2.table.IndexColumn;
017: import org.h2.table.Table;
018: import org.h2.table.TableData;
019: import org.h2.util.ObjectArray;
020:
021: /**
022: * A multi-version index is a combination of a regular index,
023: * and a in-memory tree index that contains uncommitted changes.
024: * Uncommitted changes can include new rows, and deleted rows.
025: */
026: public class MultiVersionIndex implements Index {
027:
028: private final Index base;
029: private final TreeIndex delta;
030: private final TableData table;
031: private final Object sync;
032:
033: public MultiVersionIndex(Index base, TableData table)
034: throws SQLException {
035: this .base = base;
036: this .table = table;
037: IndexType deltaIndexType = IndexType.createNonUnique(false);
038: this .delta = new TreeIndex(table, -1, "DELTA", base
039: .getIndexColumns(), deltaIndexType);
040: this .sync = base.getDatabase();
041: }
042:
043: public void add(Session session, Row row) throws SQLException {
044: synchronized (sync) {
045: base.add(session, row);
046: if (removeIfExists(session, row)) {
047: // for example rolling back an delete operation
048: } else if (row.getSessionId() != 0) {
049: // don't insert rows that are added when creating an index
050: delta.add(session, row);
051: }
052: }
053: }
054:
055: public void close(Session session) throws SQLException {
056: synchronized (sync) {
057: base.close(session);
058: }
059: }
060:
061: public Cursor find(Session session, SearchRow first, SearchRow last)
062: throws SQLException {
063: synchronized (sync) {
064: Cursor baseCursor = base.find(session, first, last);
065: Cursor deltaCursor = delta.find(session, first, last);
066: return new MultiVersionCursor(session, this , baseCursor,
067: deltaCursor, sync);
068: }
069: }
070:
071: public Cursor findNext(Session session, SearchRow first,
072: SearchRow last) throws SQLException {
073: throw Message.getInternalError();
074: }
075:
076: public boolean canFindNext() {
077: // TODO possible, but more complicated
078: return false;
079: }
080:
081: public boolean canGetFirstOrLast() {
082: // TODO in many cases possible, but more complicated
083: return false;
084: }
085:
086: public SearchRow findFirstOrLast(Session session, boolean first)
087: throws SQLException {
088: throw Message.getUnsupportedException();
089: }
090:
091: public double getCost(Session session, int[] masks)
092: throws SQLException {
093: return base.getCost(session, masks);
094: }
095:
096: public boolean needRebuild() {
097: return base.needRebuild();
098: }
099:
100: private boolean removeIfExists(Session session, Row row)
101: throws SQLException {
102: // maybe it was inserted by the same session just before
103: Cursor c = delta.find(session, row, row);
104: while (c.next()) {
105: Row r = c.get();
106: if (r.getPos() == row.getPos()) {
107: delta.remove(session, row);
108: return true;
109: }
110: }
111: return false;
112: }
113:
114: public void remove(Session session, Row row) throws SQLException {
115: synchronized (sync) {
116: base.remove(session, row);
117: if (removeIfExists(session, row)) {
118: // added and deleted in the same transaction: no change
119: } else {
120: delta.add(session, row);
121: }
122: }
123: }
124:
125: public void remove(Session session) throws SQLException {
126: synchronized (sync) {
127: base.remove(session);
128: }
129: }
130:
131: public void truncate(Session session) throws SQLException {
132: synchronized (sync) {
133: delta.truncate(session);
134: base.truncate(session);
135: }
136: }
137:
138: public void commit(int operation, Row row) throws SQLException {
139: synchronized (sync) {
140: removeIfExists(null, row);
141: }
142: }
143:
144: public int compareKeys(SearchRow rowData, SearchRow compare) {
145: return base.compareKeys(rowData, compare);
146: }
147:
148: public int compareRows(SearchRow rowData, SearchRow compare)
149: throws SQLException {
150: return base.compareRows(rowData, compare);
151: }
152:
153: public int getColumnIndex(Column col) {
154: return base.getColumnIndex(col);
155: }
156:
157: public String getColumnListSQL() {
158: return base.getColumnListSQL();
159: }
160:
161: public Column[] getColumns() {
162: return base.getColumns();
163: }
164:
165: public IndexColumn[] getIndexColumns() {
166: return base.getIndexColumns();
167: }
168:
169: public long getCostRangeIndex(int[] masks, long rowCount)
170: throws SQLException {
171: return base.getCostRangeIndex(masks, rowCount);
172: }
173:
174: public String getCreateSQL() {
175: return base.getCreateSQL();
176: }
177:
178: public String getCreateSQLForCopy(Table table, String quotedName) {
179: return base.getCreateSQLForCopy(table, quotedName);
180: }
181:
182: public String getDropSQL() {
183: return base.getDropSQL();
184: }
185:
186: public SQLException getDuplicateKeyException() {
187: return base.getDuplicateKeyException();
188: }
189:
190: public IndexType getIndexType() {
191: return base.getIndexType();
192: }
193:
194: public int getLookupCost(long rowCount) {
195: return base.getLookupCost(rowCount);
196: }
197:
198: public String getPlanSQL() {
199: return base.getPlanSQL();
200: }
201:
202: public long getRowCount(Session session) {
203: return base.getRowCount(session);
204: }
205:
206: public Table getTable() {
207: return base.getTable();
208: }
209:
210: public int getType() {
211: return base.getType();
212: }
213:
214: public boolean isNull(Row newRow) {
215: return base.isNull(newRow);
216: }
217:
218: public void removeChildrenAndResources(Session session)
219: throws SQLException {
220: synchronized (sync) {
221: table.removeIndex(this );
222: remove(session);
223: }
224: }
225:
226: public String getSQL() {
227: return base.getSQL();
228: }
229:
230: public Schema getSchema() {
231: return base.getSchema();
232: }
233:
234: public void checkRename() throws SQLException {
235: base.checkRename();
236: }
237:
238: public ObjectArray getChildren() {
239: return base.getChildren();
240: }
241:
242: public String getComment() {
243: return base.getComment();
244: }
245:
246: public Database getDatabase() {
247: return base.getDatabase();
248: }
249:
250: public int getHeadPos() {
251: return base.getHeadPos();
252: }
253:
254: public int getId() {
255: return base.getId();
256: }
257:
258: public long getModificationId() {
259: return base.getModificationId();
260: }
261:
262: public String getName() {
263: return base.getName();
264: }
265:
266: public boolean getTemporary() {
267: return base.getTemporary();
268: }
269:
270: public void rename(String newName) throws SQLException {
271: base.rename(newName);
272: }
273:
274: public void setComment(String comment) {
275: base.setComment(comment);
276: }
277:
278: public void setModified() {
279: base.setModified();
280: }
281:
282: public void setTemporary(boolean temporary) {
283: base.setTemporary(temporary);
284: }
285:
286: void debug(String s, Session session, SearchRow row)
287: throws SQLException {
288: // System.out.println(this + " " + s + " session:" +
289: // (session == null ? -1: session.getId()) + " " +
290: // (row == null ? "" : row.getValue(0).getString()));
291: }
292:
293: }
|