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.sql.ResultSet; |
9 | import java.sql.SQLException; |
10 | import java.util.ArrayList; |
11 | import org.h2.engine.Database; |
12 | import org.h2.engine.Session; |
13 | import org.h2.expression.Expression; |
14 | import org.h2.message.DbException; |
15 | import org.h2.util.New; |
16 | import org.h2.util.ValueHashMap; |
17 | import org.h2.value.DataType; |
18 | import org.h2.value.Value; |
19 | import org.h2.value.ValueArray; |
20 | |
21 | /** |
22 | * A local result set contains all row data of a result set. |
23 | * This is the object generated by engine, |
24 | * and it is also used directly by the ResultSet class in the embedded mode. |
25 | * If the result does not fit in memory, it is written to a temporary file. |
26 | */ |
27 | public class LocalResult implements ResultInterface, ResultTarget { |
28 | |
29 | private int maxMemoryRows; |
30 | private Session session; |
31 | private int visibleColumnCount; |
32 | private Expression[] expressions; |
33 | private int rowId, rowCount; |
34 | private ArrayList<Value[]> rows; |
35 | private SortOrder sort; |
36 | private ValueHashMap<Value[]> distinctRows; |
37 | private Value[] currentRow; |
38 | private int offset; |
39 | private int limit = -1; |
40 | private ResultExternal external; |
41 | private int diskOffset; |
42 | private boolean distinct; |
43 | private boolean randomAccess; |
44 | private boolean closed; |
45 | |
46 | /** |
47 | * Construct a local result object. |
48 | */ |
49 | public LocalResult() { |
50 | // nothing to do |
51 | } |
52 | |
53 | /** |
54 | * Construct a local result object. |
55 | * |
56 | * @param session the session |
57 | * @param expressions the expression array |
58 | * @param visibleColumnCount the number of visible columns |
59 | */ |
60 | public LocalResult(Session session, Expression[] expressions, |
61 | int visibleColumnCount) { |
62 | this.session = session; |
63 | if (session == null) { |
64 | this.maxMemoryRows = Integer.MAX_VALUE; |
65 | } else { |
66 | Database db = session.getDatabase(); |
67 | if (db.isPersistent() && !db.isReadOnly()) { |
68 | this.maxMemoryRows = session.getDatabase().getMaxMemoryRows(); |
69 | } else { |
70 | this.maxMemoryRows = Integer.MAX_VALUE; |
71 | } |
72 | } |
73 | rows = New.arrayList(); |
74 | this.visibleColumnCount = visibleColumnCount; |
75 | rowId = -1; |
76 | this.expressions = expressions; |
77 | } |
78 | |
79 | public void setMaxMemoryRows(int maxValue) { |
80 | this.maxMemoryRows = maxValue; |
81 | } |
82 | |
83 | /** |
84 | * Construct a local result set by reading all data from a regular result |
85 | * set. |
86 | * |
87 | * @param session the session |
88 | * @param rs the result set |
89 | * @param maxrows the maximum number of rows to read (0 for no limit) |
90 | * @return the local result set |
91 | */ |
92 | public static LocalResult read(Session session, ResultSet rs, int maxrows) { |
93 | Expression[] cols = Expression.getExpressionColumns(session, rs); |
94 | int columnCount = cols.length; |
95 | LocalResult result = new LocalResult(session, cols, columnCount); |
96 | try { |
97 | for (int i = 0; (maxrows == 0 || i < maxrows) && rs.next(); i++) { |
98 | Value[] list = new Value[columnCount]; |
99 | for (int j = 0; j < columnCount; j++) { |
100 | int type = result.getColumnType(j); |
101 | list[j] = DataType.readValue(session, rs, j + 1, type); |
102 | } |
103 | result.addRow(list); |
104 | } |
105 | } catch (SQLException e) { |
106 | throw DbException.convert(e); |
107 | } |
108 | result.done(); |
109 | return result; |
110 | } |
111 | |
112 | /** |
113 | * Create a shallow copy of the result set. The data and a temporary table |
114 | * (if there is any) is not copied. |
115 | * |
116 | * @param targetSession the session of the copy |
117 | * @return the copy |
118 | */ |
119 | public LocalResult createShallowCopy(Session targetSession) { |
120 | if (external == null && (rows == null || rows.size() < rowCount)) { |
121 | return null; |
122 | } |
123 | ResultExternal e2 = null; |
124 | if (external != null) { |
125 | e2 = external.createShallowCopy(); |
126 | if (e2 == null) { |
127 | return null; |
128 | } |
129 | } |
130 | LocalResult copy = new LocalResult(); |
131 | copy.maxMemoryRows = this.maxMemoryRows; |
132 | copy.session = targetSession; |
133 | copy.visibleColumnCount = this.visibleColumnCount; |
134 | copy.expressions = this.expressions; |
135 | copy.rowId = -1; |
136 | copy.rowCount = this.rowCount; |
137 | copy.rows = this.rows; |
138 | copy.sort = this.sort; |
139 | copy.distinctRows = this.distinctRows; |
140 | copy.distinct = distinct; |
141 | copy.randomAccess = randomAccess; |
142 | copy.currentRow = null; |
143 | copy.offset = 0; |
144 | copy.limit = -1; |
145 | copy.external = e2; |
146 | copy.diskOffset = this.diskOffset; |
147 | return copy; |
148 | } |
149 | |
150 | /** |
151 | * Set the sort order. |
152 | * |
153 | * @param sort the sort order |
154 | */ |
155 | public void setSortOrder(SortOrder sort) { |
156 | this.sort = sort; |
157 | } |
158 | |
159 | /** |
160 | * Remove duplicate rows. |
161 | */ |
162 | public void setDistinct() { |
163 | distinct = true; |
164 | distinctRows = ValueHashMap.newInstance(); |
165 | } |
166 | |
167 | /** |
168 | * Random access is required (containsDistinct). |
169 | */ |
170 | public void setRandomAccess() { |
171 | this.randomAccess = true; |
172 | } |
173 | |
174 | /** |
175 | * Remove the row from the result set if it exists. |
176 | * |
177 | * @param values the row |
178 | */ |
179 | public void removeDistinct(Value[] values) { |
180 | if (!distinct) { |
181 | DbException.throwInternalError(); |
182 | } |
183 | if (distinctRows != null) { |
184 | ValueArray array = ValueArray.get(values); |
185 | distinctRows.remove(array); |
186 | rowCount = distinctRows.size(); |
187 | } else { |
188 | rowCount = external.removeRow(values); |
189 | } |
190 | } |
191 | |
192 | /** |
193 | * Check if this result set contains the given row. |
194 | * |
195 | * @param values the row |
196 | * @return true if the row exists |
197 | */ |
198 | public boolean containsDistinct(Value[] values) { |
199 | if (external != null) { |
200 | return external.contains(values); |
201 | } |
202 | if (distinctRows == null) { |
203 | distinctRows = ValueHashMap.newInstance(); |
204 | for (Value[] row : rows) { |
205 | if (row.length > visibleColumnCount) { |
206 | Value[] r2 = new Value[visibleColumnCount]; |
207 | System.arraycopy(row, 0, r2, 0, visibleColumnCount); |
208 | row = r2; |
209 | } |
210 | ValueArray array = ValueArray.get(row); |
211 | distinctRows.put(array, row); |
212 | } |
213 | } |
214 | ValueArray array = ValueArray.get(values); |
215 | return distinctRows.get(array) != null; |
216 | } |
217 | |
218 | @Override |
219 | public void reset() { |
220 | rowId = -1; |
221 | if (external != null) { |
222 | external.reset(); |
223 | if (diskOffset > 0) { |
224 | for (int i = 0; i < diskOffset; i++) { |
225 | external.next(); |
226 | } |
227 | } |
228 | } |
229 | } |
230 | |
231 | @Override |
232 | public Value[] currentRow() { |
233 | return currentRow; |
234 | } |
235 | |
236 | @Override |
237 | public boolean next() { |
238 | if (!closed && rowId < rowCount) { |
239 | rowId++; |
240 | if (rowId < rowCount) { |
241 | if (external != null) { |
242 | currentRow = external.next(); |
243 | } else { |
244 | currentRow = rows.get(rowId); |
245 | } |
246 | return true; |
247 | } |
248 | currentRow = null; |
249 | } |
250 | return false; |
251 | } |
252 | |
253 | @Override |
254 | public int getRowId() { |
255 | return rowId; |
256 | } |
257 | |
258 | private void cloneLobs(Value[] values) { |
259 | for (int i = 0; i < values.length; i++) { |
260 | Value v = values[i]; |
261 | Value v2 = v.copyToResult(); |
262 | if (v2 != v) { |
263 | session.addTemporaryLob(v2); |
264 | values[i] = v2; |
265 | } |
266 | } |
267 | } |
268 | |
269 | /** |
270 | * Add a row to this object. |
271 | * |
272 | * @param values the row to add |
273 | */ |
274 | @Override |
275 | public void addRow(Value[] values) { |
276 | cloneLobs(values); |
277 | if (distinct) { |
278 | if (distinctRows != null) { |
279 | ValueArray array = ValueArray.get(values); |
280 | distinctRows.put(array, values); |
281 | rowCount = distinctRows.size(); |
282 | if (rowCount > maxMemoryRows) { |
283 | external = new ResultTempTable(session, expressions, true, sort); |
284 | rowCount = external.addRows(distinctRows.values()); |
285 | distinctRows = null; |
286 | } |
287 | } else { |
288 | rowCount = external.addRow(values); |
289 | } |
290 | return; |
291 | } |
292 | rows.add(values); |
293 | rowCount++; |
294 | if (rows.size() > maxMemoryRows) { |
295 | if (external == null) { |
296 | external = new ResultTempTable(session, expressions, false, sort); |
297 | } |
298 | addRowsToDisk(); |
299 | } |
300 | } |
301 | |
302 | private void addRowsToDisk() { |
303 | rowCount = external.addRows(rows); |
304 | rows.clear(); |
305 | } |
306 | |
307 | @Override |
308 | public int getVisibleColumnCount() { |
309 | return visibleColumnCount; |
310 | } |
311 | |
312 | /** |
313 | * This method is called after all rows have been added. |
314 | */ |
315 | public void done() { |
316 | if (distinct) { |
317 | if (distinctRows != null) { |
318 | rows = distinctRows.values(); |
319 | } else { |
320 | if (external != null && sort != null) { |
321 | // external sort |
322 | ResultExternal temp = external; |
323 | external = null; |
324 | temp.reset(); |
325 | rows = New.arrayList(); |
326 | // TODO use offset directly if possible |
327 | while (true) { |
328 | Value[] list = temp.next(); |
329 | if (list == null) { |
330 | break; |
331 | } |
332 | if (external == null) { |
333 | external = new ResultTempTable(session, expressions, true, sort); |
334 | } |
335 | rows.add(list); |
336 | if (rows.size() > maxMemoryRows) { |
337 | rowCount = external.addRows(rows); |
338 | rows.clear(); |
339 | } |
340 | } |
341 | temp.close(); |
342 | // the remaining data in rows is written in the following |
343 | // lines |
344 | } |
345 | } |
346 | } |
347 | if (external != null) { |
348 | addRowsToDisk(); |
349 | external.done(); |
350 | } else { |
351 | if (sort != null) { |
352 | if (offset > 0 || limit > 0) { |
353 | sort.sort(rows, offset, limit < 0 ? rows.size() : limit); |
354 | } else { |
355 | sort.sort(rows); |
356 | } |
357 | } |
358 | } |
359 | applyOffset(); |
360 | applyLimit(); |
361 | reset(); |
362 | } |
363 | |
364 | @Override |
365 | public int getRowCount() { |
366 | return rowCount; |
367 | } |
368 | |
369 | /** |
370 | * Set the number of rows that this result will return at the maximum. |
371 | * |
372 | * @param limit the limit (-1 means no limit, 0 means no rows) |
373 | */ |
374 | public void setLimit(int limit) { |
375 | this.limit = limit; |
376 | } |
377 | |
378 | private void applyLimit() { |
379 | if (limit < 0) { |
380 | return; |
381 | } |
382 | if (external == null) { |
383 | if (rows.size() > limit) { |
384 | rows = New.arrayList(rows.subList(0, limit)); |
385 | rowCount = limit; |
386 | } |
387 | } else { |
388 | if (limit < rowCount) { |
389 | rowCount = limit; |
390 | } |
391 | } |
392 | } |
393 | |
394 | @Override |
395 | public boolean needToClose() { |
396 | return external != null; |
397 | } |
398 | |
399 | @Override |
400 | public void close() { |
401 | if (external != null) { |
402 | external.close(); |
403 | external = null; |
404 | closed = true; |
405 | } |
406 | } |
407 | |
408 | @Override |
409 | public String getAlias(int i) { |
410 | return expressions[i].getAlias(); |
411 | } |
412 | |
413 | @Override |
414 | public String getTableName(int i) { |
415 | return expressions[i].getTableName(); |
416 | } |
417 | |
418 | @Override |
419 | public String getSchemaName(int i) { |
420 | return expressions[i].getSchemaName(); |
421 | } |
422 | |
423 | @Override |
424 | public int getDisplaySize(int i) { |
425 | return expressions[i].getDisplaySize(); |
426 | } |
427 | |
428 | @Override |
429 | public String getColumnName(int i) { |
430 | return expressions[i].getColumnName(); |
431 | } |
432 | |
433 | @Override |
434 | public int getColumnType(int i) { |
435 | return expressions[i].getType(); |
436 | } |
437 | |
438 | @Override |
439 | public long getColumnPrecision(int i) { |
440 | return expressions[i].getPrecision(); |
441 | } |
442 | |
443 | @Override |
444 | public int getNullable(int i) { |
445 | return expressions[i].getNullable(); |
446 | } |
447 | |
448 | @Override |
449 | public boolean isAutoIncrement(int i) { |
450 | return expressions[i].isAutoIncrement(); |
451 | } |
452 | |
453 | @Override |
454 | public int getColumnScale(int i) { |
455 | return expressions[i].getScale(); |
456 | } |
457 | |
458 | /** |
459 | * Set the offset of the first row to return. |
460 | * |
461 | * @param offset the offset |
462 | */ |
463 | public void setOffset(int offset) { |
464 | this.offset = offset; |
465 | } |
466 | |
467 | private void applyOffset() { |
468 | if (offset <= 0) { |
469 | return; |
470 | } |
471 | if (external == null) { |
472 | if (offset >= rows.size()) { |
473 | rows.clear(); |
474 | rowCount = 0; |
475 | } else { |
476 | // avoid copying the whole array for each row |
477 | int remove = Math.min(offset, rows.size()); |
478 | rows = New.arrayList(rows.subList(remove, rows.size())); |
479 | rowCount -= remove; |
480 | } |
481 | } else { |
482 | if (offset >= rowCount) { |
483 | rowCount = 0; |
484 | } else { |
485 | diskOffset = offset; |
486 | rowCount -= offset; |
487 | } |
488 | } |
489 | } |
490 | |
491 | @Override |
492 | public String toString() { |
493 | return super.toString() + " columns: " + visibleColumnCount + |
494 | " rows: " + rowCount + " pos: " + rowId; |
495 | } |
496 | |
497 | /** |
498 | * Check if this result set is closed. |
499 | * |
500 | * @return true if it is |
501 | */ |
502 | public boolean isClosed() { |
503 | return closed; |
504 | } |
505 | |
506 | @Override |
507 | public int getFetchSize() { |
508 | return 0; |
509 | } |
510 | |
511 | @Override |
512 | public void setFetchSize(int fetchSize) { |
513 | // ignore |
514 | } |
515 | |
516 | } |