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.result; |
7 | |
8 | import java.util.ArrayList; |
9 | import org.h2.engine.Constants; |
10 | import org.h2.engine.Database; |
11 | import org.h2.engine.Session; |
12 | import org.h2.store.Data; |
13 | import org.h2.store.FileStore; |
14 | import org.h2.util.New; |
15 | import org.h2.value.Value; |
16 | |
17 | /** |
18 | * A list of rows. If the list grows too large, it is buffered to disk |
19 | * automatically. |
20 | */ |
21 | public class RowList { |
22 | |
23 | private final Session session; |
24 | private final ArrayList<Row> list = New.arrayList(); |
25 | private int size; |
26 | private int index, listIndex; |
27 | private FileStore file; |
28 | private Data rowBuff; |
29 | private ArrayList<Value> lobs; |
30 | private final int maxMemory; |
31 | private int memory; |
32 | private boolean written; |
33 | private boolean readUncached; |
34 | |
35 | /** |
36 | * Construct a new row list for this session. |
37 | * |
38 | * @param session the session |
39 | */ |
40 | public RowList(Session session) { |
41 | this.session = session; |
42 | if (session.getDatabase().isPersistent()) { |
43 | maxMemory = session.getDatabase().getMaxOperationMemory(); |
44 | } else { |
45 | maxMemory = 0; |
46 | } |
47 | } |
48 | |
49 | private void writeRow(Data buff, Row r) { |
50 | buff.checkCapacity(1 + Data.LENGTH_INT * 8); |
51 | buff.writeByte((byte) 1); |
52 | buff.writeInt(r.getMemory()); |
53 | int columnCount = r.getColumnCount(); |
54 | buff.writeInt(columnCount); |
55 | buff.writeLong(r.getKey()); |
56 | buff.writeInt(r.getVersion()); |
57 | buff.writeInt(r.isDeleted() ? 1 : 0); |
58 | buff.writeInt(r.getSessionId()); |
59 | for (int i = 0; i < columnCount; i++) { |
60 | Value v = r.getValue(i); |
61 | buff.checkCapacity(1); |
62 | if (v == null) { |
63 | buff.writeByte((byte) 0); |
64 | } else { |
65 | buff.writeByte((byte) 1); |
66 | if (v.getType() == Value.CLOB || v.getType() == Value.BLOB) { |
67 | // need to keep a reference to temporary lobs, |
68 | // otherwise the temp file is deleted |
69 | if (v.getSmall() == null && v.getTableId() == 0) { |
70 | if (lobs == null) { |
71 | lobs = New.arrayList(); |
72 | } |
73 | // need to create a copy, otherwise, |
74 | // if stored multiple times, it may be renamed |
75 | // and then not found |
76 | v = v.copyToTemp(); |
77 | lobs.add(v); |
78 | } |
79 | } |
80 | buff.checkCapacity(buff.getValueLen(v)); |
81 | buff.writeValue(v); |
82 | } |
83 | } |
84 | } |
85 | |
86 | private void writeAllRows() { |
87 | if (file == null) { |
88 | Database db = session.getDatabase(); |
89 | String fileName = db.createTempFile(); |
90 | file = db.openFile(fileName, "rw", false); |
91 | file.setCheckedWriting(false); |
92 | file.seek(FileStore.HEADER_LENGTH); |
93 | rowBuff = Data.create(db, Constants.DEFAULT_PAGE_SIZE); |
94 | file.seek(FileStore.HEADER_LENGTH); |
95 | } |
96 | Data buff = rowBuff; |
97 | initBuffer(buff); |
98 | for (int i = 0, size = list.size(); i < size; i++) { |
99 | if (i > 0 && buff.length() > Constants.IO_BUFFER_SIZE) { |
100 | flushBuffer(buff); |
101 | initBuffer(buff); |
102 | } |
103 | Row r = list.get(i); |
104 | writeRow(buff, r); |
105 | } |
106 | flushBuffer(buff); |
107 | file.autoDelete(); |
108 | list.clear(); |
109 | memory = 0; |
110 | } |
111 | |
112 | private static void initBuffer(Data buff) { |
113 | buff.reset(); |
114 | buff.writeInt(0); |
115 | } |
116 | |
117 | private void flushBuffer(Data buff) { |
118 | buff.checkCapacity(1); |
119 | buff.writeByte((byte) 0); |
120 | buff.fillAligned(); |
121 | buff.setInt(0, buff.length() / Constants.FILE_BLOCK_SIZE); |
122 | file.write(buff.getBytes(), 0, buff.length()); |
123 | } |
124 | |
125 | /** |
126 | * Add a row to the list. |
127 | * |
128 | * @param r the row to add |
129 | */ |
130 | public void add(Row r) { |
131 | list.add(r); |
132 | memory += r.getMemory() + Constants.MEMORY_POINTER; |
133 | if (maxMemory > 0 && memory > maxMemory) { |
134 | writeAllRows(); |
135 | } |
136 | size++; |
137 | } |
138 | |
139 | /** |
140 | * Remove all rows from the list. |
141 | */ |
142 | public void reset() { |
143 | index = 0; |
144 | if (file != null) { |
145 | listIndex = 0; |
146 | if (!written) { |
147 | writeAllRows(); |
148 | written = true; |
149 | } |
150 | list.clear(); |
151 | file.seek(FileStore.HEADER_LENGTH); |
152 | } |
153 | } |
154 | |
155 | /** |
156 | * Check if there are more rows in this list. |
157 | * |
158 | * @return true it there are more rows |
159 | */ |
160 | public boolean hasNext() { |
161 | return index < size; |
162 | } |
163 | |
164 | private Row readRow(Data buff) { |
165 | if (buff.readByte() == 0) { |
166 | return null; |
167 | } |
168 | int mem = buff.readInt(); |
169 | int columnCount = buff.readInt(); |
170 | long key = buff.readLong(); |
171 | int version = buff.readInt(); |
172 | if (readUncached) { |
173 | key = 0; |
174 | } |
175 | boolean deleted = buff.readInt() == 1; |
176 | int sessionId = buff.readInt(); |
177 | Value[] values = new Value[columnCount]; |
178 | for (int i = 0; i < columnCount; i++) { |
179 | Value v; |
180 | if (buff.readByte() == 0) { |
181 | v = null; |
182 | } else { |
183 | v = buff.readValue(); |
184 | if (v.isLinked()) { |
185 | // the table id is 0 if it was linked when writing |
186 | // a temporary entry |
187 | if (v.getTableId() == 0) { |
188 | session.unlinkAtCommit(v); |
189 | } |
190 | } |
191 | } |
192 | values[i] = v; |
193 | } |
194 | Row row = new Row(values, mem); |
195 | row.setKey(key); |
196 | row.setVersion(version); |
197 | row.setDeleted(deleted); |
198 | row.setSessionId(sessionId); |
199 | return row; |
200 | } |
201 | |
202 | /** |
203 | * Get the next row from the list. |
204 | * |
205 | * @return the next row |
206 | */ |
207 | public Row next() { |
208 | Row r; |
209 | if (file == null) { |
210 | r = list.get(index++); |
211 | } else { |
212 | if (listIndex >= list.size()) { |
213 | list.clear(); |
214 | listIndex = 0; |
215 | Data buff = rowBuff; |
216 | buff.reset(); |
217 | int min = Constants.FILE_BLOCK_SIZE; |
218 | file.readFully(buff.getBytes(), 0, min); |
219 | int len = buff.readInt() * Constants.FILE_BLOCK_SIZE; |
220 | buff.checkCapacity(len); |
221 | if (len - min > 0) { |
222 | file.readFully(buff.getBytes(), min, len - min); |
223 | } |
224 | while (true) { |
225 | r = readRow(buff); |
226 | if (r == null) { |
227 | break; |
228 | } |
229 | list.add(r); |
230 | } |
231 | } |
232 | index++; |
233 | r = list.get(listIndex++); |
234 | } |
235 | return r; |
236 | } |
237 | |
238 | /** |
239 | * Get the number of rows in this list. |
240 | * |
241 | * @return the number of rows |
242 | */ |
243 | public int size() { |
244 | return size; |
245 | } |
246 | |
247 | /** |
248 | * Do not use the cache. |
249 | */ |
250 | public void invalidateCache() { |
251 | readUncached = true; |
252 | } |
253 | |
254 | /** |
255 | * Close the result list and delete the temporary file. |
256 | */ |
257 | public void close() { |
258 | if (file != null) { |
259 | file.autoDelete(); |
260 | file.closeAndDeleteSilently(); |
261 | file = null; |
262 | rowBuff = null; |
263 | } |
264 | } |
265 | |
266 | } |