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.command; |
7 | |
8 | import java.sql.SQLException; |
9 | import java.util.ArrayList; |
10 | |
11 | import org.h2.api.ErrorCode; |
12 | import org.h2.engine.Constants; |
13 | import org.h2.engine.Database; |
14 | import org.h2.engine.Session; |
15 | import org.h2.expression.ParameterInterface; |
16 | import org.h2.message.DbException; |
17 | import org.h2.message.Trace; |
18 | import org.h2.result.ResultInterface; |
19 | import org.h2.util.MathUtils; |
20 | |
21 | /** |
22 | * Represents a SQL statement. This object is only used on the server side. |
23 | */ |
24 | public abstract class Command implements CommandInterface { |
25 | |
26 | /** |
27 | * The session. |
28 | */ |
29 | protected final Session session; |
30 | |
31 | /** |
32 | * The last start time. |
33 | */ |
34 | protected long startTime; |
35 | |
36 | /** |
37 | * The trace module. |
38 | */ |
39 | private final Trace trace; |
40 | |
41 | /** |
42 | * If this query was canceled. |
43 | */ |
44 | private volatile boolean cancel; |
45 | |
46 | private final String sql; |
47 | |
48 | private boolean canReuse; |
49 | |
50 | Command(Parser parser, String sql) { |
51 | this.session = parser.getSession(); |
52 | this.sql = sql; |
53 | trace = session.getDatabase().getTrace(Trace.COMMAND); |
54 | } |
55 | |
56 | /** |
57 | * Check if this command is transactional. |
58 | * If it is not, then it forces the current transaction to commit. |
59 | * |
60 | * @return true if it is |
61 | */ |
62 | public abstract boolean isTransactional(); |
63 | |
64 | /** |
65 | * Check if this command is a query. |
66 | * |
67 | * @return true if it is |
68 | */ |
69 | @Override |
70 | public abstract boolean isQuery(); |
71 | |
72 | /** |
73 | * Get the list of parameters. |
74 | * |
75 | * @return the list of parameters |
76 | */ |
77 | @Override |
78 | public abstract ArrayList<? extends ParameterInterface> getParameters(); |
79 | |
80 | /** |
81 | * Check if this command is read only. |
82 | * |
83 | * @return true if it is |
84 | */ |
85 | public abstract boolean isReadOnly(); |
86 | |
87 | /** |
88 | * Get an empty result set containing the meta data. |
89 | * |
90 | * @return an empty result set |
91 | */ |
92 | public abstract ResultInterface queryMeta(); |
93 | |
94 | /** |
95 | * Execute an updating statement (for example insert, delete, or update), if |
96 | * this is possible. |
97 | * |
98 | * @return the update count |
99 | * @throws DbException if the command is not an updating statement |
100 | */ |
101 | public int update() { |
102 | throw DbException.get(ErrorCode.METHOD_NOT_ALLOWED_FOR_QUERY); |
103 | } |
104 | |
105 | /** |
106 | * Execute a query statement, if this is possible. |
107 | * |
108 | * @param maxrows the maximum number of rows returned |
109 | * @return the local result set |
110 | * @throws DbException if the command is not a query |
111 | */ |
112 | public ResultInterface query(int maxrows) { |
113 | throw DbException.get(ErrorCode.METHOD_ONLY_ALLOWED_FOR_QUERY); |
114 | } |
115 | |
116 | @Override |
117 | public final ResultInterface getMetaData() { |
118 | return queryMeta(); |
119 | } |
120 | |
121 | /** |
122 | * Start the stopwatch. |
123 | */ |
124 | void start() { |
125 | if (trace.isInfoEnabled()) { |
126 | startTime = System.currentTimeMillis(); |
127 | } |
128 | } |
129 | |
130 | void setProgress(int state) { |
131 | session.getDatabase().setProgress(state, sql, 0, 0); |
132 | } |
133 | |
134 | /** |
135 | * Check if this command has been canceled, and throw an exception if yes. |
136 | * |
137 | * @throws DbException if the statement has been canceled |
138 | */ |
139 | protected void checkCanceled() { |
140 | if (cancel) { |
141 | cancel = false; |
142 | throw DbException.get(ErrorCode.STATEMENT_WAS_CANCELED); |
143 | } |
144 | } |
145 | |
146 | private void stop() { |
147 | session.endStatement(); |
148 | session.setCurrentCommand(null); |
149 | if (!isTransactional()) { |
150 | session.commit(true); |
151 | } else if (session.getAutoCommit()) { |
152 | session.commit(false); |
153 | } else if (session.getDatabase().isMultiThreaded()) { |
154 | Database db = session.getDatabase(); |
155 | if (db != null) { |
156 | if (db.getLockMode() == Constants.LOCK_MODE_READ_COMMITTED) { |
157 | session.unlockReadLocks(); |
158 | } |
159 | } |
160 | } |
161 | if (trace.isInfoEnabled() && startTime > 0) { |
162 | long time = System.currentTimeMillis() - startTime; |
163 | if (time > Constants.SLOW_QUERY_LIMIT_MS) { |
164 | trace.info("slow query: {0} ms", time); |
165 | } |
166 | } |
167 | } |
168 | |
169 | /** |
170 | * Execute a query and return the result. |
171 | * This method prepares everything and calls {@link #query(int)} finally. |
172 | * |
173 | * @param maxrows the maximum number of rows to return |
174 | * @param scrollable if the result set must be scrollable (ignored) |
175 | * @return the result set |
176 | */ |
177 | @Override |
178 | public ResultInterface executeQuery(int maxrows, boolean scrollable) { |
179 | startTime = 0; |
180 | long start = 0; |
181 | Database database = session.getDatabase(); |
182 | Object sync = database.isMultiThreaded() ? (Object) session : (Object) database; |
183 | session.waitIfExclusiveModeEnabled(); |
184 | boolean callStop = true; |
185 | boolean writing = !isReadOnly(); |
186 | if (writing) { |
187 | while (!database.beforeWriting()) { |
188 | // wait |
189 | } |
190 | } |
191 | synchronized (sync) { |
192 | session.setCurrentCommand(this); |
193 | try { |
194 | while (true) { |
195 | database.checkPowerOff(); |
196 | try { |
197 | return query(maxrows); |
198 | } catch (DbException e) { |
199 | start = filterConcurrentUpdate(e, start); |
200 | } catch (OutOfMemoryError e) { |
201 | callStop = false; |
202 | // there is a serious problem: |
203 | // the transaction may be applied partially |
204 | // in this case we need to panic: |
205 | // close the database |
206 | database.shutdownImmediately(); |
207 | throw DbException.convert(e); |
208 | } catch (Throwable e) { |
209 | throw DbException.convert(e); |
210 | } |
211 | } |
212 | } catch (DbException e) { |
213 | e = e.addSQL(sql); |
214 | SQLException s = e.getSQLException(); |
215 | database.exceptionThrown(s, sql); |
216 | if (s.getErrorCode() == ErrorCode.OUT_OF_MEMORY) { |
217 | callStop = false; |
218 | database.shutdownImmediately(); |
219 | throw e; |
220 | } |
221 | database.checkPowerOff(); |
222 | throw e; |
223 | } finally { |
224 | if (callStop) { |
225 | stop(); |
226 | } |
227 | if (writing) { |
228 | database.afterWriting(); |
229 | } |
230 | } |
231 | } |
232 | } |
233 | |
234 | @Override |
235 | public int executeUpdate() { |
236 | long start = 0; |
237 | Database database = session.getDatabase(); |
238 | Object sync = database.isMultiThreaded() ? (Object) session : (Object) database; |
239 | session.waitIfExclusiveModeEnabled(); |
240 | boolean callStop = true; |
241 | boolean writing = !isReadOnly(); |
242 | if (writing) { |
243 | while (!database.beforeWriting()) { |
244 | // wait |
245 | } |
246 | } |
247 | synchronized (sync) { |
248 | Session.Savepoint rollback = session.setSavepoint(); |
249 | session.setCurrentCommand(this); |
250 | try { |
251 | while (true) { |
252 | database.checkPowerOff(); |
253 | try { |
254 | return update(); |
255 | } catch (DbException e) { |
256 | start = filterConcurrentUpdate(e, start); |
257 | } catch (OutOfMemoryError e) { |
258 | callStop = false; |
259 | database.shutdownImmediately(); |
260 | throw DbException.convert(e); |
261 | } catch (Throwable e) { |
262 | throw DbException.convert(e); |
263 | } |
264 | } |
265 | } catch (DbException e) { |
266 | e = e.addSQL(sql); |
267 | SQLException s = e.getSQLException(); |
268 | database.exceptionThrown(s, sql); |
269 | if (s.getErrorCode() == ErrorCode.OUT_OF_MEMORY) { |
270 | callStop = false; |
271 | database.shutdownImmediately(); |
272 | throw e; |
273 | } |
274 | database.checkPowerOff(); |
275 | if (s.getErrorCode() == ErrorCode.DEADLOCK_1) { |
276 | session.rollback(); |
277 | } else { |
278 | session.rollbackTo(rollback, false); |
279 | } |
280 | throw e; |
281 | } finally { |
282 | try { |
283 | if (callStop) { |
284 | stop(); |
285 | } |
286 | } finally { |
287 | if (writing) { |
288 | database.afterWriting(); |
289 | } |
290 | } |
291 | } |
292 | } |
293 | } |
294 | |
295 | private long filterConcurrentUpdate(DbException e, long start) { |
296 | if (e.getErrorCode() != ErrorCode.CONCURRENT_UPDATE_1) { |
297 | throw e; |
298 | } |
299 | long now = System.nanoTime() / 1000000; |
300 | if (start != 0 && now - start > session.getLockTimeout()) { |
301 | throw DbException.get(ErrorCode.LOCK_TIMEOUT_1, e.getCause(), ""); |
302 | } |
303 | Database database = session.getDatabase(); |
304 | int sleep = 1 + MathUtils.randomInt(10); |
305 | while (true) { |
306 | try { |
307 | if (database.isMultiThreaded()) { |
308 | Thread.sleep(sleep); |
309 | } else { |
310 | database.wait(sleep); |
311 | } |
312 | } catch (InterruptedException e1) { |
313 | // ignore |
314 | } |
315 | long slept = System.nanoTime() / 1000000 - now; |
316 | if (slept >= sleep) { |
317 | break; |
318 | } |
319 | } |
320 | return start == 0 ? now : start; |
321 | } |
322 | |
323 | @Override |
324 | public void close() { |
325 | canReuse = true; |
326 | } |
327 | |
328 | @Override |
329 | public void cancel() { |
330 | this.cancel = true; |
331 | } |
332 | |
333 | @Override |
334 | public String toString() { |
335 | return sql + Trace.formatParams(getParameters()); |
336 | } |
337 | |
338 | public boolean isCacheable() { |
339 | return false; |
340 | } |
341 | |
342 | /** |
343 | * Whether the command is already closed (in which case it can be re-used). |
344 | * |
345 | * @return true if it can be re-used |
346 | */ |
347 | public boolean canReuse() { |
348 | return canReuse; |
349 | } |
350 | |
351 | /** |
352 | * The command is now re-used, therefore reset the canReuse flag, and the |
353 | * parameter values. |
354 | */ |
355 | public void reuse() { |
356 | canReuse = false; |
357 | ArrayList<? extends ParameterInterface> parameters = getParameters(); |
358 | for (int i = 0, size = parameters.size(); i < size; i++) { |
359 | ParameterInterface param = parameters.get(i); |
360 | param.setValue(null, true); |
361 | } |
362 | } |
363 | |
364 | } |