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 java.util.Collections;
010: import java.util.HashMap;
011: import java.util.HashSet;
012: import java.util.Iterator;
013:
014: import org.h2.engine.Constants;
015: import org.h2.engine.Session;
016: import org.h2.log.UndoLogRecord;
017: import org.h2.message.Message;
018: import org.h2.result.Row;
019: import org.h2.result.SearchRow;
020: import org.h2.store.Storage;
021: import org.h2.table.Column;
022: import org.h2.table.IndexColumn;
023: import org.h2.table.TableData;
024: import org.h2.util.ObjectArray;
025: import org.h2.util.ObjectUtils;
026: import org.h2.value.Value;
027: import org.h2.value.ValueLob;
028:
029: /**
030: * The scan index is not really an 'index' in the strict sense, because it can
031: * not be used for direct lookup. It can only be used to iterate over all rows
032: * of a table. Each regular table has one such object, even if no primary key or
033: * indexes are defined.
034: */
035: public class ScanIndex extends BaseIndex {
036: private int firstFree = -1;
037: private ObjectArray rows = new ObjectArray();
038: private Storage storage;
039: private TableData tableData;
040: private int rowCountDiff;
041: private HashMap sessionRowCount;
042: private HashSet delta;
043:
044: public ScanIndex(TableData table, int id, IndexColumn[] columns,
045: IndexType indexType) throws SQLException {
046: super (table, id, table.getName() + "_TABLE_SCAN", columns,
047: indexType);
048: if (database.isMultiVersion()) {
049: sessionRowCount = new HashMap();
050: }
051: tableData = table;
052: if (!database.isPersistent() || id < 0) {
053: return;
054: }
055: this .storage = database.getStorage(table, id, true);
056: int count = storage.getRecordCount();
057: rowCount = count;
058: table.setRowCount(count);
059: trace.info("open existing " + table.getName() + " rows: "
060: + count);
061: }
062:
063: public void remove(Session session) throws SQLException {
064: truncate(session);
065: if (storage != null) {
066: storage.delete(session);
067: }
068: }
069:
070: public void truncate(Session session) throws SQLException {
071: if (storage == null) {
072: rows = new ObjectArray();
073: firstFree = -1;
074: } else {
075: storage.truncate(session);
076: }
077: if (tableData.getContainsLargeObject()
078: && tableData.isPersistent()) {
079: ValueLob.removeAllForTable(database, table.getId());
080: }
081: tableData.setRowCount(0);
082: rowCount = 0;
083: if (database.isMultiVersion()) {
084: sessionRowCount.clear();
085: }
086: }
087:
088: public String getCreateSQL() {
089: return null;
090: }
091:
092: public void close(Session session) throws SQLException {
093: if (storage != null) {
094: storage = null;
095: }
096: }
097:
098: public Row getRow(Session session, int key) throws SQLException {
099: if (storage != null) {
100: return (Row) storage.getRecord(session, key);
101: }
102: return (Row) rows.get(key);
103: }
104:
105: public void add(Session session, Row row) throws SQLException {
106: if (storage != null) {
107: if (tableData.getContainsLargeObject()) {
108: for (int i = 0; i < row.getColumnCount(); i++) {
109: Value v = row.getValue(i);
110: Value v2 = v.link(database, getId());
111: if (v2.isLinked()) {
112: session.unlinkAtCommitStop(v2);
113: }
114: if (v != v2) {
115: row.setValue(i, v2);
116: }
117: }
118: }
119: storage.addRecord(session, row, Storage.ALLOCATE_POS);
120: } else {
121: if (firstFree == -1) {
122: int key = rows.size();
123: row.setPos(key);
124: rows.add(row);
125: } else {
126: int key = firstFree;
127: Row free = (Row) rows.get(key);
128: firstFree = free.getPos();
129: row.setPos(key);
130: rows.set(key, row);
131: }
132: row.setDeleted(false);
133: }
134: if (database.isMultiVersion()) {
135: if (delta == null) {
136: delta = new HashSet();
137: }
138: boolean wasDeleted = delta.remove(row);
139: if (!wasDeleted) {
140: delta.add(row);
141: }
142: incrementRowCount(session.getId(), 1);
143: }
144: rowCount++;
145: }
146:
147: public void commit(int operation, Row row) throws SQLException {
148: if (database.isMultiVersion()) {
149: if (delta != null) {
150: delta.remove(row);
151: }
152: incrementRowCount(row.getSessionId(),
153: operation == UndoLogRecord.DELETE ? 1 : -1);
154: }
155: }
156:
157: private void incrementRowCount(int sessionId, int count) {
158: if (database.isMultiVersion()) {
159: Integer id = ObjectUtils.getInteger(sessionId);
160: Integer c = (Integer) sessionRowCount.get(id);
161: int current = c == null ? 0 : c.intValue();
162: sessionRowCount.put(id, ObjectUtils.getInteger(current
163: + count));
164: rowCountDiff += count;
165: }
166: }
167:
168: public void remove(Session session, Row row) throws SQLException {
169: if (storage != null) {
170: storage.removeRecord(session, row.getPos());
171: if (tableData.getContainsLargeObject()) {
172: for (int i = 0; i < row.getColumnCount(); i++) {
173: Value v = row.getValue(i);
174: if (v.isLinked()) {
175: session.unlinkAtCommit(v);
176: }
177: }
178: }
179: } else {
180: Row free = new Row(null, 0);
181: free.setPos(firstFree);
182: int key = row.getPos();
183: rows.set(key, free);
184: firstFree = key;
185: }
186: if (database.isMultiVersion()) {
187: // if storage is null, the delete flag is not yet set
188: row.setDeleted(true);
189: if (delta == null) {
190: delta = new HashSet();
191: }
192: boolean wasAdded = delta.remove(row);
193: if (!wasAdded) {
194: delta.add(row);
195: }
196: incrementRowCount(session.getId(), -1);
197: }
198: rowCount--;
199: }
200:
201: public Cursor find(Session session, SearchRow first, SearchRow last)
202: throws SQLException {
203: return new ScanCursor(session, this , database.isMultiVersion());
204: }
205:
206: public double getCost(Session session, int[] masks)
207: throws SQLException {
208: long cost = tableData.getRowCount(session)
209: + Constants.COST_ROW_OFFSET;
210: if (storage != null) {
211: cost *= 10;
212: }
213: return cost;
214: }
215:
216: public long getRowCount(Session session) {
217: if (database.isMultiVersion()) {
218: Integer i = (Integer) sessionRowCount.get(ObjectUtils
219: .getInteger(session.getId()));
220: long count = i == null ? 0 : i.intValue();
221: count += super .getRowCount(session);
222: count -= rowCountDiff;
223: return count;
224: }
225: return super .getRowCount(session);
226: }
227:
228: Row getNextRow(Session session, Row row) throws SQLException {
229: if (storage == null) {
230: int key;
231: if (row == null) {
232: key = -1;
233: } else {
234: key = row.getPos();
235: }
236: while (true) {
237: key++;
238: if (key >= rows.size()) {
239: return null;
240: }
241: row = (Row) rows.get(key);
242: if (!row.isEmpty()) {
243: return row;
244: }
245: }
246: }
247: int pos = storage.getNext(row);
248: if (pos < 0) {
249: return null;
250: }
251: return (Row) storage.getRecord(session, pos);
252: }
253:
254: public int getColumnIndex(Column col) {
255: // the scan index cannot use any columns
256: return -1;
257: }
258:
259: public void checkRename() throws SQLException {
260: throw Message.getUnsupportedException();
261: }
262:
263: public boolean needRebuild() {
264: return false;
265: }
266:
267: public boolean canGetFirstOrLast() {
268: return false;
269: }
270:
271: public SearchRow findFirstOrLast(Session session, boolean first)
272: throws SQLException {
273: throw Message.getUnsupportedException();
274: }
275:
276: public Iterator getDelta() {
277: return delta == null ? Collections.EMPTY_LIST.iterator()
278: : delta.iterator();
279: }
280:
281: }
|