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.util.ArrayList; |
9 | import org.h2.api.DatabaseEventListener; |
10 | import org.h2.api.ErrorCode; |
11 | import org.h2.engine.Database; |
12 | import org.h2.engine.Session; |
13 | import org.h2.expression.Expression; |
14 | import org.h2.expression.Parameter; |
15 | import org.h2.message.DbException; |
16 | import org.h2.message.Trace; |
17 | import org.h2.result.ResultInterface; |
18 | import org.h2.util.StatementBuilder; |
19 | import org.h2.value.Value; |
20 | |
21 | /** |
22 | * A prepared statement. |
23 | */ |
24 | public abstract class Prepared { |
25 | |
26 | /** |
27 | * The session. |
28 | */ |
29 | protected Session session; |
30 | |
31 | /** |
32 | * The SQL string. |
33 | */ |
34 | protected String sqlStatement; |
35 | |
36 | /** |
37 | * Whether to create a new object (for indexes). |
38 | */ |
39 | protected boolean create = true; |
40 | |
41 | /** |
42 | * The list of parameters. |
43 | */ |
44 | protected ArrayList<Parameter> parameters; |
45 | |
46 | /** |
47 | * If the query should be prepared before each execution. This is set for |
48 | * queries with LIKE ?, because the query plan depends on the parameter |
49 | * value. |
50 | */ |
51 | protected boolean prepareAlways; |
52 | |
53 | private long modificationMetaId; |
54 | private Command command; |
55 | private int objectId; |
56 | private int currentRowNumber; |
57 | private int rowScanCount; |
58 | |
59 | /** |
60 | * Create a new object. |
61 | * |
62 | * @param session the session |
63 | */ |
64 | public Prepared(Session session) { |
65 | this.session = session; |
66 | modificationMetaId = session.getDatabase().getModificationMetaId(); |
67 | } |
68 | |
69 | /** |
70 | * Check if this command is transactional. |
71 | * If it is not, then it forces the current transaction to commit. |
72 | * |
73 | * @return true if it is |
74 | */ |
75 | public abstract boolean isTransactional(); |
76 | |
77 | /** |
78 | * Get an empty result set containing the meta data. |
79 | * |
80 | * @return the result set |
81 | */ |
82 | public abstract ResultInterface queryMeta(); |
83 | |
84 | |
85 | /** |
86 | * Get the command type as defined in CommandInterface |
87 | * |
88 | * @return the statement type |
89 | */ |
90 | public abstract int getType(); |
91 | |
92 | /** |
93 | * Check if this command is read only. |
94 | * |
95 | * @return true if it is |
96 | */ |
97 | public boolean isReadOnly() { |
98 | return false; |
99 | } |
100 | |
101 | /** |
102 | * Check if the statement needs to be re-compiled. |
103 | * |
104 | * @return true if it must |
105 | */ |
106 | public boolean needRecompile() { |
107 | Database db = session.getDatabase(); |
108 | if (db == null) { |
109 | throw DbException.get(ErrorCode.CONNECTION_BROKEN_1, "database closed"); |
110 | } |
111 | // parser: currently, compiling every create/drop/... twice |
112 | // because needRecompile return true even for the first execution |
113 | return prepareAlways || |
114 | modificationMetaId < db.getModificationMetaId() || |
115 | db.getSettings().recompileAlways; |
116 | } |
117 | |
118 | /** |
119 | * Get the meta data modification id of the database when this statement was |
120 | * compiled. |
121 | * |
122 | * @return the meta data modification id |
123 | */ |
124 | long getModificationMetaId() { |
125 | return modificationMetaId; |
126 | } |
127 | |
128 | /** |
129 | * Set the meta data modification id of this statement. |
130 | * |
131 | * @param id the new id |
132 | */ |
133 | void setModificationMetaId(long id) { |
134 | this.modificationMetaId = id; |
135 | } |
136 | |
137 | /** |
138 | * Set the parameter list of this statement. |
139 | * |
140 | * @param parameters the parameter list |
141 | */ |
142 | public void setParameterList(ArrayList<Parameter> parameters) { |
143 | this.parameters = parameters; |
144 | } |
145 | |
146 | /** |
147 | * Get the parameter list. |
148 | * |
149 | * @return the parameter list |
150 | */ |
151 | public ArrayList<Parameter> getParameters() { |
152 | return parameters; |
153 | } |
154 | |
155 | /** |
156 | * Check if all parameters have been set. |
157 | * |
158 | * @throws DbException if any parameter has not been set |
159 | */ |
160 | protected void checkParameters() { |
161 | if (parameters != null) { |
162 | for (int i = 0, size = parameters.size(); i < size; i++) { |
163 | Parameter param = parameters.get(i); |
164 | param.checkSet(); |
165 | } |
166 | } |
167 | } |
168 | |
169 | /** |
170 | * Set the command. |
171 | * |
172 | * @param command the new command |
173 | */ |
174 | public void setCommand(Command command) { |
175 | this.command = command; |
176 | } |
177 | |
178 | /** |
179 | * Check if this object is a query. |
180 | * |
181 | * @return true if it is |
182 | */ |
183 | public boolean isQuery() { |
184 | return false; |
185 | } |
186 | |
187 | /** |
188 | * Prepare this statement. |
189 | */ |
190 | public void prepare() { |
191 | // nothing to do |
192 | } |
193 | |
194 | /** |
195 | * Execute the statement. |
196 | * |
197 | * @return the update count |
198 | * @throws DbException if it is a query |
199 | */ |
200 | public int update() { |
201 | throw DbException.get(ErrorCode.METHOD_NOT_ALLOWED_FOR_QUERY); |
202 | } |
203 | |
204 | /** |
205 | * Execute the query. |
206 | * |
207 | * @param maxrows the maximum number of rows to return |
208 | * @return the result set |
209 | * @throws DbException if it is not a query |
210 | */ |
211 | public ResultInterface query(int maxrows) { |
212 | throw DbException.get(ErrorCode.METHOD_ONLY_ALLOWED_FOR_QUERY); |
213 | } |
214 | |
215 | /** |
216 | * Set the SQL statement. |
217 | * |
218 | * @param sql the SQL statement |
219 | */ |
220 | public void setSQL(String sql) { |
221 | this.sqlStatement = sql; |
222 | } |
223 | |
224 | /** |
225 | * Get the SQL statement. |
226 | * |
227 | * @return the SQL statement |
228 | */ |
229 | public String getSQL() { |
230 | return sqlStatement; |
231 | } |
232 | |
233 | /** |
234 | * Get the object id to use for the database object that is created in this |
235 | * statement. This id is only set when the object is persistent. |
236 | * If not set, this method returns 0. |
237 | * |
238 | * @return the object id or 0 if not set |
239 | */ |
240 | protected int getCurrentObjectId() { |
241 | return objectId; |
242 | } |
243 | |
244 | /** |
245 | * Get the current object id, or get a new id from the database. The object |
246 | * id is used when creating new database object (CREATE statement). |
247 | * |
248 | * @return the object id |
249 | */ |
250 | protected int getObjectId() { |
251 | int id = objectId; |
252 | if (id == 0) { |
253 | id = session.getDatabase().allocateObjectId(); |
254 | } else { |
255 | objectId = 0; |
256 | } |
257 | return id; |
258 | } |
259 | |
260 | /** |
261 | * Get the SQL statement with the execution plan. |
262 | * |
263 | * @return the execution plan |
264 | */ |
265 | public String getPlanSQL() { |
266 | return null; |
267 | } |
268 | |
269 | /** |
270 | * Check if this statement was canceled. |
271 | * |
272 | * @throws DbException if it was canceled |
273 | */ |
274 | public void checkCanceled() { |
275 | session.checkCanceled(); |
276 | Command c = command != null ? command : session.getCurrentCommand(); |
277 | if (c != null) { |
278 | c.checkCanceled(); |
279 | } |
280 | } |
281 | |
282 | /** |
283 | * Set the object id for this statement. |
284 | * |
285 | * @param i the object id |
286 | */ |
287 | public void setObjectId(int i) { |
288 | this.objectId = i; |
289 | this.create = false; |
290 | } |
291 | |
292 | /** |
293 | * Set the session for this statement. |
294 | * |
295 | * @param currentSession the new session |
296 | */ |
297 | public void setSession(Session currentSession) { |
298 | this.session = currentSession; |
299 | } |
300 | |
301 | /** |
302 | * Print information about the statement executed if info trace level is |
303 | * enabled. |
304 | * |
305 | * @param startTime when the statement was started |
306 | * @param rowCount the query or update row count |
307 | */ |
308 | void trace(long startTime, int rowCount) { |
309 | if (session.getTrace().isInfoEnabled() && startTime > 0) { |
310 | long deltaTime = System.currentTimeMillis() - startTime; |
311 | String params = Trace.formatParams(parameters); |
312 | session.getTrace().infoSQL(sqlStatement, params, rowCount, deltaTime); |
313 | } |
314 | if (session.getDatabase().getQueryStatistics()) { |
315 | long deltaTime = System.currentTimeMillis() - startTime; |
316 | session.getDatabase().getQueryStatisticsData(). |
317 | update(toString(), deltaTime, rowCount); |
318 | } |
319 | } |
320 | |
321 | /** |
322 | * Set the prepare always flag. |
323 | * If set, the statement is re-compiled whenever it is executed. |
324 | * |
325 | * @param prepareAlways the new value |
326 | */ |
327 | public void setPrepareAlways(boolean prepareAlways) { |
328 | this.prepareAlways = prepareAlways; |
329 | } |
330 | |
331 | /** |
332 | * Set the current row number. |
333 | * |
334 | * @param rowNumber the row number |
335 | */ |
336 | protected void setCurrentRowNumber(int rowNumber) { |
337 | if ((++rowScanCount & 127) == 0) { |
338 | checkCanceled(); |
339 | } |
340 | this.currentRowNumber = rowNumber; |
341 | setProgress(); |
342 | } |
343 | |
344 | /** |
345 | * Get the current row number. |
346 | * |
347 | * @return the row number |
348 | */ |
349 | public int getCurrentRowNumber() { |
350 | return currentRowNumber; |
351 | } |
352 | |
353 | /** |
354 | * Notifies query progress via the DatabaseEventListener |
355 | */ |
356 | private void setProgress() { |
357 | if ((currentRowNumber & 127) == 0) { |
358 | session.getDatabase().setProgress( |
359 | DatabaseEventListener.STATE_STATEMENT_PROGRESS, |
360 | sqlStatement, currentRowNumber, 0); |
361 | } |
362 | } |
363 | |
364 | /** |
365 | * Convert the statement to a String. |
366 | * |
367 | * @return the SQL statement |
368 | */ |
369 | @Override |
370 | public String toString() { |
371 | return sqlStatement; |
372 | } |
373 | |
374 | /** |
375 | * Get the SQL snippet of the value list. |
376 | * |
377 | * @param values the value list |
378 | * @return the SQL snippet |
379 | */ |
380 | protected static String getSQL(Value[] values) { |
381 | StatementBuilder buff = new StatementBuilder(); |
382 | for (Value v : values) { |
383 | buff.appendExceptFirst(", "); |
384 | if (v != null) { |
385 | buff.append(v.getSQL()); |
386 | } |
387 | } |
388 | return buff.toString(); |
389 | } |
390 | |
391 | /** |
392 | * Get the SQL snippet of the expression list. |
393 | * |
394 | * @param list the expression list |
395 | * @return the SQL snippet |
396 | */ |
397 | protected static String getSQL(Expression[] list) { |
398 | StatementBuilder buff = new StatementBuilder(); |
399 | for (Expression e : list) { |
400 | buff.appendExceptFirst(", "); |
401 | if (e != null) { |
402 | buff.append(e.getSQL()); |
403 | } |
404 | } |
405 | return buff.toString(); |
406 | } |
407 | |
408 | /** |
409 | * Set the SQL statement of the exception to the given row. |
410 | * |
411 | * @param e the exception |
412 | * @param rowId the row number |
413 | * @param values the values of the row |
414 | * @return the exception |
415 | */ |
416 | protected DbException setRow(DbException e, int rowId, String values) { |
417 | StringBuilder buff = new StringBuilder(); |
418 | if (sqlStatement != null) { |
419 | buff.append(sqlStatement); |
420 | } |
421 | buff.append(" -- "); |
422 | if (rowId > 0) { |
423 | buff.append("row #").append(rowId + 1).append(' '); |
424 | } |
425 | buff.append('(').append(values).append(')'); |
426 | return e.addSQL(buff.toString()); |
427 | } |
428 | |
429 | public boolean isCacheable() { |
430 | return false; |
431 | } |
432 | |
433 | } |