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.io.IOException; |
9 | import java.util.ArrayList; |
10 | import org.h2.engine.SessionRemote; |
11 | import org.h2.engine.SysProperties; |
12 | import org.h2.message.DbException; |
13 | import org.h2.message.Trace; |
14 | import org.h2.util.New; |
15 | import org.h2.value.Transfer; |
16 | import org.h2.value.Value; |
17 | |
18 | /** |
19 | * The client side part of a result set that is kept on the server. |
20 | * In many cases, the complete data is kept on the client side, |
21 | * but for large results only a subset is in-memory. |
22 | */ |
23 | public class ResultRemote implements ResultInterface { |
24 | |
25 | private int fetchSize; |
26 | private SessionRemote session; |
27 | private Transfer transfer; |
28 | private int id; |
29 | private final ResultColumn[] columns; |
30 | private Value[] currentRow; |
31 | private final int rowCount; |
32 | private int rowId, rowOffset; |
33 | private ArrayList<Value[]> result; |
34 | private final Trace trace; |
35 | |
36 | public ResultRemote(SessionRemote session, Transfer transfer, int id, |
37 | int columnCount, int fetchSize) throws IOException { |
38 | this.session = session; |
39 | trace = session.getTrace(); |
40 | this.transfer = transfer; |
41 | this.id = id; |
42 | this.columns = new ResultColumn[columnCount]; |
43 | rowCount = transfer.readInt(); |
44 | for (int i = 0; i < columnCount; i++) { |
45 | columns[i] = new ResultColumn(transfer); |
46 | } |
47 | rowId = -1; |
48 | result = New.arrayList(); |
49 | this.fetchSize = fetchSize; |
50 | fetchRows(false); |
51 | } |
52 | |
53 | @Override |
54 | public String getAlias(int i) { |
55 | return columns[i].alias; |
56 | } |
57 | |
58 | @Override |
59 | public String getSchemaName(int i) { |
60 | return columns[i].schemaName; |
61 | } |
62 | |
63 | @Override |
64 | public String getTableName(int i) { |
65 | return columns[i].tableName; |
66 | } |
67 | |
68 | @Override |
69 | public String getColumnName(int i) { |
70 | return columns[i].columnName; |
71 | } |
72 | |
73 | @Override |
74 | public int getColumnType(int i) { |
75 | return columns[i].columnType; |
76 | } |
77 | |
78 | @Override |
79 | public long getColumnPrecision(int i) { |
80 | return columns[i].precision; |
81 | } |
82 | |
83 | @Override |
84 | public int getColumnScale(int i) { |
85 | return columns[i].scale; |
86 | } |
87 | |
88 | @Override |
89 | public int getDisplaySize(int i) { |
90 | return columns[i].displaySize; |
91 | } |
92 | |
93 | @Override |
94 | public boolean isAutoIncrement(int i) { |
95 | return columns[i].autoIncrement; |
96 | } |
97 | |
98 | @Override |
99 | public int getNullable(int i) { |
100 | return columns[i].nullable; |
101 | } |
102 | |
103 | @Override |
104 | public void reset() { |
105 | rowId = -1; |
106 | currentRow = null; |
107 | if (session == null) { |
108 | return; |
109 | } |
110 | synchronized (session) { |
111 | session.checkClosed(); |
112 | try { |
113 | session.traceOperation("RESULT_RESET", id); |
114 | transfer.writeInt(SessionRemote.RESULT_RESET).writeInt(id).flush(); |
115 | } catch (IOException e) { |
116 | throw DbException.convertIOException(e, null); |
117 | } |
118 | } |
119 | } |
120 | |
121 | @Override |
122 | public Value[] currentRow() { |
123 | return currentRow; |
124 | } |
125 | |
126 | @Override |
127 | public boolean next() { |
128 | if (rowId < rowCount) { |
129 | rowId++; |
130 | remapIfOld(); |
131 | if (rowId < rowCount) { |
132 | if (rowId - rowOffset >= result.size()) { |
133 | fetchRows(true); |
134 | } |
135 | currentRow = result.get(rowId - rowOffset); |
136 | return true; |
137 | } |
138 | currentRow = null; |
139 | } |
140 | return false; |
141 | } |
142 | |
143 | @Override |
144 | public int getRowId() { |
145 | return rowId; |
146 | } |
147 | |
148 | @Override |
149 | public int getVisibleColumnCount() { |
150 | return columns.length; |
151 | } |
152 | |
153 | @Override |
154 | public int getRowCount() { |
155 | return rowCount; |
156 | } |
157 | |
158 | private void sendClose() { |
159 | if (session == null) { |
160 | return; |
161 | } |
162 | // TODO result sets: no reset possible for larger remote result sets |
163 | try { |
164 | synchronized (session) { |
165 | session.traceOperation("RESULT_CLOSE", id); |
166 | transfer.writeInt(SessionRemote.RESULT_CLOSE).writeInt(id); |
167 | } |
168 | } catch (IOException e) { |
169 | trace.error(e, "close"); |
170 | } finally { |
171 | transfer = null; |
172 | session = null; |
173 | } |
174 | } |
175 | |
176 | @Override |
177 | public void close() { |
178 | result = null; |
179 | sendClose(); |
180 | } |
181 | |
182 | private void remapIfOld() { |
183 | if (session == null) { |
184 | return; |
185 | } |
186 | try { |
187 | if (id <= session.getCurrentId() - SysProperties.SERVER_CACHED_OBJECTS / 2) { |
188 | // object is too old - we need to map it to a new id |
189 | int newId = session.getNextId(); |
190 | session.traceOperation("CHANGE_ID", id); |
191 | transfer.writeInt(SessionRemote.CHANGE_ID).writeInt(id).writeInt(newId); |
192 | id = newId; |
193 | // TODO remote result set: very old result sets may be |
194 | // already removed on the server (theoretically) - how to |
195 | // solve this? |
196 | } |
197 | } catch (IOException e) { |
198 | throw DbException.convertIOException(e, null); |
199 | } |
200 | } |
201 | |
202 | private void fetchRows(boolean sendFetch) { |
203 | synchronized (session) { |
204 | session.checkClosed(); |
205 | try { |
206 | rowOffset += result.size(); |
207 | result.clear(); |
208 | int fetch = Math.min(fetchSize, rowCount - rowOffset); |
209 | if (sendFetch) { |
210 | session.traceOperation("RESULT_FETCH_ROWS", id); |
211 | transfer.writeInt(SessionRemote.RESULT_FETCH_ROWS). |
212 | writeInt(id).writeInt(fetch); |
213 | session.done(transfer); |
214 | } |
215 | for (int r = 0; r < fetch; r++) { |
216 | boolean row = transfer.readBoolean(); |
217 | if (!row) { |
218 | break; |
219 | } |
220 | int len = columns.length; |
221 | Value[] values = new Value[len]; |
222 | for (int i = 0; i < len; i++) { |
223 | Value v = transfer.readValue(); |
224 | values[i] = v; |
225 | } |
226 | result.add(values); |
227 | } |
228 | if (rowOffset + result.size() >= rowCount) { |
229 | sendClose(); |
230 | } |
231 | } catch (IOException e) { |
232 | throw DbException.convertIOException(e, null); |
233 | } |
234 | } |
235 | } |
236 | |
237 | @Override |
238 | public String toString() { |
239 | return "columns: " + columns.length + " rows: " + rowCount + " pos: " + rowId; |
240 | } |
241 | |
242 | @Override |
243 | public int getFetchSize() { |
244 | return fetchSize; |
245 | } |
246 | |
247 | @Override |
248 | public void setFetchSize(int fetchSize) { |
249 | this.fetchSize = fetchSize; |
250 | } |
251 | |
252 | @Override |
253 | public boolean needToClose() { |
254 | return true; |
255 | } |
256 | |
257 | } |