001: /*
002: * Copyright 2004-2008 H2 Group. Licensed under the H2 License, Version 1.0
003: * (http://h2database.com/html/license.html).
004: * Initial Developer: H2 Group
005: */
006: package org.h2.result;
007:
008: import java.sql.ResultSet;
009: import java.sql.ResultSetMetaData;
010: import java.sql.SQLException;
011:
012: import org.h2.constant.SysProperties;
013: import org.h2.engine.Database;
014: import org.h2.engine.Session;
015: import org.h2.expression.Expression;
016: import org.h2.expression.ExpressionColumn;
017: import org.h2.message.Message;
018: import org.h2.table.Column;
019: import org.h2.util.ObjectArray;
020: import org.h2.util.ValueHashMap;
021: import org.h2.value.DataType;
022: import org.h2.value.Value;
023: import org.h2.value.ValueArray;
024:
025: /**
026: * A local result set contains all row data of a result set.
027: * This is the object generated by engine,
028: * and it is also used directly by the ResultSet class in the embedded mode.
029: * If the result does not fit in memory, it is written to a temporary file.
030: */
031: public class LocalResult implements ResultInterface {
032:
033: private int maxMemoryRows;
034: private Session session;
035: private int visibleColumnCount;
036: private Expression[] expressions;
037: private int rowId, rowCount;
038: private ObjectArray rows;
039: private SortOrder sort;
040: private ValueHashMap distinctRows;
041: private Value[] currentRow;
042: private int offset, limit;
043: private ResultExternal disk;
044: private int diskOffset;
045: private boolean isUpdateCount;
046: private int updateCount;
047: private boolean distinct;
048:
049: public static LocalResult read(Session session, ResultSet rs,
050: int maxrows) throws SQLException {
051: ObjectArray cols = getExpressionColumns(session, rs);
052: int columnCount = cols.size();
053: LocalResult result = new LocalResult(session, cols, columnCount);
054: for (int i = 0; (maxrows == 0 || i < maxrows) && rs.next(); i++) {
055: Value[] list = new Value[columnCount];
056: for (int j = 0; j < columnCount; j++) {
057: int type = result.getColumnType(j);
058: list[j] = DataType.readValue(session, rs, j + 1, type);
059: }
060: result.addRow(list);
061: }
062: result.done();
063: return result;
064: }
065:
066: private static ObjectArray getExpressionColumns(Session session,
067: ResultSet rs) throws SQLException {
068: ResultSetMetaData meta = rs.getMetaData();
069: int columnCount = meta.getColumnCount();
070: ObjectArray cols = new ObjectArray(columnCount);
071: Database db = session == null ? null : session.getDatabase();
072: for (int i = 0; i < columnCount; i++) {
073: String name = meta.getColumnLabel(i + 1);
074: int type = DataType.convertSQLTypeToValueType(meta
075: .getColumnType(i + 1));
076: int precision = meta.getPrecision(i + 1);
077: int scale = meta.getScale(i + 1);
078: int displaySize = meta.getColumnDisplaySize(i + 1);
079: Column col = new Column(name, type, precision, scale,
080: displaySize);
081: Expression expr = new ExpressionColumn(db, col);
082: cols.add(expr);
083: }
084: return cols;
085: }
086:
087: public LocalResult(int updateCount) {
088: this .isUpdateCount = true;
089: this .updateCount = updateCount;
090: }
091:
092: public LocalResult createShallowCopy(Session session) {
093: if (disk == null && (rows == null || rows.size() < rowCount)) {
094: return null;
095: }
096: LocalResult copy = new LocalResult(0);
097: copy.maxMemoryRows = this .maxMemoryRows;
098: copy.session = session;
099: copy.visibleColumnCount = this .visibleColumnCount;
100: copy.expressions = this .expressions;
101: copy.rowId = -1;
102: copy.rowCount = this .rowCount;
103: copy.rows = this .rows;
104: copy.sort = this .sort;
105: copy.distinctRows = this .distinctRows;
106: copy.distinct = distinct;
107: copy.currentRow = null;
108: copy.offset = 0;
109: copy.limit = 0;
110: copy.disk = this .disk;
111: copy.diskOffset = this .diskOffset;
112: copy.isUpdateCount = this .isUpdateCount;
113: copy.updateCount = this .updateCount;
114: return copy;
115: }
116:
117: public boolean isUpdateCount() {
118: return isUpdateCount;
119: }
120:
121: public int getUpdateCount() {
122: return updateCount;
123: }
124:
125: public LocalResult(Session session, ObjectArray expressionList,
126: int visibleColumnCount) {
127: this (session, getList(expressionList), visibleColumnCount);
128: }
129:
130: private static Expression[] getList(ObjectArray expressionList) {
131: Expression[] expressions = new Expression[expressionList.size()];
132: expressionList.toArray(expressions);
133: return expressions;
134: }
135:
136: public LocalResult(Session session, Expression[] expressions,
137: int visibleColumnCount) {
138: this .session = session;
139: if (session == null) {
140: this .maxMemoryRows = Integer.MAX_VALUE;
141: } else {
142: this .maxMemoryRows = session.getDatabase()
143: .getMaxMemoryRows();
144: }
145: rows = new ObjectArray();
146: this .visibleColumnCount = visibleColumnCount;
147: rowId = -1;
148: this .expressions = expressions;
149: }
150:
151: public void setSortOrder(SortOrder sort) {
152: this .sort = sort;
153: }
154:
155: public void setDistinct() {
156: distinct = true;
157: distinctRows = new ValueHashMap(session.getDatabase());
158: }
159:
160: public void removeDistinct(Value[] values) throws SQLException {
161: if (!distinct) {
162: throw Message.getInternalError();
163: }
164: if (distinctRows != null) {
165: ValueArray array = ValueArray.get(values);
166: distinctRows.remove(array);
167: rowCount = distinctRows.size();
168: } else {
169: rowCount = disk.removeRow(values);
170: }
171: }
172:
173: public boolean containsDistinct(Value[] values) throws SQLException {
174: if (!distinct) {
175: throw Message.getInternalError();
176: }
177: if (distinctRows != null) {
178: ValueArray array = ValueArray.get(values);
179: return distinctRows.get(array) != null;
180: } else {
181: return disk.contains(values);
182: }
183: }
184:
185: public void reset() throws SQLException {
186: rowId = -1;
187: if (disk != null) {
188: disk.reset();
189: if (diskOffset > 0) {
190: for (int i = 0; i < diskOffset; i++) {
191: disk.next();
192: }
193: }
194: }
195: }
196:
197: public Value[] currentRow() {
198: return currentRow;
199: }
200:
201: public boolean next() throws SQLException {
202: if (rowId < rowCount) {
203: rowId++;
204: if (rowId < rowCount) {
205: if (disk != null) {
206: currentRow = disk.next();
207: } else {
208: currentRow = (Value[]) rows.get(rowId);
209: }
210: return true;
211: }
212: currentRow = null;
213: }
214: return false;
215: }
216:
217: public int getRowId() {
218: return rowId;
219: }
220:
221: public void addRow(Value[] values) throws SQLException {
222: if (distinct) {
223: if (distinctRows != null) {
224: ValueArray array = ValueArray.get(values);
225: distinctRows.put(array, values);
226: rowCount = distinctRows.size();
227: if (rowCount > SysProperties.MAX_MEMORY_ROWS_DISTINCT
228: && session.getDatabase().isPersistent()) {
229: disk = new ResultTempTable(session, sort,
230: values.length);
231: disk.addRows(distinctRows.values());
232: distinctRows = null;
233: }
234: } else {
235: rowCount = disk.addRow(values);
236: }
237: return;
238: }
239: rows.add(values);
240: rowCount++;
241: if (rows.size() > maxMemoryRows
242: && session.getDatabase().isPersistent()) {
243: if (disk == null) {
244: disk = new ResultDiskBuffer(session, sort,
245: values.length);
246: }
247: addRowsToDisk();
248: }
249: }
250:
251: private void addRowsToDisk() throws SQLException {
252: disk.addRows(rows);
253: rows.clear();
254: }
255:
256: public int getVisibleColumnCount() {
257: return visibleColumnCount;
258: }
259:
260: public void done() throws SQLException {
261: if (distinct) {
262: if (distinctRows != null) {
263: rows = distinctRows.values();
264: distinctRows = null;
265: } else {
266: if (disk != null && sort != null) {
267: // external sort
268: ResultExternal temp = disk;
269: disk = null;
270: temp.reset();
271: rows = new ObjectArray();
272: // TODO use offset directly if possible
273: while (true) {
274: Value[] list = temp.next();
275: if (list == null) {
276: break;
277: }
278: if (disk == null) {
279: disk = new ResultDiskBuffer(session, sort,
280: list.length);
281: }
282: rows.add(list);
283: if (rows.size() > maxMemoryRows) {
284: disk.addRows(rows);
285: rows.clear();
286: }
287: }
288: temp.close();
289: // the remaining data in rows is written in the following lines
290: }
291: }
292: }
293: if (disk != null) {
294: addRowsToDisk();
295: disk.done();
296: } else {
297: if (sort != null) {
298: sort.sort(rows);
299: }
300: }
301: applyOffset();
302: applyLimit();
303: reset();
304: }
305:
306: public int getRowCount() {
307: return rowCount;
308: }
309:
310: public void setLimit(int limit) {
311: this .limit = limit;
312: }
313:
314: private void applyLimit() {
315: if (limit <= 0) {
316: return;
317: }
318: if (disk == null) {
319: if (rows.size() > limit) {
320: rows.removeRange(limit, rows.size());
321: rowCount = limit;
322: }
323: } else {
324: if (limit < rowCount) {
325: rowCount = limit;
326: }
327: }
328: }
329:
330: public void close() {
331: if (disk != null) {
332: disk.close();
333: disk = null;
334: }
335: }
336:
337: public String getAlias(int i) {
338: return expressions[i].getAlias();
339: }
340:
341: public String getTableName(int i) {
342: return expressions[i].getTableName();
343: }
344:
345: public String getSchemaName(int i) {
346: return expressions[i].getSchemaName();
347: }
348:
349: public int getDisplaySize(int i) {
350: return expressions[i].getDisplaySize();
351: }
352:
353: public String getColumnName(int i) {
354: return expressions[i].getColumnName();
355: }
356:
357: public int getColumnType(int i) {
358: return expressions[i].getType();
359: }
360:
361: public long getColumnPrecision(int i) {
362: return expressions[i].getPrecision();
363: }
364:
365: public int getNullable(int i) {
366: return expressions[i].getNullable();
367: }
368:
369: public boolean isAutoIncrement(int i) {
370: return expressions[i].isAutoIncrement();
371: }
372:
373: public int getColumnScale(int i) {
374: return expressions[i].getScale();
375: }
376:
377: public void setOffset(int offset) {
378: this .offset = offset;
379: }
380:
381: private void applyOffset() {
382: if (offset <= 0) {
383: return;
384: }
385: if (disk == null) {
386: if (offset >= rows.size()) {
387: rows.clear();
388: rowCount = 0;
389: } else {
390: // avoid copying the whole array for each row
391: int remove = Math.min(offset, rows.size());
392: rows.removeRange(0, remove);
393: rowCount -= remove;
394: }
395: } else {
396: if (offset >= rowCount) {
397: rowCount = 0;
398: } else {
399: diskOffset = offset;
400: rowCount -= offset;
401: }
402: }
403: }
404:
405: public String toString() {
406: return "columns: " + visibleColumnCount + " rows: " + rowCount
407: + " pos: " + rowId;
408: }
409:
410: }
|