File D:\maven-qcr-plugin-testsunitaires\qcr4java\h2-1.3.170-sources\target\site\qc\lecode\index\PageDataIndex.java.html

1 /*
2 * Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
3 * Version 1.0, and under the Eclipse Public License, Version 1.0
4 * (http://h2database.com/html/license.html).
5 * Initial Developer: H2 Group
6 */
7 package org.h2.index;
8
9 import java.util.Collections;
10 import java.util.HashMap;
11 import java.util.HashSet;
12 import java.util.Iterator;
13 import java.util.List;
14 import org.h2.constant.ErrorCode;
15 import org.h2.constant.SysProperties;
16 import org.h2.engine.Constants;
17 import org.h2.engine.Session;
18 import org.h2.engine.UndoLogRecord;
19 import org.h2.message.DbException;
20 import org.h2.result.Row;
21 import org.h2.result.SearchRow;
22 import org.h2.store.Page;
23 import org.h2.store.PageStore;
24 import org.h2.table.Column;
25 import org.h2.table.IndexColumn;
26 import org.h2.table.RegularTable;
27 import org.h2.util.MathUtils;
28 import org.h2.util.New;
29 import org.h2.value.Value;
30 import org.h2.value.ValueNull;
31
32 /**
33 * The scan index allows to access a row by key. It can be used to iterate over
34 * all rows of a table. Each regular table has one such object, even if no
35 * primary key or indexes are defined.
36 */
37 public class PageDataIndex extends PageIndex {
38
39 private final PageStore store;
40 private final RegularTable tableData;
41 private long lastKey;
42 private long rowCount;
43 private HashSet<Row> delta;
44 private int rowCountDiff;
45 private final HashMap<Integer, Integer> sessionRowCount;
46 private int mainIndexColumn = -1;
47 private DbException fastDuplicateKeyException;
48
49 /**
50 * The estimated heap memory per page, in number of double words (4 bytes
51 * each).
52 */
53 private int memoryPerPage;
54 private int memoryCount;
55
56 private final boolean multiVersion;
57
58 public PageDataIndex(RegularTable table, int id, IndexColumn[] columns,
59 IndexType indexType, boolean create, Session session) {
60 initBaseIndex(table, id, table.getName() + "_DATA", columns, indexType);
61 this.multiVersion = database.isMultiVersion();
62
63 // trace = database.getTrace(Trace.PAGE_STORE + "_di");
64 // trace.setLevel(TraceSystem.DEBUG);
65 if (multiVersion) {
66 sessionRowCount = New.hashMap();
67 isMultiVersion = true;
68 } else {
69 sessionRowCount = null;
70 }
71 tableData = table;
72 this.store = database.getPageStore();
73 store.addIndex(this);
74 if (!database.isPersistent()) {
75 throw DbException.throwInternalError(table.getName());
76 }
77 if (create) {
78 rootPageId = store.allocatePage();
79 store.addMeta(this, session);
80 PageDataLeaf root = PageDataLeaf.create(this, rootPageId, PageData.ROOT);
81 store.update(root);
82 } else {
83 rootPageId = store.getRootPageId(id);
84 PageData root = getPage(rootPageId, 0);
85 lastKey = root.getLastKey();
86 rowCount = root.getRowCount();
87 }
88 if (trace.isDebugEnabled()) {
89 trace.debug("{0} opened rows: {1}", this, rowCount);
90 }
91 table.setRowCount(rowCount);
92 memoryPerPage = (Constants.MEMORY_PAGE_DATA + store.getPageSize()) >> 2;
93 }
94
95 public DbException getDuplicateKeyException() {
96 if (fastDuplicateKeyException == null) {
97 fastDuplicateKeyException = super.getDuplicateKeyException();
98 }
99 return fastDuplicateKeyException;
100 }
101
102 public void add(Session session, Row row) {
103 boolean retry = false;
104 if (mainIndexColumn != -1) {
105 row.setKey(row.getValue(mainIndexColumn).getLong());
106 } else {
107 if (row.getKey() == 0) {
108 row.setKey((int) ++lastKey);
109 retry = true;
110 }
111 }
112 if (tableData.getContainsLargeObject()) {
113 for (int i = 0, len = row.getColumnCount(); i < len; i++) {
114 Value v = row.getValue(i);
115 Value v2 = v.link(database, getId());
116 if (v2.isLinked()) {
117 session.unlinkAtCommitStop(v2);
118 }
119 if (v != v2) {
120 row.setValue(i, v2);
121 }
122 }
123 }
124 // when using auto-generated values, it's possible that multiple
125 // tries are required (specially if there was originally a primary key)
126 if (trace.isDebugEnabled()) {
127 trace.debug("{0} add {1}", getName(), row);
128 }
129 long add = 0;
130 while (true) {
131 try {
132 addTry(session, row);
133 break;
134 } catch (DbException e) {
135 if (e != fastDuplicateKeyException) {
136 throw e;
137 }
138 if (!retry) {
139 throw getNewDuplicateKeyException();
140 }
141 if (add == 0) {
142 // in the first re-try add a small random number,
143 // to avoid collisions after a re-start
144 row.setKey((long) (row.getKey() + Math.random() * 10000));
145 } else {
146 row.setKey(row.getKey() + add);
147 }
148 add++;
149 } finally {
150 store.incrementChangeCount();
151 }
152 }
153 lastKey = Math.max(lastKey, row.getKey());
154 }
155
156 public DbException getNewDuplicateKeyException() {
157 String sql = "PRIMARY KEY ON " + table.getSQL();
158 if (mainIndexColumn >= 0 && mainIndexColumn < indexColumns.length) {
159 sql += "(" + indexColumns[mainIndexColumn].getSQL() + ")";
160 }
161 DbException e = DbException.get(ErrorCode.DUPLICATE_KEY_1, sql);
162 e.setSource(this);
163 return e;
164 }
165
166 private void addTry(Session session, Row row) {
167 while (true) {
168 PageData root = getPage(rootPageId, 0);
169 int splitPoint = root.addRowTry(row);
170 if (splitPoint == -1) {
171 break;
172 }
173 if (trace.isDebugEnabled()) {
174 trace.debug("{0} split", this);
175 }
176 long pivot = splitPoint == 0 ? row.getKey() : root.getKey(splitPoint - 1);
177 PageData page1 = root;
178 PageData page2 = root.split(splitPoint);
179 int id = store.allocatePage();
180 page1.setPageId(id);
181 page1.setParentPageId(rootPageId);
182 page2.setParentPageId(rootPageId);
183 PageDataNode newRoot = PageDataNode.create(this, rootPageId, PageData.ROOT);
184 newRoot.init(page1, pivot, page2);
185 store.update(page1);
186 store.update(page2);
187 store.update(newRoot);
188 root = newRoot;
189 }
190 row.setDeleted(false);
191 if (multiVersion) {
192 if (delta == null) {
193 delta = New.hashSet();
194 }
195 boolean wasDeleted = delta.remove(row);
196 if (!wasDeleted) {
197 delta.add(row);
198 }
199 incrementRowCount(session.getId(), 1);
200 }
201 invalidateRowCount();
202 rowCount++;
203 store.logAddOrRemoveRow(session, tableData.getId(), row, true);
204 }
205
206 /**
207 * Read an overflow page page.
208 *
209 * @param id the page id
210 * @return the page
211 */
212 PageDataOverflow getPageOverflow(int id) {
213 Page p = store.getPage(id);
214 if (p instanceof PageDataOverflow) {
215 return (PageDataOverflow) p;
216 }
217 throw DbException.get(ErrorCode.FILE_CORRUPTED_1, p == null ? "null" : p.toString());
218 }
219
220 /**
221 * Read the given page.
222 *
223 * @param id the page id
224 * @param parent the parent, or -1 if unknown
225 * @return the page
226 */
227 PageData getPage(int id, int parent) {
228 Page pd = store.getPage(id);
229 if (pd == null) {
230 PageDataLeaf empty = PageDataLeaf.create(this, id, parent);
231 // could have been created before, but never committed
232 store.logUndo(empty, null);
233 store.update(empty);
234 return empty;
235 } else if (!(pd instanceof PageData)) {
236 throw DbException.get(ErrorCode.FILE_CORRUPTED_1, "" + pd);
237 }
238 PageData p = (PageData) pd;
239 if (parent != -1) {
240 if (p.getParentPageId() != parent) {
241 throw DbException.throwInternalError(p + " parent " + p.getParentPageId() + " expected " + parent);
242 }
243 }
244 return p;
245 }
246
247 public boolean canGetFirstOrLast() {
248 return false;
249 }
250
251 /**
252 * Get the key from the row.
253 *
254 * @param row the row
255 * @param ifEmpty the value to use if the row is empty
256 * @param ifNull the value to use if the column is NULL
257 * @return the key
258 */
259 long getKey(SearchRow row, long ifEmpty, long ifNull) {
260 if (row == null) {
261 return ifEmpty;
262 }
263 Value v = row.getValue(mainIndexColumn);
264 if (v == null) {
265 throw DbException.throwInternalError(row.toString());
266 } else if (v == ValueNull.INSTANCE) {
267 return ifNull;
268 }
269 return v.getLong();
270 }
271
272 public Cursor find(Session session, SearchRow first, SearchRow last) {
273 long from = first == null ? Long.MIN_VALUE : first.getKey();
274 long to = last == null ? Long.MAX_VALUE : last.getKey();
275 PageData root = getPage(rootPageId, 0);
276 return root.find(session, from, to, isMultiVersion);
277
278 }
279
280 /**
281 * Search for a specific row or a set of rows.
282 *
283 * @param session the session
284 * @param first the key of the first row
285 * @param last the key of the last row
286 * @param multiVersion if mvcc should be used
287 * @return the cursor
288 */
289 Cursor find(Session session, long first, long last, boolean multiVersion) {
290 PageData root = getPage(rootPageId, 0);
291 return root.find(session, first, last, multiVersion);
292 }
293
294 public Cursor findFirstOrLast(Session session, boolean first) {
295 throw DbException.throwInternalError();
296 }
297
298 long getLastKey() {
299 PageData root = getPage(rootPageId, 0);
300 return root.getLastKey();
301 }
302
303 public double getCost(Session session, int[] masks) {
304 long cost = 10 * (tableData.getRowCountApproximation() + Constants.COST_ROW_OFFSET);
305 return cost;
306 }
307
308 public boolean needRebuild() {
309 return false;
310 }
311
312 public void remove(Session session, Row row) {
313 if (tableData.getContainsLargeObject()) {
314 for (int i = 0, len = row.getColumnCount(); i < len; i++) {
315 Value v = row.getValue(i);
316 if (v.isLinked()) {
317 session.unlinkAtCommit(v);
318 }
319 }
320 }
321 if (trace.isDebugEnabled()) {
322 trace.debug("{0} remove {1}", getName(), row);
323 }
324 if (rowCount == 1) {
325 removeAllRows();
326 } else {
327 try {
328 long key = row.getKey();
329 PageData root = getPage(rootPageId, 0);
330 root.remove(key);
331 invalidateRowCount();
332 rowCount--;
333 } finally {
334 store.incrementChangeCount();
335 }
336 }
337 if (multiVersion) {
338 // if storage is null, the delete flag is not yet set
339 row.setDeleted(true);
340 if (delta == null) {
341 delta = New.hashSet();
342 }
343 boolean wasAdded = delta.remove(row);
344 if (!wasAdded) {
345 delta.add(row);
346 }
347 incrementRowCount(session.getId(), -1);
348 }
349 store.logAddOrRemoveRow(session, tableData.getId(), row, false);
350 }
351
352 public void remove(Session session) {
353 if (trace.isDebugEnabled()) {
354 trace.debug("{0} remove", this);
355 }
356 removeAllRows();
357 store.free(rootPageId);
358 store.removeMeta(this, session);
359 }
360
361 public void truncate(Session session) {
362 if (trace.isDebugEnabled()) {
363 trace.debug("{0} truncate", this);
364 }
365 store.logTruncate(session, tableData.getId());
366 removeAllRows();
367 if (tableData.getContainsLargeObject() && tableData.isPersistData()) {
368 // unfortunately, the data is gone on rollback
369 session.commit(false);
370 database.getLobStorage().removeAllForTable(table.getId());
371 }
372 if (multiVersion) {
373 sessionRowCount.clear();
374 }
375 tableData.setRowCount(0);
376 }
377
378 private void removeAllRows() {
379 try {
380 PageData root = getPage(rootPageId, 0);
381 root.freeRecursive();
382 root = PageDataLeaf.create(this, rootPageId, PageData.ROOT);
383 store.removeRecord(rootPageId);
384 store.update(root);
385 rowCount = 0;
386 lastKey = 0;
387 } finally {
388 store.incrementChangeCount();
389 }
390 }
391
392 public void checkRename() {
393 throw DbException.getUnsupportedException("PAGE");
394 }
395
396 public Row getRow(Session session, long key) {
397 return getRowWithKey(key);
398 }
399
400 /**
401 * Get the row with the given key.
402 *
403 * @param key the key
404 * @return the row
405 */
406 public Row getRowWithKey(long key) {
407 PageData root = getPage(rootPageId, 0);
408 return root.getRowWithKey(key);
409 }
410
411 PageStore getPageStore() {
412 return store;
413 }
414
415 public long getRowCountApproximation() {
416 return rowCount;
417 }
418
419 public long getRowCount(Session session) {
420 if (multiVersion) {
421 Integer i = sessionRowCount.get(session.getId());
422 long count = i == null ? 0 : i.intValue();
423 count += rowCount;
424 count -= rowCountDiff;
425 return count;
426 }
427 return rowCount;
428 }
429
430 public String getCreateSQL() {
431 return null;
432 }
433
434 public int getColumnIndex(Column col) {
435 // can not use this index - use the PageDelegateIndex instead
436 return -1;
437 }
438
439 public void close(Session session) {
440 if (trace.isDebugEnabled()) {
441 trace.debug("{0} close", this);
442 }
443 if (delta != null) {
444 delta.clear();
445 }
446 rowCountDiff = 0;
447 if (sessionRowCount != null) {
448 sessionRowCount.clear();
449 }
450 // can not close the index because it might get used afterwards,
451 // for example after running recovery
452 writeRowCount();
453 }
454
455 Iterator<Row> getDelta() {
456 if (delta == null) {
457 List<Row> e = Collections.emptyList();
458 return e.iterator();
459 }
460 return delta.iterator();
461 }
462
463 private void incrementRowCount(int sessionId, int count) {
464 if (multiVersion) {
465 Integer id = sessionId;
466 Integer c = sessionRowCount.get(id);
467 int current = c == null ? 0 : c.intValue();
468 sessionRowCount.put(id, current + count);
469 rowCountDiff += count;
470 }
471 }
472
473 public void commit(int operation, Row row) {
474 if (multiVersion) {
475 if (delta != null) {
476 delta.remove(row);
477 }
478 incrementRowCount(row.getSessionId(), operation == UndoLogRecord.DELETE ? 1 : -1);
479 }
480 }
481
482 /**
483 * The root page has changed.
484 *
485 * @param session the session
486 * @param newPos the new position
487 */
488 void setRootPageId(Session session, int newPos) {
489 store.removeMeta(this, session);
490 this.rootPageId = newPos;
491 store.addMeta(this, session);
492 store.addIndex(this);
493 }
494
495 public void setMainIndexColumn(int mainIndexColumn) {
496 this.mainIndexColumn = mainIndexColumn;
497 }
498
499 public int getMainIndexColumn() {
500 return mainIndexColumn;
501 }
502
503 public String toString() {
504 return getName();
505 }
506
507 private void invalidateRowCount() {
508 PageData root = getPage(rootPageId, 0);
509 root.setRowCountStored(PageData.UNKNOWN_ROWCOUNT);
510 }
511
512 public void writeRowCount() {
513 if (SysProperties.MODIFY_ON_WRITE && rootPageId == 0) {
514 // currently creating the index
515 return;
516 }
517 try {
518 PageData root = getPage(rootPageId, 0);
519 root.setRowCountStored(MathUtils.convertLongToInt(rowCount));
520 } finally {
521 store.incrementChangeCount();
522 }
523 }
524
525 public String getPlanSQL() {
526 return table.getSQL() + ".tableScan";
527 }
528
529 int getMemoryPerPage() {
530 return memoryPerPage;
531 }
532
533 /**
534 * The memory usage of a page was changed. The new value is used to adopt
535 * the average estimated memory size of a page.
536 *
537 * @param x the new memory size
538 */
539 void memoryChange(int x) {
540 if (memoryCount < Constants.MEMORY_FACTOR) {
541 memoryPerPage += (x - memoryPerPage) / ++memoryCount;
542 } else {
543 memoryPerPage += (x > memoryPerPage ? 1 : -1) + ((x - memoryPerPage) / Constants.MEMORY_FACTOR);
544 }
545 }
546
547 public boolean isRowIdIndex() {
548 return true;
549 }
550
551 }
552