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 | */ |
6 | package org.h2.mvstore.db; |
7 | |
8 | import java.util.ArrayDeque; |
9 | import java.util.ArrayList; |
10 | import java.util.Collections; |
11 | import java.util.Comparator; |
12 | import java.util.Set; |
13 | import java.util.concurrent.ConcurrentHashMap; |
14 | import org.h2.api.DatabaseEventListener; |
15 | import org.h2.api.ErrorCode; |
16 | import org.h2.command.ddl.Analyze; |
17 | import org.h2.command.ddl.CreateTableData; |
18 | import org.h2.constraint.Constraint; |
19 | import org.h2.constraint.ConstraintReferential; |
20 | import org.h2.engine.Constants; |
21 | import org.h2.engine.DbObject; |
22 | import org.h2.engine.Session; |
23 | import org.h2.engine.SysProperties; |
24 | import org.h2.index.Cursor; |
25 | import org.h2.index.Index; |
26 | import org.h2.index.IndexType; |
27 | import org.h2.index.MultiVersionIndex; |
28 | import org.h2.message.DbException; |
29 | import org.h2.message.Trace; |
30 | import org.h2.mvstore.db.MVTableEngine.Store; |
31 | import org.h2.mvstore.db.TransactionStore.Transaction; |
32 | import org.h2.result.Row; |
33 | import org.h2.result.SortOrder; |
34 | import org.h2.schema.SchemaObject; |
35 | import org.h2.table.Column; |
36 | import org.h2.table.IndexColumn; |
37 | import org.h2.table.Table; |
38 | import org.h2.table.TableBase; |
39 | import org.h2.util.MathUtils; |
40 | import org.h2.util.New; |
41 | import org.h2.value.DataType; |
42 | import org.h2.value.Value; |
43 | |
44 | /** |
45 | * A table stored in a MVStore. |
46 | */ |
47 | public 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 | } |