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.store; |
7 | |
8 | import org.h2.api.ErrorCode; |
9 | import org.h2.engine.Session; |
10 | import org.h2.message.DbException; |
11 | |
12 | /** |
13 | * A trunk page of a stream. It contains the page numbers of the stream, and the |
14 | * page number of the next trunk. The format is: |
15 | * <ul> |
16 | * <li>page type: byte (0)</li> |
17 | * <li>checksum: short (1-2)</li> |
18 | * <li>previous trunk page, or 0 if none: int (3-6)</li> |
19 | * <li>log key: int (7-10)</li> |
20 | * <li>next trunk page: int (11-14)</li> |
21 | * <li>number of pages: short (15-16)</li> |
22 | * <li>page ids (17-)</li> |
23 | * </ul> |
24 | */ |
25 | public class PageStreamTrunk extends Page { |
26 | |
27 | private static final int DATA_START = 17; |
28 | |
29 | /** |
30 | * The previous stream trunk. |
31 | */ |
32 | int parent; |
33 | |
34 | /** |
35 | * The next stream trunk. |
36 | */ |
37 | int nextTrunk; |
38 | |
39 | private final PageStore store; |
40 | private int logKey; |
41 | private int[] pageIds; |
42 | private int pageCount; |
43 | private Data data; |
44 | |
45 | private PageStreamTrunk(PageStore store, int parent, int pageId, int next, |
46 | int logKey, int[] pageIds) { |
47 | setPos(pageId); |
48 | this.parent = parent; |
49 | this.store = store; |
50 | this.nextTrunk = next; |
51 | this.logKey = logKey; |
52 | this.pageCount = pageIds.length; |
53 | this.pageIds = pageIds; |
54 | } |
55 | |
56 | private PageStreamTrunk(PageStore store, Data data, int pageId) { |
57 | setPos(pageId); |
58 | this.data = data; |
59 | this.store = store; |
60 | } |
61 | |
62 | /** |
63 | * Read a stream trunk page. |
64 | * |
65 | * @param store the page store |
66 | * @param data the data |
67 | * @param pageId the page id |
68 | * @return the page |
69 | */ |
70 | static PageStreamTrunk read(PageStore store, Data data, int pageId) { |
71 | PageStreamTrunk p = new PageStreamTrunk(store, data, pageId); |
72 | p.read(); |
73 | return p; |
74 | } |
75 | |
76 | /** |
77 | * Create a new stream trunk page. |
78 | * |
79 | * @param store the page store |
80 | * @param parent the parent page |
81 | * @param pageId the page id |
82 | * @param next the next trunk page |
83 | * @param logKey the log key |
84 | * @param pageIds the stream data page ids |
85 | * @return the page |
86 | */ |
87 | static PageStreamTrunk create(PageStore store, int parent, int pageId, |
88 | int next, int logKey, int[] pageIds) { |
89 | return new PageStreamTrunk(store, parent, pageId, next, logKey, pageIds); |
90 | } |
91 | |
92 | /** |
93 | * Read the page from the disk. |
94 | */ |
95 | private void read() { |
96 | data.reset(); |
97 | data.readByte(); |
98 | data.readShortInt(); |
99 | parent = data.readInt(); |
100 | logKey = data.readInt(); |
101 | nextTrunk = data.readInt(); |
102 | pageCount = data.readShortInt(); |
103 | pageIds = new int[pageCount]; |
104 | for (int i = 0; i < pageCount; i++) { |
105 | pageIds[i] = data.readInt(); |
106 | } |
107 | } |
108 | |
109 | /** |
110 | * Get the data page id at the given position. |
111 | * |
112 | * @param index the index (0, 1, ...) |
113 | * @return the value, or -1 if the index is too large |
114 | */ |
115 | int getPageData(int index) { |
116 | if (index >= pageIds.length) { |
117 | return -1; |
118 | } |
119 | return pageIds[index]; |
120 | } |
121 | |
122 | @Override |
123 | public void write() { |
124 | data = store.createData(); |
125 | data.writeByte((byte) Page.TYPE_STREAM_TRUNK); |
126 | data.writeShortInt(0); |
127 | data.writeInt(parent); |
128 | data.writeInt(logKey); |
129 | data.writeInt(nextTrunk); |
130 | data.writeShortInt(pageCount); |
131 | for (int i = 0; i < pageCount; i++) { |
132 | data.writeInt(pageIds[i]); |
133 | } |
134 | store.writePage(getPos(), data); |
135 | } |
136 | |
137 | /** |
138 | * Get the number of pages that can be addressed in a stream trunk page. |
139 | * |
140 | * @param pageSize the page size |
141 | * @return the number of pages |
142 | */ |
143 | static int getPagesAddressed(int pageSize) { |
144 | return (pageSize - DATA_START) / 4; |
145 | } |
146 | |
147 | /** |
148 | * Check if the given data page is in this trunk page. |
149 | * |
150 | * @param dataPageId the page id |
151 | * @return true if it is |
152 | */ |
153 | boolean contains(int dataPageId) { |
154 | for (int i = 0; i < pageCount; i++) { |
155 | if (pageIds[i] == dataPageId) { |
156 | return true; |
157 | } |
158 | } |
159 | return false; |
160 | } |
161 | |
162 | /** |
163 | * Free this page and all data pages. Pages after the last used data page |
164 | * (if within this list) are empty and therefore not just freed, but marked |
165 | * as not used. |
166 | * |
167 | * @param lastUsedPage the last used data page |
168 | * @return the number of pages freed |
169 | */ |
170 | int free(int lastUsedPage) { |
171 | store.free(getPos(), false); |
172 | int freed = 1; |
173 | boolean notUsed = false; |
174 | for (int i = 0; i < pageCount; i++) { |
175 | int page = pageIds[i]; |
176 | if (notUsed) { |
177 | store.freeUnused(page); |
178 | } else { |
179 | store.free(page, false); |
180 | } |
181 | freed++; |
182 | if (page == lastUsedPage) { |
183 | notUsed = true; |
184 | } |
185 | } |
186 | return freed; |
187 | } |
188 | |
189 | /** |
190 | * Get the estimated memory size. |
191 | * |
192 | * @return number of double words (4 bytes) |
193 | */ |
194 | @Override |
195 | public int getMemory() { |
196 | return store.getPageSize() >> 2; |
197 | } |
198 | |
199 | @Override |
200 | public void moveTo(Session session, int newPos) { |
201 | // not required |
202 | } |
203 | |
204 | int getLogKey() { |
205 | return logKey; |
206 | } |
207 | |
208 | public int getNextTrunk() { |
209 | return nextTrunk; |
210 | } |
211 | |
212 | /** |
213 | * An iterator over page stream trunk pages. |
214 | */ |
215 | static class Iterator { |
216 | |
217 | private final PageStore store; |
218 | private int first; |
219 | private int next; |
220 | private int previous; |
221 | private boolean canDelete; |
222 | private int current; |
223 | |
224 | Iterator(PageStore store, int first) { |
225 | this.store = store; |
226 | this.next = first; |
227 | } |
228 | |
229 | int getCurrentPageId() { |
230 | return current; |
231 | } |
232 | |
233 | /** |
234 | * Get the next trunk page or null if no next trunk page. |
235 | * |
236 | * @return the next trunk page or null |
237 | */ |
238 | PageStreamTrunk next() { |
239 | canDelete = false; |
240 | if (first == 0) { |
241 | first = next; |
242 | } else if (first == next) { |
243 | return null; |
244 | } |
245 | if (next == 0 || next >= store.getPageCount()) { |
246 | return null; |
247 | } |
248 | Page p; |
249 | current = next; |
250 | try { |
251 | p = store.getPage(next); |
252 | } catch (DbException e) { |
253 | if (e.getErrorCode() == ErrorCode.FILE_CORRUPTED_1) { |
254 | // wrong checksum means end of stream |
255 | return null; |
256 | } |
257 | throw e; |
258 | } |
259 | if (p == null || p instanceof PageStreamTrunk || |
260 | p instanceof PageStreamData) { |
261 | canDelete = true; |
262 | } |
263 | if (!(p instanceof PageStreamTrunk)) { |
264 | return null; |
265 | } |
266 | PageStreamTrunk t = (PageStreamTrunk) p; |
267 | if (previous > 0 && t.parent != previous) { |
268 | return null; |
269 | } |
270 | previous = next; |
271 | next = t.nextTrunk; |
272 | return t; |
273 | } |
274 | |
275 | /** |
276 | * Check if the current page can be deleted. It can if it's empty, a |
277 | * stream trunk, or a stream data page. |
278 | * |
279 | * @return true if it can be deleted |
280 | */ |
281 | boolean canDelete() { |
282 | return canDelete; |
283 | } |
284 | |
285 | } |
286 | |
287 | @Override |
288 | public boolean canRemove() { |
289 | return true; |
290 | } |
291 | |
292 | @Override |
293 | public String toString() { |
294 | return "page[" + getPos() + "] stream trunk key:" + logKey + |
295 | " next:" + nextTrunk; |
296 | } |
297 | |
298 | @Override |
299 | public boolean canMove() { |
300 | return false; |
301 | } |
302 | |
303 | } |