EMMA Coverage Report (generated Sun Mar 01 22:06:14 CET 2015)
[all classes][org.h2.mvstore.db]

COVERAGE SUMMARY FOR SOURCE FILE [MVTable.java]

nameclass, %method, %block, %line, %
MVTable.java100% (2/2)96%  (47/49)91%  (1509/1665)90%  (350.3/389)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class MVTable100% (1/1)96%  (45/47)91%  (1497/1653)90%  (349.3/388)
getDiskSpaceUsed (): long 0%   (0/1)0%   (0/4)0%   (0/1)
getUniqueIndex (): Index 0%   (0/1)0%   (0/3)0%   (0/1)
rebuildIndex (Session, MVIndex, String): void 100% (1/1)49%  (18/37)38%  (5/13)
checkDeadlock (Session, Session, Set): ArrayList 100% (1/1)77%  (77/100)75%  (21/28)
doLock1 (Session, int, boolean): void 100% (1/1)82%  (101/123)76%  (25/33)
addRow (Session, Row): void 100% (1/1)85%  (73/86)85%  (17/20)
rebuildIndexBuffered (Session, Index): void 100% (1/1)86%  (91/106)95%  (20/21)
rebuildIndexBlockMerge (Session, MVIndex): void 100% (1/1)88%  (133/152)95%  (35/37)
removeChildrenAndResources (Session): void 100% (1/1)89%  (89/100)96%  (22/23)
unlock (Session): void 100% (1/1)90%  (46/51)94%  (10.4/11)
getDeadlockDetails (ArrayList, boolean): String 100% (1/1)91%  (92/101)88%  (15/17)
getMainIndexColumn (IndexType, IndexColumn []): int 100% (1/1)94%  (34/36)91%  (10/11)
lock (Session, boolean, boolean): boolean 100% (1/1)96%  (96/100)96%  (22/23)
addIndex (Session, String, int, IndexColumn [], IndexType, boolean, String): ... 100% (1/1)97%  (160/165)97%  (31/32)
doLock2 (Session, int, boolean): boolean 100% (1/1)98%  (79/81)95%  (20/21)
MVTable (CreateTableData, MVTableEngine$Store): void 100% (1/1)100% (61/61)100% (12/12)
addRowsToIndex (Session, ArrayList, Index): void 100% (1/1)100% (21/21)100% (6/6)
analyzeIfRequired (Session): void 100% (1/1)100% (40/40)100% (9/9)
canDrop (): boolean 100% (1/1)100% (2/2)100% (1/1)
canGetRowCount (): boolean 100% (1/1)100% (2/2)100% (1/1)
canTruncate (): boolean 100% (1/1)100% (44/44)100% (11/11)
checkRename (): void 100% (1/1)100% (1/1)100% (1/1)
checkSupportAlter (): void 100% (1/1)100% (1/1)100% (1/1)
close (Session): void 100% (1/1)100% (1/1)100% (1/1)
commit (): void 100% (1/1)100% (9/9)100% (3/3)
getContainsLargeObject (): boolean 100% (1/1)100% (3/3)100% (1/1)
getIndexes (): ArrayList 100% (1/1)100% (3/3)100% (1/1)
getLockSyncObject (): Object 100% (1/1)100% (9/9)100% (3/3)
getMapName (): String 100% (1/1)100% (4/4)100% (1/1)
getMaxDataModificationId (): long 100% (1/1)100% (3/3)100% (1/1)
getRow (Session, long): Row 100% (1/1)100% (6/6)100% (1/1)
getRowCount (Session): long 100% (1/1)100% (5/5)100% (1/1)
getRowCountApproximation (): long 100% (1/1)100% (4/4)100% (1/1)
getRowIdColumn (): Column 100% (1/1)100% (18/18)100% (4/4)
getScanIndex (Session): Index 100% (1/1)100% (3/3)100% (1/1)
getTableType (): String 100% (1/1)100% (2/2)100% (1/1)
getTransaction (Session): TransactionStore$Transaction 100% (1/1)100% (9/9)100% (3/3)
init (Session): void 100% (1/1)100% (22/22)100% (3/3)
isDeterministic (): boolean 100% (1/1)100% (2/2)100% (1/1)
isLockedExclusively (): boolean 100% (1/1)100% (7/7)100% (1/1)
isLockedExclusivelyBy (Session): boolean 100% (1/1)100% (8/8)100% (1/1)
isMVStore (): boolean 100% (1/1)100% (2/2)100% (1/1)
removeRow (Session, Row): void 100% (1/1)100% (44/44)100% (12/12)
sortRows (ArrayList, Index): void 100% (1/1)100% (7/7)100% (2/2)
toString (): String 100% (1/1)100% (3/3)100% (1/1)
traceLock (Session, boolean, String): void 100% (1/1)100% (34/34)100% (3/3)
truncate (Session): void 100% (1/1)100% (28/28)100% (6/6)
     
