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.index; |
7 | |
8 | import org.h2.engine.Session; |
9 | import org.h2.engine.SysProperties; |
10 | import org.h2.message.DbException; |
11 | import org.h2.result.Row; |
12 | import org.h2.result.SearchRow; |
13 | import org.h2.util.MathUtils; |
14 | |
15 | /** |
16 | * The cursor implementation for the multi-version index. |
17 | */ |
18 | public class MultiVersionCursor implements Cursor { |
19 | |
20 | private final MultiVersionIndex index; |
21 | private final Session session; |
22 | private final Cursor baseCursor, deltaCursor; |
23 | private final Object sync; |
24 | private SearchRow baseRow; |
25 | private Row deltaRow; |
26 | private boolean onBase; |
27 | private boolean end; |
28 | private boolean needNewDelta, needNewBase; |
29 | private boolean reverse; |
30 | |
31 | MultiVersionCursor(Session session, MultiVersionIndex index, Cursor base, |
32 | Cursor delta, Object sync) { |
33 | this.session = session; |
34 | this.index = index; |
35 | this.baseCursor = base; |
36 | this.deltaCursor = delta; |
37 | this.sync = sync; |
38 | needNewDelta = true; |
39 | needNewBase = true; |
40 | } |
41 | |
42 | /** |
43 | * Load the current row. |
44 | */ |
45 | void loadCurrent() { |
46 | synchronized (sync) { |
47 | baseRow = baseCursor.getSearchRow(); |
48 | deltaRow = deltaCursor.get(); |
49 | needNewDelta = false; |
50 | needNewBase = false; |
51 | } |
52 | } |
53 | |
54 | private void loadNext(boolean base) { |
55 | synchronized (sync) { |
56 | if (base) { |
57 | if (step(baseCursor)) { |
58 | baseRow = baseCursor.getSearchRow(); |
59 | } else { |
60 | baseRow = null; |
61 | } |
62 | } else { |
63 | if (step(deltaCursor)) { |
64 | deltaRow = deltaCursor.get(); |
65 | } else { |
66 | deltaRow = null; |
67 | } |
68 | } |
69 | } |
70 | } |
71 | |
72 | private boolean step(Cursor cursor) { |
73 | return reverse ? cursor.previous() : cursor.next(); |
74 | } |
75 | |
76 | @Override |
77 | public Row get() { |
78 | synchronized (sync) { |
79 | if (end) { |
80 | return null; |
81 | } |
82 | return onBase ? baseCursor.get() : deltaCursor.get(); |
83 | } |
84 | } |
85 | |
86 | @Override |
87 | public SearchRow getSearchRow() { |
88 | synchronized (sync) { |
89 | if (end) { |
90 | return null; |
91 | } |
92 | return onBase ? baseCursor.getSearchRow() : deltaCursor.getSearchRow(); |
93 | } |
94 | } |
95 | |
96 | @Override |
97 | public boolean next() { |
98 | synchronized (sync) { |
99 | if (SysProperties.CHECK && end) { |
100 | DbException.throwInternalError(); |
101 | } |
102 | while (true) { |
103 | if (needNewDelta) { |
104 | loadNext(false); |
105 | needNewDelta = false; |
106 | } |
107 | if (needNewBase) { |
108 | loadNext(true); |
109 | needNewBase = false; |
110 | } |
111 | if (deltaRow == null) { |
112 | if (baseRow == null) { |
113 | end = true; |
114 | return false; |
115 | } |
116 | onBase = true; |
117 | needNewBase = true; |
118 | return true; |
119 | } |
120 | int sessionId = deltaRow.getSessionId(); |
121 | boolean isThisSession = sessionId == session.getId(); |
122 | boolean isDeleted = deltaRow.isDeleted(); |
123 | if (isThisSession && isDeleted) { |
124 | needNewDelta = true; |
125 | continue; |
126 | } |
127 | if (baseRow == null) { |
128 | if (isDeleted) { |
129 | if (isThisSession) { |
130 | end = true; |
131 | return false; |
132 | } |
133 | // the row was deleted by another session: return it |
134 | onBase = false; |
135 | needNewDelta = true; |
136 | return true; |
137 | } |
138 | DbException.throwInternalError(); |
139 | } |
140 | int compare = index.compareRows(deltaRow, baseRow); |
141 | if (compare == 0) { |
142 | // can't use compareKeys because the |
143 | // version would be compared as well |
144 | long k1 = deltaRow.getKey(); |
145 | long k2 = baseRow.getKey(); |
146 | compare = MathUtils.compareLong(k1, k2); |
147 | } |
148 | if (compare == 0) { |
149 | if (isDeleted) { |
150 | if (isThisSession) { |
151 | DbException.throwInternalError(); |
152 | } |
153 | // another session updated the row |
154 | } else { |
155 | if (isThisSession) { |
156 | onBase = false; |
157 | needNewBase = true; |
158 | needNewDelta = true; |
159 | return true; |
160 | } |
161 | // another session inserted the row: ignore |
162 | needNewBase = true; |
163 | needNewDelta = true; |
164 | continue; |
165 | } |
166 | } |
167 | if (compare > 0) { |
168 | onBase = true; |
169 | needNewBase = true; |
170 | return true; |
171 | } |
172 | onBase = false; |
173 | needNewDelta = true; |
174 | return true; |
175 | } |
176 | } |
177 | } |
178 | |
179 | @Override |
180 | public boolean previous() { |
181 | reverse = true; |
182 | try { |
183 | return next(); |
184 | } finally { |
185 | reverse = false; |
186 | } |
187 | } |
188 | |
189 | } |