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.api.ErrorCode; |
9 | import org.h2.engine.Constants; |
10 | import org.h2.engine.Session; |
11 | import org.h2.engine.SysProperties; |
12 | import org.h2.message.DbException; |
13 | import org.h2.store.Data; |
14 | import org.h2.store.Page; |
15 | import org.h2.store.PageStore; |
16 | |
17 | /** |
18 | * Overflow data for a leaf page. Format: |
19 | * <ul> |
20 | * <li>page type: byte (0)</li> |
21 | * <li>checksum: short (1-2)</li> |
22 | * <li>parent page id (0 for root): int (3-6)</li> |
23 | * <li>more data: next overflow page id: int (7-10)</li> |
24 | * <li>last remaining size: short (7-8)</li> |
25 | * <li>data (11-/9-)</li> |
26 | * </ul> |
27 | */ |
28 | public class PageDataOverflow extends Page { |
29 | |
30 | /** |
31 | * The start of the data in the last overflow page. |
32 | */ |
33 | static final int START_LAST = 9; |
34 | |
35 | /** |
36 | * The start of the data in a overflow page that is not the last one. |
37 | */ |
38 | static final int START_MORE = 11; |
39 | |
40 | private static final int START_NEXT_OVERFLOW = 7; |
41 | |
42 | /** |
43 | * The page store. |
44 | */ |
45 | private final PageStore store; |
46 | |
47 | /** |
48 | * The page type. |
49 | */ |
50 | private int type; |
51 | |
52 | /** |
53 | * The parent page (overflow or leaf). |
54 | */ |
55 | private int parentPageId; |
56 | |
57 | /** |
58 | * The next overflow page, or 0. |
59 | */ |
60 | private int nextPage; |
61 | |
62 | private final Data data; |
63 | |
64 | private int start; |
65 | private int size; |
66 | |
67 | /** |
68 | * Create an object from the given data page. |
69 | * |
70 | * @param store the page store |
71 | * @param pageId the page id |
72 | * @param data the data page |
73 | */ |
74 | private PageDataOverflow(PageStore store, int pageId, Data data) { |
75 | this.store = store; |
76 | setPos(pageId); |
77 | this.data = data; |
78 | } |
79 | |
80 | /** |
81 | * Read an overflow page. |
82 | * |
83 | * @param store the page store |
84 | * @param data the data |
85 | * @param pageId the page id |
86 | * @return the page |
87 | */ |
88 | public static Page read(PageStore store, Data data, int pageId) { |
89 | PageDataOverflow p = new PageDataOverflow(store, pageId, data); |
90 | p.read(); |
91 | return p; |
92 | } |
93 | |
94 | /** |
95 | * Create a new overflow page. |
96 | * |
97 | * @param store the page store |
98 | * @param page the page id |
99 | * @param type the page type |
100 | * @param parentPageId the parent page id |
101 | * @param next the next page or 0 |
102 | * @param all the data |
103 | * @param offset the offset within the data |
104 | * @param size the number of bytes |
105 | * @return the page |
106 | */ |
107 | static PageDataOverflow create(PageStore store, int page, |
108 | int type, int parentPageId, int next, |
109 | Data all, int offset, int size) { |
110 | Data data = store.createData(); |
111 | PageDataOverflow p = new PageDataOverflow(store, page, data); |
112 | store.logUndo(p, null); |
113 | data.writeByte((byte) type); |
114 | data.writeShortInt(0); |
115 | data.writeInt(parentPageId); |
116 | if (type == Page.TYPE_DATA_OVERFLOW) { |
117 | data.writeInt(next); |
118 | } else { |
119 | data.writeShortInt(size); |
120 | } |
121 | p.start = data.length(); |
122 | data.write(all.getBytes(), offset, size); |
123 | p.type = type; |
124 | p.parentPageId = parentPageId; |
125 | p.nextPage = next; |
126 | p.size = size; |
127 | return p; |
128 | } |
129 | |
130 | /** |
131 | * Read the page. |
132 | */ |
133 | private void read() { |
134 | data.reset(); |
135 | type = data.readByte(); |
136 | data.readShortInt(); |
137 | parentPageId = data.readInt(); |
138 | if (type == (Page.TYPE_DATA_OVERFLOW | Page.FLAG_LAST)) { |
139 | size = data.readShortInt(); |
140 | nextPage = 0; |
141 | } else if (type == Page.TYPE_DATA_OVERFLOW) { |
142 | nextPage = data.readInt(); |
143 | size = store.getPageSize() - data.length(); |
144 | } else { |
145 | throw DbException.get(ErrorCode.FILE_CORRUPTED_1, "page:" + |
146 | getPos() + " type:" + type); |
147 | } |
148 | start = data.length(); |
149 | } |
150 | |
151 | /** |
152 | * Read the data into a target buffer. |
153 | * |
154 | * @param target the target data page |
155 | * @return the next page, or 0 if no next page |
156 | */ |
157 | int readInto(Data target) { |
158 | target.checkCapacity(size); |
159 | if (type == (Page.TYPE_DATA_OVERFLOW | Page.FLAG_LAST)) { |
160 | target.write(data.getBytes(), START_LAST, size); |
161 | return 0; |
162 | } |
163 | target.write(data.getBytes(), START_MORE, size); |
164 | return nextPage; |
165 | } |
166 | |
167 | int getNextOverflow() { |
168 | return nextPage; |
169 | } |
170 | |
171 | private void writeHead() { |
172 | data.writeByte((byte) type); |
173 | data.writeShortInt(0); |
174 | data.writeInt(parentPageId); |
175 | } |
176 | |
177 | @Override |
178 | public void write() { |
179 | writeData(); |
180 | store.writePage(getPos(), data); |
181 | } |
182 | |
183 | |
184 | private void writeData() { |
185 | data.reset(); |
186 | writeHead(); |
187 | if (type == Page.TYPE_DATA_OVERFLOW) { |
188 | data.writeInt(nextPage); |
189 | } else { |
190 | data.writeShortInt(size); |
191 | } |
192 | } |
193 | |
194 | |
195 | @Override |
196 | public String toString() { |
197 | return "page[" + getPos() + "] data leaf overflow parent:" + |
198 | parentPageId + " next:" + nextPage; |
199 | } |
200 | |
201 | /** |
202 | * Get the estimated memory size. |
203 | * |
204 | * @return number of double words (4 bytes) |
205 | */ |
206 | @Override |
207 | public int getMemory() { |
208 | return (Constants.MEMORY_PAGE_DATA_OVERFLOW + store.getPageSize()) >> 2; |
209 | } |
210 | |
211 | void setParentPageId(int parent) { |
212 | store.logUndo(this, data); |
213 | this.parentPageId = parent; |
214 | } |
215 | |
216 | @Override |
217 | public void moveTo(Session session, int newPos) { |
218 | // load the pages into the cache, to ensure old pages |
219 | // are written |
220 | Page parent = store.getPage(parentPageId); |
221 | if (parent == null) { |
222 | throw DbException.throwInternalError(); |
223 | } |
224 | PageDataOverflow next = null; |
225 | if (nextPage != 0) { |
226 | next = (PageDataOverflow) store.getPage(nextPage); |
227 | } |
228 | store.logUndo(this, data); |
229 | PageDataOverflow p2 = PageDataOverflow.create(store, newPos, type, |
230 | parentPageId, nextPage, data, start, size); |
231 | store.update(p2); |
232 | if (next != null) { |
233 | next.setParentPageId(newPos); |
234 | store.update(next); |
235 | } |
236 | if (parent instanceof PageDataOverflow) { |
237 | PageDataOverflow p1 = (PageDataOverflow) parent; |
238 | p1.setNext(getPos(), newPos); |
239 | } else { |
240 | PageDataLeaf p1 = (PageDataLeaf) parent; |
241 | p1.setOverflow(getPos(), newPos); |
242 | } |
243 | store.update(parent); |
244 | store.free(getPos()); |
245 | } |
246 | |
247 | private void setNext(int old, int nextPage) { |
248 | if (SysProperties.CHECK && old != this.nextPage) { |
249 | DbException.throwInternalError("move " + this + " " + nextPage); |
250 | } |
251 | store.logUndo(this, data); |
252 | this.nextPage = nextPage; |
253 | data.setInt(START_NEXT_OVERFLOW, nextPage); |
254 | } |
255 | |
256 | /** |
257 | * Free this page. |
258 | */ |
259 | void free() { |
260 | store.logUndo(this, data); |
261 | store.free(getPos()); |
262 | } |
263 | |
264 | @Override |
265 | public boolean canRemove() { |
266 | return true; |
267 | } |
268 | |
269 | @Override |
270 | public boolean isStream() { |
271 | return true; |
272 | } |
273 | |
274 | } |