class MVTable$1100% (1/1)100% (2/2)100% (12/12)100% (2/2)
MVTable$1 (Index): void 100% (1/1)100% (6/6)100% (1/1)
compare (Row, Row): int 100% (1/1)100% (6/6)100% (1/1)

1/*
2 * Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0,
3 * and the EPL 1.0 (http://h2database.com/html/license.html).
4 * Initial Developer: H2 Group
5 */
6package org.h2.mvstore.db;
7 
8import java.util.ArrayDeque;
9import java.util.ArrayList;
10import java.util.Collections;
11import java.util.Comparator;
12import java.util.Set;
13import java.util.concurrent.ConcurrentHashMap;
14import org.h2.api.DatabaseEventListener;
15import org.h2.api.ErrorCode;
16import org.h2.command.ddl.Analyze;
17import org.h2.command.ddl.CreateTableData;
18import org.h2.constraint.Constraint;
19import org.h2.constraint.ConstraintReferential;
20import org.h2.engine.Constants;
21import org.h2.engine.DbObject;
22import org.h2.engine.Session;
23import org.h2.engine.SysProperties;
24import org.h2.index.Cursor;
25import org.h2.index.Index;
26import org.h2.index.IndexType;
27import org.h2.index.MultiVersionIndex;
28import org.h2.message.DbException;
29import org.h2.message.Trace;
30import org.h2.mvstore.db.MVTableEngine.Store;
31import org.h2.mvstore.db.TransactionStore.Transaction;
32import org.h2.result.Row;
33import org.h2.result.SortOrder;
34import org.h2.schema.SchemaObject;
35import org.h2.table.Column;
36import org.h2.table.IndexColumn;
37import org.h2.table.Table;
38import org.h2.table.TableBase;
39import org.h2.util.MathUtils;
40import org.h2.util.New;
41import org.h2.value.DataType;
42import org.h2.value.Value;
43 
44/**
45 * A table stored in a MVStore.
46 */
47public class MVTable extends TableBase {
48 
49    private MVPrimaryIndex primaryIndex;
50    private final ArrayList<Index> indexes = New.arrayList();
51    private long lastModificationId;
52    private volatile Session lockExclusiveSession;
53 
54    // using a ConcurrentHashMap as a set
55    private final ConcurrentHashMap<Session, Session> lockSharedSessions =
56            new ConcurrentHashMap<Session, Session>();
57 
58    /**
59     * The queue of sessions waiting to lock the table. It is a FIFO queue to
60     * prevent starvation, since Java's synchronized locking is biased.
61     */
62    private final ArrayDeque<Session> waitingSessions = new ArrayDeque<Session>();
63    private final Trace traceLock;
64    private int changesSinceAnalyze;
65    private int nextAnalyze;
66    private boolean containsLargeObject;
67    private Column rowIdColumn;
68 
69    private final TransactionStore store;
70 
71    public MVTable(CreateTableData data, MVTableEngine.Store store) {
72        super(data);
73        nextAnalyze = database.getSettings().analyzeAuto;
74        this.store = store.getTransactionStore();
75        this.isHidden = data.isHidden;
76        for (Column col : getColumns()) {
77            if (DataType.isLargeObject(col.getType())) {
78                containsLargeObject = true;
79            }
80        }
81        traceLock = database.getTrace(Trace.LOCK);
82    }
83 
84    /**
85     * Initialize the table.
86     *
87     * @param session the session
88     */
89    void init(Session session) {
90        primaryIndex = new MVPrimaryIndex(session.getDatabase(), this, getId(),
91                IndexColumn.wrap(getColumns()), IndexType.createScan(true));
92        indexes.add(primaryIndex);
93    }
94 
95    public String getMapName() {
96        return primaryIndex.getMapName();
97    }
98 
99    @Override
100    public boolean lock(Session session, boolean exclusive,
101            boolean forceLockEvenInMvcc) {
102        int lockMode = database.getLockMode();
103        if (lockMode == Constants.LOCK_MODE_OFF) {
104            return false;
105        }
106        if (!forceLockEvenInMvcc && database.isMultiVersion()) {
107            // MVCC: update, delete, and insert use a shared lock.
108            // Select doesn't lock except when using FOR UPDATE and
109            // the system property h2.selectForUpdateMvcc
110            // is not enabled
111            if (exclusive) {
112                exclusive = false;
113            } else {
114                if (lockExclusiveSession == null) {
115                    return false;
116                }
117            }
118        }
119        if (lockExclusiveSession == session) {
120            return true;
121        }
122        if (!exclusive && lockSharedSessions.contains(session)) {
123            return true;
124        }
125        synchronized (getLockSyncObject()) {
126            if (!exclusive && lockSharedSessions.contains(session)) {
127                return true;
128            }
129            session.setWaitForLock(this, Thread.currentThread());
130            waitingSessions.addLast(session);
131            try {
132                doLock1(session, lockMode, exclusive);
133            } finally {
134                session.setWaitForLock(null, null);
135                waitingSessions.remove(session);
136            }
137        }
138        return false;
139    }
140 
141    /**
142     * The the object on which to synchronize and wait on. For the
143     * multi-threaded mode, this is this object, but for non-multi-threaded, it
144     * is the database, as in this case all operations are synchronized on the
145     * database object.
146     *
147     * @return the lock sync object
148     */
149    private Object getLockSyncObject() {
150        if (database.isMultiThreaded()) {
151            return this;
152        }
153        return database;
154    }
155 
156    private void doLock1(Session session, int lockMode, boolean exclusive) {
157        traceLock(session, exclusive, "requesting for");
158        // don't get the current time unless necessary
159        long max = 0;
160        boolean checkDeadlock = false;
161        while (true) {
162            // if I'm the next one in the queue
163            if (waitingSessions.getFirst() == session) {
164                if (doLock2(session, lockMode, exclusive)) {
165                    return;
166                }
167            }
168            if (checkDeadlock) {
169                ArrayList<Session> sessions = checkDeadlock(session, null, null);
170                if (sessions != null) {
171                    throw DbException.get(ErrorCode.DEADLOCK_1,
172                            getDeadlockDetails(sessions, exclusive));
173                }
174            } else {
175                // check for deadlocks from now on
176                checkDeadlock = true;
177            }
178            long now = System.currentTimeMillis();
179            if (max == 0) {
180                // try at least one more time
181                max = now + session.getLockTimeout();
182            } else if (now >= max) {
183                traceLock(session, exclusive,
184                        "timeout after " + session.getLockTimeout());
185                throw DbException.get(ErrorCode.LOCK_TIMEOUT_1, getName());
186            }
187            try {
188                traceLock(session, exclusive, "waiting for");
189                if (database.getLockMode() == Constants.LOCK_MODE_TABLE_GC) {
190                    for (int i = 0; i < 20; i++) {
191                        long free = Runtime.getRuntime().freeMemory();
192                        System.gc();
193                        long free2 = Runtime.getRuntime().freeMemory();
194                        if (free == free2) {
195                            break;
196                        }
197                    }
198                }
199                // don't wait too long so that deadlocks are detected early
200                long sleep = Math.min(Constants.DEADLOCK_CHECK, max - now);
201                if (sleep == 0) {
202                    sleep = 1;
203                }
204                getLockSyncObject().wait(sleep);
205            } catch (InterruptedException e) {
206                // ignore
207            }
208        }
209    }
210 
211    private boolean doLock2(Session session, int lockMode, boolean exclusive) {
212        if (exclusive) {
213            if (lockExclusiveSession == null) {
214                if (lockSharedSessions.isEmpty()) {
215                    traceLock(session, exclusive, "added for");
216                    session.addLock(this);
217                    lockExclusiveSession = session;
218                    return true;
219                } else if (lockSharedSessions.size() == 1 &&
220                        lockSharedSessions.contains(session)) {
221                    traceLock(session, exclusive, "add (upgraded) for ");
222                    lockExclusiveSession = session;
223                    return true;
224                }
225            }
226        } else {
227            if (lockExclusiveSession == null) {
228                if (lockMode == Constants.LOCK_MODE_READ_COMMITTED) {
229                    if (!database.isMultiThreaded() &&
230                            !database.isMultiVersion()) {
231                        // READ_COMMITTED: a read lock is acquired,
232                        // but released immediately after the operation
233                        // is complete.
234                        // When allowing only one thread, no lock is
235                        // required.
236                        // Row level locks work like read committed.
237                        return true;
238                    }
239                }
240                if (!lockSharedSessions.contains(session)) {
241                    traceLock(session, exclusive, "ok");
242                    session.addLock(this);
243                    lockSharedSessions.put(session, session);
244                }
245                return true;
246            }
247        }
248        return false;
249    }
250 
251    private static String getDeadlockDetails(ArrayList<Session> sessions, boolean exclusive) {
252        // We add the thread details here to make it easier for customers to
253        // match up these error messages with their own logs.
254        StringBuilder buff = new StringBuilder();
255        for (Session s : sessions) {
256            Table lock = s.getWaitForLock();
257            Thread thread = s.getWaitForLockThread();
258            buff.append("\nSession ").append(s.toString())
259                    .append(" on thread ").append(thread.getName())
260                    .append(" is waiting to lock ").append(lock.toString())
261                    .append(exclusive ? " (exclusive)" : " (shared)")
262                    .append(" while locking ");
263            int i = 0;
264            for (Table t : s.getLocks()) {
265                if (i++ > 0) {
266                    buff.append(", ");
267                }
268                buff.append(t.toString());
269                if (t instanceof MVTable) {
270                    if (((MVTable) t).lockExclusiveSession == s) {
271                        buff.append(" (exclusive)");
272                    } else {
273                        buff.append(" (shared)");
274                    }
275                }
276            }
277            buff.append('.');
278        }
279        return buff.toString();
280    }
281 
282    @Override
283    public ArrayList<Session> checkDeadlock(Session session, Session clash,
284            Set<Session> visited) {
285        // only one deadlock check at any given time
286        synchronized (MVTable.class) {
287            if (clash == null) {
288                // verification is started
289                clash = session;
290                visited = New.hashSet();
291            } else if (clash == session) {
292                // we found a circle where this session is involved
293                return New.arrayList();
294            } else if (visited.contains(session)) {
295                // we have already checked this session.
296                // there is a circle, but the sessions in the circle need to
297                // find it out themselves
298                return null;
299            }
300            visited.add(session);
301            ArrayList<Session> error = null;
302            for (Session s : lockSharedSessions.keySet()) {
303                if (s == session) {
304                    // it doesn't matter if we have locked the object already
305                    continue;
306                }
307                Table t = s.getWaitForLock();
308                if (t != null) {
309                    error = t.checkDeadlock(s, clash, visited);
310                    if (error != null) {
311                        error.add(session);
312                        break;
313                    }
314                }
315            }
316            if (error == null && lockExclusiveSession != null) {
317                Table t = lockExclusiveSession.getWaitForLock();
318                if (t != null) {
319                    error = t.checkDeadlock(lockExclusiveSession, clash,
320                            visited);
321                    if (error != null) {
322                        error.add(session);
323                    }
324                }
325            }
326            return error;
327        }
328    }
329 
330    private void traceLock(Session session, boolean exclusive, String s) {
331        if (traceLock.isDebugEnabled()) {
332            traceLock.debug("{0} {1} {2} {3}", session.getId(),
333                    exclusive ? "exclusive write lock" : "shared read lock", s,
334                    getName());
335        }
336    }
337 
338    @Override
339    public boolean isLockedExclusively() {
340        return lockExclusiveSession != null;
341    }
342 
343    @Override
344    public boolean isLockedExclusivelyBy(Session session) {
345        return lockExclusiveSession == session;
346    }
347 
348    @Override
349    public void unlock(Session s) {
350        if (database != null) {
351            traceLock(s, lockExclusiveSession == s, "unlock");
352            if (lockExclusiveSession == s) {
353                lockExclusiveSession = null;
354            }
355            synchronized (getLockSyncObject()) {
356                if (lockSharedSessions.size() > 0) {
357                    lockSharedSessions.remove(s);
358                }
359                if (!waitingSessions.isEmpty()) {
360                    getLockSyncObject().notifyAll();
361                }
362            }
363        }
364    }
365 
366    @Override
367    public boolean canTruncate() {
368        if (getCheckForeignKeyConstraints() &&
369                database.getReferentialIntegrity()) {
370            ArrayList<Constraint> constraints = getConstraints();
371            if (constraints != null) {
372                for (int i = 0, size = constraints.size(); i < size; i++) {
373                    Constraint c = constraints.get(i);
374                    if (!(c.getConstraintType().equals(Constraint.REFERENTIAL))) {
375                        continue;
376                    }
377                    ConstraintReferential ref = (ConstraintReferential) c;
378                    if (ref.getRefTable() == this) {
379                        return false;
380                    }
381                }
382            }
383        }
384        return true;
385    }
386 
387    @Override
388    public void close(Session session) {
389        // ignore
390    }
391 
392    @Override
393    public Row getRow(Session session, long key) {
394        return primaryIndex.getRow(session, key);
395    }
396 
397    @Override
398    public Index addIndex(Session session, String indexName, int indexId,
399            IndexColumn[] cols, IndexType indexType, boolean create,
400            String indexComment) {
401        if (indexType.isPrimaryKey()) {
402            for (IndexColumn c : cols) {
403                Column column = c.column;
404                if (column.isNullable()) {
405                    throw DbException.get(
406                            ErrorCode.COLUMN_MUST_NOT_BE_NULLABLE_1,
407                            column.getName());
408                }
409                column.setPrimaryKey(true);
410            }
411        }
412        boolean isSessionTemporary = isTemporary() && !isGlobalTemporary();
413        if (!isSessionTemporary) {
414            database.lockMeta(session);
415        }
416        MVIndex index;
417        // TODO support in-memory indexes
418        // if (isPersistIndexes() && indexType.isPersistent()) {
419        int mainIndexColumn;
420        mainIndexColumn = getMainIndexColumn(indexType, cols);
421        if (database.isStarting()) {
422            if (store.store.hasMap("index." + indexId)) {
423                mainIndexColumn = -1;
424            }
425        } else if (primaryIndex.getRowCountMax() != 0) {
426            mainIndexColumn = -1;
427        }
428        if (mainIndexColumn != -1) {
429            primaryIndex.setMainIndexColumn(mainIndexColumn);
430            index = new MVDelegateIndex(this, indexId, indexName, primaryIndex,
431                    indexType);
432        } else if (indexType.isSpatial()) {
433            index = new MVSpatialIndex(session.getDatabase(), this, indexId,
434                    indexName, cols, indexType);
435        } else {
436            index = new MVSecondaryIndex(session.getDatabase(), this, indexId,
437                    indexName, cols, indexType);
438        }
439        if (index.needRebuild()) {
440            rebuildIndex(session, index, indexName);
441        }
442        index.setTemporary(isTemporary());
443        if (index.getCreateSQL() != null) {
444            index.setComment(indexComment);
445            if (isSessionTemporary) {
446                session.addLocalTempTableIndex(index);
447            } else {
448                database.addSchemaObject(session, index);
449            }
450        }
451        indexes.add(index);
452        setModified();
453        return index;
454    }
455 
456    private void rebuildIndex(Session session, MVIndex index, String indexName) {
457        try {
458            if (session.getDatabase().getMvStore() == null ||
459                    index instanceof MVSpatialIndex) {
460                // in-memory
461                rebuildIndexBuffered(session, index);
462            } else {
463                rebuildIndexBlockMerge(session, index);
464            }
465        } catch (DbException e) {
466            getSchema().freeUniqueName(indexName);
467            try {
468                index.remove(session);
469            } catch (DbException e2) {
470                // this could happen, for example on failure in the storage
471                // but if that is not the case it means
472                // there is something wrong with the database
473                trace.error(e2, "could not remove index");
474                throw e2;
475            }
476            throw e;
477        }
478    }
479 
480    private void rebuildIndexBlockMerge(Session session, MVIndex index) {
481        if (index instanceof MVSpatialIndex) {
482            // the spatial index doesn't support multi-way merge sort
483            rebuildIndexBuffered(session, index);
484        }
485        // Read entries in memory, sort them, write to a new map (in sorted
486        // order); repeat (using a new map for every block of 1 MB) until all
487        // record are read. Merge all maps to the target (using merge sort;
488        // duplicates are detected in the target). For randomly ordered data,
489        // this should use relatively few write operations.
490        // A possible optimization is: change the buffer size from "row count"
491        // to "amount of memory", and buffer index keys instead of rows.
492        Index scan = getScanIndex(session);
493        long remaining = scan.getRowCount(session);
494        long total = remaining;
495        Cursor cursor = scan.find(session, null, null);
496        long i = 0;
497        Store store = session.getDatabase().getMvStore();
498 
499        int bufferSize = database.getMaxMemoryRows() / 2;
500        ArrayList<Row> buffer = New.arrayList(bufferSize);
501        String n = getName() + ":" + index.getName();
502        int t = MathUtils.convertLongToInt(total);
503        ArrayList<String> bufferNames = New.arrayList();
504        while (cursor.next()) {
505            Row row = cursor.get();
506            buffer.add(row);
507            database.setProgress(DatabaseEventListener.STATE_CREATE_INDEX, n,
508                    MathUtils.convertLongToInt(i++), t);
509            if (buffer.size() >= bufferSize) {
510                sortRows(buffer, index);
511                String mapName = store.nextTemporaryMapName();
512                index.addRowsToBuffer(buffer, mapName);
513                bufferNames.add(mapName);
514                buffer.clear();
515            }
516            remaining--;
517        }
518        sortRows(buffer, index);
519        if (bufferNames.size() > 0) {
520            String mapName = store.nextTemporaryMapName();
521            index.addRowsToBuffer(buffer, mapName);
522            bufferNames.add(mapName);
523            buffer.clear();
524            index.addBufferedRows(bufferNames);
525        } else {
526            addRowsToIndex(session, buffer, index);
527        }
528        if (SysProperties.CHECK && remaining != 0) {
529            DbException.throwInternalError("rowcount remaining=" + remaining +
530                    " " + getName());
531        }
532    }
533 
534    private void rebuildIndexBuffered(Session session, Index index) {
535        Index scan = getScanIndex(session);
536        long remaining = scan.getRowCount(session);
537        long total = remaining;
538        Cursor cursor = scan.find(session, null, null);
539        long i = 0;
540        int bufferSize = (int) Math.min(total, database.getMaxMemoryRows());
541        ArrayList<Row> buffer = New.arrayList(bufferSize);
542        String n = getName() + ":" + index.getName();
543        int t = MathUtils.convertLongToInt(total);
544        while (cursor.next()) {
545            Row row = cursor.get();
546            buffer.add(row);
547            database.setProgress(DatabaseEventListener.STATE_CREATE_INDEX, n,
548                    MathUtils.convertLongToInt(i++), t);
549            if (buffer.size() >= bufferSize) {
550                addRowsToIndex(session, buffer, index);
551            }
552            remaining--;
553        }
554        addRowsToIndex(session, buffer, index);
555        if (SysProperties.CHECK && remaining != 0) {
556            DbException.throwInternalError("rowcount remaining=" + remaining +
557                    " " + getName());
558        }
559    }
560 
561    private int getMainIndexColumn(IndexType indexType, IndexColumn[] cols) {
562        if (primaryIndex.getMainIndexColumn() != -1) {
563            return -1;
564        }
565        if (!indexType.isPrimaryKey() || cols.length != 1) {
566            return -1;
567        }
568        IndexColumn first = cols[0];
569        if (first.sortType != SortOrder.ASCENDING) {
570            return -1;
571        }
572        switch (first.column.getType()) {
573        case Value.BYTE:
574        case Value.SHORT:
575        case Value.INT:
576        case Value.LONG:
577            break;
578        default:
579            return -1;
580        }
581        return first.column.getColumnId();
582    }
583 
584    private static void addRowsToIndex(Session session, ArrayList<Row> list,
585            Index index) {
586        sortRows(list, index);
587        for (Row row : list) {
588            index.add(session, row);
589        }
590        list.clear();
591    }
592 
593    private static void sortRows(ArrayList<Row> list, final Index index) {
594        Collections.sort(list, new Comparator<Row>() {
595            @Override
596            public int compare(Row r1, Row r2) {
597                return index.compareRows(r1, r2);
598            }
599        });
600    }
601 
602    @Override
603    public void removeRow(Session session, Row row) {
604        lastModificationId = database.getNextModificationDataId();
605        Transaction t = getTransaction(session);
606        long savepoint = t.setSavepoint();
607        try {
608            for (int i = indexes.size() - 1; i >= 0; i--) {
609                Index index = indexes.get(i);
610                index.remove(session, row);
611            }
612        } catch (Throwable e) {
613            t.rollbackToSavepoint(savepoint);
614            throw DbException.convert(e);
615        }
616        analyzeIfRequired(session);
617    }
618 
619    @Override
620    public void truncate(Session session) {
621        lastModificationId = database.getNextModificationDataId();
622        for (int i = indexes.size() - 1; i >= 0; i--) {
623            Index index = indexes.get(i);
624            index.truncate(session);
625        }
626        changesSinceAnalyze = 0;
627    }
628 
629    @Override
630    public void addRow(Session session, Row row) {
631        lastModificationId = database.getNextModificationDataId();
632        Transaction t = getTransaction(session);
633        long savepoint = t.setSavepoint();
634        try {
635            for (int i = 0, size = indexes.size(); i < size; i++) {
636                Index index = indexes.get(i);
637                index.add(session, row);
638            }
639        } catch (Throwable e) {
640            t.rollbackToSavepoint(savepoint);
641            DbException de = DbException.convert(e);
642            if (de.getErrorCode() == ErrorCode.DUPLICATE_KEY_1) {
643                for (int j = 0; j < indexes.size(); j++) {
644                    Index index = indexes.get(j);
645                    if (index.getIndexType().isUnique() &&
646                            index instanceof MultiVersionIndex) {
647                        MultiVersionIndex mv = (MultiVersionIndex) index;
648                        if (mv.isUncommittedFromOtherSession(session, row)) {
649                            throw DbException.get(
650                                    ErrorCode.CONCURRENT_UPDATE_1,
651                                    index.getName());
652                        }
653                    }
654                }
655            }
656            throw de;
657        }
658        analyzeIfRequired(session);
659    }
660 
661    private void analyzeIfRequired(Session session) {
662        if (nextAnalyze == 0 || nextAnalyze > changesSinceAnalyze++) {
663            return;
664        }
665        changesSinceAnalyze = 0;
666        int n = 2 * nextAnalyze;
667        if (n > 0) {
668            nextAnalyze = n;
669        }
670        int rows = session.getDatabase().getSettings().analyzeSample / 10;
671        Analyze.analyzeTable(session, this, rows, false);
672    }
673 
674    @Override
675    public void checkSupportAlter() {
676        // ok
677    }
678 
679    @Override
680    public String getTableType() {
681        return Table.TABLE;
682    }
683 
684    @Override
685    public Index getScanIndex(Session session) {
686        return primaryIndex;
687    }
688 
689    @Override
690    public Index getUniqueIndex() {
691        return primaryIndex;
692    }
693 
694    @Override
695    public ArrayList<Index> getIndexes() {
696        return indexes;
697    }
698 
699    @Override
700    public long getMaxDataModificationId() {
701        return lastModificationId;
702    }
703 
704    public boolean getContainsLargeObject() {
705        return containsLargeObject;
706    }
707 
708    @Override
709    public boolean isDeterministic() {
710        return true;
711    }
712 
713    @Override
714    public boolean canGetRowCount() {
715        return true;
716    }
717 
718    @Override
719    public boolean canDrop() {
720        return true;
721    }
722 
723    @Override
724    public void removeChildrenAndResources(Session session) {
725        if (containsLargeObject) {
726            // unfortunately, the data is gone on rollback
727            truncate(session);
728            database.getLobStorage().removeAllForTable(getId());
729            database.lockMeta(session);
730        }
731        database.getMvStore().removeTable(this);
732        super.removeChildrenAndResources(session);
733        // go backwards because database.removeIndex will
734        // call table.removeIndex
735        while (indexes.size() > 1) {
736            Index index = indexes.get(1);
737            if (index.getName() != null) {
738                database.removeSchemaObject(session, index);
739            }
740            // needed for session temporary indexes
741            indexes.remove(index);
742        }
743        if (SysProperties.CHECK) {
744            for (SchemaObject obj : database
745                    .getAllSchemaObjects(DbObject.INDEX)) {
746                Index index = (Index) obj;
747                if (index.getTable() == this) {
748                    DbException.throwInternalError("index not dropped: " +
749                            index.getName());
750                }
751            }
752        }
753        primaryIndex.remove(session);
754        database.removeMeta(session, getId());
755        close(session);
756        invalidate();
757    }
758 
759    @Override
760    public long getRowCount(Session session) {
761        return primaryIndex.getRowCount(session);
762    }
763 
764    @Override
765    public long getRowCountApproximation() {
766        return primaryIndex.getRowCountApproximation();
767    }
768 
769    @Override
770    public long getDiskSpaceUsed() {
771        return primaryIndex.getDiskSpaceUsed();
772    }
773 
774    @Override
775    public void checkRename() {
776        // ok
777    }
778 
779    /**
780     * Get the transaction to use for this session.
781     *
782     * @param session the session
783     * @return the transaction
784     */
785    Transaction getTransaction(Session session) {
786        if (session == null) {
787            // TODO need to commit/rollback the transaction
788            return store.begin();
789        }
790        return session.getTransaction();
791    }
792 
793    @Override
794    public Column getRowIdColumn() {
795        if (rowIdColumn == null) {
796            rowIdColumn = new Column(Column.ROWID, Value.LONG);
797            rowIdColumn.setTable(this, -1);
798        }
799        return rowIdColumn;
800    }
801 
802    @Override
803    public String toString() {
804        return getSQL();
805    }
806 
807    @Override
808    public boolean isMVStore() {
809        return true;
810    }
811 
812    /**
813     * Mark the transaction as committed, so that the modification counter of
814     * the database is incremented.
815     */
816    public void commit() {
817        if (database != null) {
818            lastModificationId = database.getNextModificationDataId();
819        }
820    }
821 
822}

[all classes][org.h2.mvstore.db]
EMMA 2.0.5312 (C) Vladimir Roubtsov