1 | /* |
2 | |
3 | Derby - Class org.apache.derby.impl.sql.GenericStatement |
4 | |
5 | Copyright 1997, 2004 The Apache Software Foundation or its licensors, as applicable. |
6 | |
7 | Licensed under the Apache License, Version 2.0 (the "License"); |
8 | you may not use this file except in compliance with the License. |
9 | You may obtain a copy of the License at |
10 | |
11 | http://www.apache.org/licenses/LICENSE-2.0 |
12 | |
13 | Unless required by applicable law or agreed to in writing, software |
14 | distributed under the License is distributed on an "AS IS" BASIS, |
15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
16 | See the License for the specific language governing permissions and |
17 | limitations under the License. |
18 | |
19 | */ |
20 | |
21 | package org.apache.derby.impl.sql; |
22 | |
23 | import org.apache.derby.iapi.services.stream.HeaderPrintWriter; |
24 | |
25 | import org.apache.derby.iapi.reference.JDBC20Translation; |
26 | import org.apache.derby.iapi.reference.JDBC30Translation; |
27 | import org.apache.derby.iapi.reference.SQLState; |
28 | |
29 | import org.apache.derby.iapi.sql.Activation; |
30 | import org.apache.derby.iapi.types.DataTypeDescriptor; |
31 | import org.apache.derby.iapi.sql.ResultSet; |
32 | import org.apache.derby.iapi.sql.Statement; |
33 | import org.apache.derby.iapi.sql.PreparedStatement; |
34 | import org.apache.derby.iapi.sql.execute.ConstantAction; |
35 | import org.apache.derby.iapi.sql.execute.ExecutionContext; |
36 | import org.apache.derby.iapi.sql.execute.ExecPreparedStatement; |
37 | import org.apache.derby.iapi.sql.ParameterValueSet; |
38 | |
39 | import org.apache.derby.iapi.sql.conn.LanguageConnectionFactory; |
40 | import org.apache.derby.iapi.sql.conn.LanguageConnectionContext; |
41 | import org.apache.derby.iapi.sql.conn.StatementContext; |
42 | |
43 | import org.apache.derby.iapi.sql.depend.Dependent; |
44 | |
45 | import org.apache.derby.iapi.sql.compile.CompilerContext; |
46 | import org.apache.derby.iapi.sql.compile.NodeFactory; |
47 | import org.apache.derby.iapi.sql.compile.Parser; |
48 | |
49 | import org.apache.derby.impl.sql.compile.QueryTreeNode; |
50 | import org.apache.derby.impl.sql.conn.GenericLanguageConnectionContext; |
51 | |
52 | import org.apache.derby.iapi.sql.dictionary.SchemaDescriptor; |
53 | import org.apache.derby.iapi.sql.dictionary.DataDictionaryContext; |
54 | import org.apache.derby.iapi.sql.dictionary.DataDictionary; |
55 | |
56 | import org.apache.derby.iapi.services.compiler.JavaFactory; |
57 | import org.apache.derby.iapi.services.uuid.UUIDFactory; |
58 | import org.apache.derby.iapi.util.ByteArray; |
59 | |
60 | import org.apache.derby.iapi.error.StandardException; |
61 | |
62 | import org.apache.derby.iapi.services.monitor.Monitor; |
63 | |
64 | import org.apache.derby.iapi.services.context.Context; |
65 | import org.apache.derby.iapi.services.context.ContextService; |
66 | import org.apache.derby.iapi.services.context.ContextManager; |
67 | import org.apache.derby.iapi.services.sanity.SanityManager; |
68 | |
69 | import org.apache.derby.iapi.services.loader.GeneratedClass; |
70 | |
71 | import java.sql.Timestamp; |
72 | import java.sql.SQLWarning; |
73 | |
74 | public class GenericStatement |
75 | implements Statement { |
76 | |
77 | // these fields define the identity of the statement |
78 | private final SchemaDescriptor compilationSchema; |
79 | private final String statementText; |
80 | private final boolean isForReadOnly; |
81 | private int prepareIsolationLevel; |
82 | private GenericPreparedStatement preparedStmt; |
83 | |
84 | /** |
85 | * Constructor for a Statement given the text of the statement in a String |
86 | * @param compilationSchema schema |
87 | * @param statementText The text of the statement |
88 | * @param isForReadOnly if the statement is opened with level CONCUR_READ_ONLY |
89 | */ |
90 | |
91 | public GenericStatement(SchemaDescriptor compilationSchema, String statementText, boolean isForReadOnly) |
92 | { |
93 | this.compilationSchema = compilationSchema; |
94 | this.statementText = statementText; |
95 | this.isForReadOnly = isForReadOnly; |
96 | } |
97 | |
98 | /* |
99 | * Statement interface |
100 | */ |
101 | |
102 | |
103 | /* RESOLVE: may need error checking, debugging code here */ |
104 | public PreparedStatement prepare(LanguageConnectionContext lcc) throws StandardException |
105 | { |
106 | /* |
107 | ** Note: don't reset state since this might be |
108 | ** a recompilation of an already prepared statement. |
109 | */ |
110 | return prepMinion(lcc, true, (Object[]) null, (SchemaDescriptor) null, false); |
111 | } |
112 | public PreparedStatement prepare(LanguageConnectionContext lcc, boolean forMetaData) throws StandardException |
113 | { |
114 | /* |
115 | ** Note: don't reset state since this might be |
116 | ** a recompilation of an already prepared statement. |
117 | */ |
118 | return prepMinion(lcc, true, (Object[]) null, (SchemaDescriptor) null, forMetaData); |
119 | } |
120 | |
121 | private PreparedStatement prepMinion(LanguageConnectionContext lcc, boolean cacheMe, Object[] paramDefaults, |
122 | SchemaDescriptor spsSchema, boolean internalSQL) |
123 | throws StandardException |
124 | { |
125 | |
126 | long beginTime = 0; |
127 | long parseTime = 0; |
128 | long bindTime = 0; |
129 | long optimizeTime = 0; |
130 | long generateTime = 0; |
131 | Timestamp beginTimestamp = null; |
132 | Timestamp endTimestamp = null; |
133 | StatementContext statementContext = null; |
134 | |
135 | // verify it isn't already prepared... |
136 | // if it is, and is valid, simply return that tree. |
137 | // if it is invalid, we will recompile now. |
138 | if (preparedStmt != null) { |
139 | if (preparedStmt.upToDate()) |
140 | return preparedStmt; |
141 | } |
142 | |
143 | // Clear the optimizer trace from the last statement |
144 | if (lcc.getOptimizerTrace()) |
145 | lcc.setOptimizerTraceOutput(getSource() + "\n"); |
146 | |
147 | beginTime = getCurrentTimeMillis(lcc); |
148 | /* beginTimestamp only meaningful if beginTime is meaningful. |
149 | * beginTime is meaningful if STATISTICS TIMING is ON. |
150 | */ |
151 | if (beginTime != 0) |
152 | { |
153 | beginTimestamp = new Timestamp(beginTime); |
154 | } |
155 | |
156 | /** set the prepare Isolaton from the LanguageConnectionContext now as |
157 | * we need to consider it in caching decisions |
158 | */ |
159 | prepareIsolationLevel = lcc.getPrepareIsolationLevel(); |
160 | |
161 | /* a note on statement caching: |
162 | * |
163 | * A GenericPreparedStatement (GPS) is only added it to the cache if the |
164 | * parameter cacheMe is set to TRUE when the GPS is created. |
165 | * |
166 | * Earlier only CacheStatement (CS) looked in the statement cache for a |
167 | * prepared statement when prepare was called. Now the functionality |
168 | * of CS has been folded into GenericStatement (GS). So we search the |
169 | * cache for an existing PreparedStatement only when cacheMe is TRUE. |
170 | * i.e if the user calls prepare with cacheMe set to TRUE: |
171 | * then we |
172 | * a) look for the prepared statement in the cache. |
173 | * b) add the prepared statement to the cache. |
174 | * |
175 | * In cases where the statement cache has been disabled (by setting the |
176 | * relevant cloudscape property) then the value of cacheMe is irrelevant. |
177 | */ |
178 | boolean foundInCache = false; |
179 | if (preparedStmt == null) |
180 | { |
181 | if (cacheMe) |
182 | preparedStmt = (GenericPreparedStatement)((GenericLanguageConnectionContext)lcc).lookupStatement(this); |
183 | |
184 | if (preparedStmt == null) |
185 | { |
186 | preparedStmt = new GenericPreparedStatement(this); |
187 | } |
188 | else |
189 | { |
190 | foundInCache = true; |
191 | } |
192 | } |
193 | |
194 | // if anyone else also has this prepared statement, |
195 | // we don't want them trying to compile with it while |
196 | // we are. So, we synchronize on it and re-check |
197 | // its validity first. |
198 | // this is a no-op if and until there is a central |
199 | // cache of prepared statement objects... |
200 | synchronized (preparedStmt) |
201 | { |
202 | |
203 | for (;;) { |
204 | |
205 | if (foundInCache) { |
206 | if (preparedStmt.referencesSessionSchema()) { |
207 | // cannot use this state since it is private to a connection. |
208 | // switch to a new statement. |
209 | foundInCache = false; |
210 | preparedStmt = new GenericPreparedStatement(this); |
211 | break; |
212 | } |
213 | } |
214 | |
215 | // did it get updated while we waited for the lock on it? |
216 | if (preparedStmt.upToDate()) { |
217 | return preparedStmt; |
218 | } |
219 | |
220 | if (!preparedStmt.compilingStatement) { |
221 | break; |
222 | } |
223 | |
224 | try { |
225 | preparedStmt.wait(); |
226 | } catch (InterruptedException ie) { |
227 | throw StandardException.interrupt(ie); |
228 | } |
229 | } |
230 | |
231 | preparedStmt.compilingStatement = true; |
232 | preparedStmt.setActivationClass(null); |
233 | } |
234 | |
235 | try { |
236 | |
237 | HeaderPrintWriter istream = lcc.getLogStatementText() ? Monitor.getStream() : null; |
238 | |
239 | /* |
240 | ** For stored prepared statements, we want all |
241 | ** errors, etc in the context of the underlying |
242 | ** EXECUTE STATEMENT statement, so don't push/pop |
243 | ** another statement context unless we don't have |
244 | ** one. We won't have one if it is an internal |
245 | ** SPS (e.g. jdbcmetadata). |
246 | */ |
247 | if (!preparedStmt.isStorable() || lcc.getStatementDepth() == 0) |
248 | { |
249 | // since this is for compilation only, set atomic |
250 | // param to true and timeout param to 0 |
251 | statementContext = lcc.pushStatementContext(true, isForReadOnly, getSource(), |
252 | null, false, 0L); |
253 | } |
254 | |
255 | |
256 | |
257 | /* |
258 | ** RESOLVE: we may ultimately wish to pass in |
259 | ** whether we are a jdbc metadata query or not to |
260 | ** get the CompilerContext to make the createDependency() |
261 | ** call a noop. |
262 | */ |
263 | CompilerContext cc = lcc.pushCompilerContext(compilationSchema); |
264 | |
265 | if (prepareIsolationLevel != |
266 | ExecutionContext.UNSPECIFIED_ISOLATION_LEVEL) |
267 | { |
268 | cc.setScanIsolationLevel(prepareIsolationLevel); |
269 | } |
270 | |
271 | |
272 | // Look for stored statements that are in a system schema |
273 | // and with a match compilation schema. If so, allow them |
274 | // to compile using internal SQL constructs. |
275 | |
276 | if (internalSQL || |
277 | (spsSchema != null) && (spsSchema.isSystemSchema()) && |
278 | (spsSchema.equals(compilationSchema))) { |
279 | cc.setReliability(CompilerContext.INTERNAL_SQL_LEGAL); |
280 | } |
281 | |
282 | try |
283 | { |
284 | // Statement logging if lcc.getLogStatementText() is true |
285 | if (istream != null) |
286 | { |
287 | String xactId = lcc.getTransactionExecute().getActiveStateTxIdString(); |
288 | istream.printlnWithHeader(LanguageConnectionContext.xidStr + |
289 | xactId + |
290 | "), " + |
291 | LanguageConnectionContext.lccStr + |
292 | lcc.getInstanceNumber() + |
293 | "), " + |
294 | LanguageConnectionContext.dbnameStr + |
295 | lcc.getDbname() + |
296 | "), " + |
297 | LanguageConnectionContext.drdaStr + |
298 | lcc.getDrdaID() + |
299 | "), Begin compiling prepared statement: " + |
300 | getSource() + |
301 | " :End prepared statement"); |
302 | } |
303 | |
304 | Parser p = cc.getParser(); |
305 | |
306 | cc.setCurrentDependent(preparedStmt); |
307 | |
308 | //Only top level statements go through here, nested statement |
309 | //will invoke this method from other places |
310 | QueryTreeNode qt = p.parseStatement(statementText, paramDefaults); |
311 | |
312 | parseTime = getCurrentTimeMillis(lcc); |
313 | |
314 | if (SanityManager.DEBUG) |
315 | { |
316 | if (SanityManager.DEBUG_ON("DumpParseTree")) |
317 | { |
318 | qt.treePrint(); |
319 | } |
320 | |
321 | if (SanityManager.DEBUG_ON("StopAfterParsing")) |
322 | { |
323 | throw StandardException.newException(SQLState.LANG_STOP_AFTER_PARSING); |
324 | } |
325 | } |
326 | |
327 | /* |
328 | ** Tell the data dictionary that we are about to do |
329 | ** a bunch of "get" operations that must be consistent with |
330 | ** each other. |
331 | */ |
332 | |
333 | DataDictionary dataDictionary = lcc.getDataDictionary(); |
334 | |
335 | int ddMode = dataDictionary == null ? 0 : dataDictionary.startReading(lcc); |
336 | |
337 | try |
338 | { |
339 | // start a nested transaction -- all locks acquired by bind |
340 | // and optimize will be released when we end the nested |
341 | // transaction. |
342 | lcc.beginNestedTransaction(true); |
343 | |
344 | qt = qt.bind(); |
345 | bindTime = getCurrentTimeMillis(lcc); |
346 | |
347 | if (SanityManager.DEBUG) |
348 | { |
349 | if (SanityManager.DEBUG_ON("DumpBindTree")) |
350 | { |
351 | qt.treePrint(); |
352 | } |
353 | |
354 | if (SanityManager.DEBUG_ON("StopAfterBinding")) { |
355 | throw StandardException.newException(SQLState.LANG_STOP_AFTER_BINDING); |
356 | } |
357 | } |
358 | |
359 | //Derby424 - In order to avoid caching select statements referencing |
360 | // any SESSION schema objects (including statements referencing views |
361 | // in SESSION schema), we need to do the SESSION schema object check |
362 | // here. |
363 | //a specific eg for statement referencing a view in SESSION schema |
364 | //CREATE TABLE t28A (c28 int) |
365 | //INSERT INTO t28A VALUES (280),(281) |
366 | //CREATE VIEW SESSION.t28v1 as select * from t28A |
367 | //SELECT * from SESSION.t28v1 should show contents of view and we |
368 | // should not cache this statement because a user can later define |
369 | // a global temporary table with the same name as the view name. |
370 | //Following demonstrates that |
371 | //DECLARE GLOBAL TEMPORARY TABLE SESSION.t28v1(c21 int, c22 int) not |
372 | // logged |
373 | //INSERT INTO SESSION.t28v1 VALUES (280,1),(281,2) |
374 | //SELECT * from SESSION.t28v1 should show contents of global temporary |
375 | //table and not the view. Since this select statement was not cached |
376 | // earlier, it will be compiled again and will go to global temporary |
377 | // table to fetch data. This plan will not be cached either because |
378 | // select statement is using SESSION schema object. |
379 | // |
380 | //Following if statement makes sure that if the statement is |
381 | // referencing SESSION schema objects, then we do not want to cache it. |
382 | // We will remove the entry that was made into the cache for |
383 | //this statement at the beginning of the compile phase. |
384 | //The reason we do this check here rather than later in the compile |
385 | // phase is because for a view, later on, we loose the information that |
386 | // it was referencing SESSION schema because the reference |
387 | //view gets replaced with the actual view definition. Right after |
388 | // binding, we still have the information on the view and that is why |
389 | // we do the check here. |
390 | if (preparedStmt.referencesSessionSchema(qt)) { |
391 | if (foundInCache) |
392 | ((GenericLanguageConnectionContext)lcc).removeStatement(this); |
393 | } |
394 | |
395 | qt = qt.optimize(); |
396 | |
397 | optimizeTime = getCurrentTimeMillis(lcc); |
398 | |
399 | // Statement logging if lcc.getLogStatementText() is true |
400 | if (istream != null) |
401 | { |
402 | String xactId = lcc.getTransactionExecute().getActiveStateTxIdString(); |
403 | istream.printlnWithHeader(LanguageConnectionContext.xidStr + |
404 | xactId + |
405 | "), " + |
406 | LanguageConnectionContext.lccStr + |
407 | lcc.getInstanceNumber() + |
408 | "), " + |
409 | LanguageConnectionContext.dbnameStr + |
410 | lcc.getDbname() + |
411 | "), " + |
412 | LanguageConnectionContext.drdaStr + |
413 | lcc.getDrdaID() + |
414 | "), End compiling prepared statement: " + |
415 | getSource() + |
416 | " :End prepared statement"); |
417 | } |
418 | } |
419 | |
420 | catch (StandardException se) |
421 | { |
422 | lcc.commitNestedTransaction(); |
423 | |
424 | // Statement logging if lcc.getLogStatementText() is true |
425 | if (istream != null) |
426 | { |
427 | String xactId = lcc.getTransactionExecute().getActiveStateTxIdString(); |
428 | istream.printlnWithHeader(LanguageConnectionContext.xidStr + |
429 | xactId + |
430 | "), " + |
431 | LanguageConnectionContext.lccStr + |
432 | lcc.getInstanceNumber() + |
433 | "), " + |
434 | LanguageConnectionContext.dbnameStr + |
435 | lcc.getDbname() + |
436 | "), " + |
437 | LanguageConnectionContext.drdaStr + |
438 | lcc.getDrdaID() + |
439 | "), Error compiling prepared statement: " + |
440 | getSource() + |
441 | " :End prepared statement"); |
442 | } |
443 | throw se; |
444 | } |
445 | |
446 | finally |
447 | { |
448 | /* Tell the data dictionary that we are done reading */ |
449 | if (dataDictionary != null) |
450 | dataDictionary.doneReading(ddMode, lcc); |
451 | } |
452 | |
453 | /* we need to move the commit of nested sub-transaction |
454 | * after we mark PS valid, during compilation, we might need |
455 | * to get some lock to synchronize with another thread's DDL |
456 | * execution, in particular, the compilation of insert/update/ |
457 | * delete vs. create index/constraint (see Beetle 3976). We |
458 | * can't release such lock until after we mark the PS valid. |
459 | * Otherwise we would just erase the DDL's invalidation when |
460 | * we mark it valid. |
461 | */ |
462 | try // put in try block, commit sub-transaction if bad |
463 | { |
464 | if (SanityManager.DEBUG) |
465 | { |
466 | if (SanityManager.DEBUG_ON("DumpOptimizedTree")) |
467 | { |
468 | qt.treePrint(); |
469 | } |
470 | |
471 | if (SanityManager.DEBUG_ON("StopAfterOptimizing")) |
472 | { |
473 | throw StandardException.newException(SQLState.LANG_STOP_AFTER_OPTIMIZING); |
474 | } |
475 | } |
476 | |
477 | GeneratedClass ac = qt.generate(preparedStmt.getByteCodeSaver()); |
478 | |
479 | generateTime = getCurrentTimeMillis(lcc); |
480 | /* endTimestamp only meaningful if generateTime is meaningful. |
481 | * generateTime is meaningful if STATISTICS TIMING is ON. |
482 | */ |
483 | if (generateTime != 0) |
484 | { |
485 | endTimestamp = new Timestamp(generateTime); |
486 | } |
487 | |
488 | if (SanityManager.DEBUG) |
489 | { |
490 | if (SanityManager.DEBUG_ON("StopAfterGenerating")) |
491 | { |
492 | throw StandardException.newException(SQLState.LANG_STOP_AFTER_GENERATING); |
493 | } |
494 | } |
495 | |
496 | /* |
497 | copy over the compile-time created objects |
498 | to the prepared statement. This always happens |
499 | at the end of a compile, so there is no need |
500 | to erase the previous entries on a re-compile -- |
501 | this erases as it replaces. Set the activation |
502 | class in case it came from a StorablePreparedStatement |
503 | */ |
504 | preparedStmt.setConstantAction( qt.makeConstantAction() ); |
505 | preparedStmt.setSavedObjects( cc.getSavedObjects() ); |
506 | preparedStmt.setRequiredPermissionsList(cc.getRequiredPermissionsList()); |
507 | preparedStmt.setActivationClass(ac); |
508 | preparedStmt.setNeedsSavepoint(qt.needsSavepoint()); |
509 | preparedStmt.setCursorInfo((CursorInfo)cc.getCursorInfo()); |
510 | preparedStmt.setIsAtomic(qt.isAtomic()); |
511 | preparedStmt.setExecuteStatementNameAndSchema( |
512 | qt.executeStatementName(), |
513 | qt.executeSchemaName() |
514 | ); |
515 | preparedStmt.setSPSName(qt.getSPSName()); |
516 | preparedStmt.completeCompile(qt); |
517 | preparedStmt.setCompileTimeWarnings(cc.getWarnings()); |
518 | } |
519 | catch (StandardException e) // hold it, throw it |
520 | { |
521 | lcc.commitNestedTransaction(); |
522 | throw e; |
523 | } |
524 | |
525 | if (lcc.getRunTimeStatisticsMode()) |
526 | { |
527 | preparedStmt.setCompileTimeMillis( |
528 | parseTime - beginTime, //parse time |
529 | bindTime - parseTime, //bind time |
530 | optimizeTime - bindTime, //optimize time |
531 | generateTime - optimizeTime, //generate time |
532 | getElapsedTimeMillis(beginTime), |
533 | beginTimestamp, |
534 | endTimestamp); |
535 | } |
536 | |
537 | } |
538 | finally // for block introduced by pushCompilerContext() |
539 | { |
540 | lcc.popCompilerContext( cc ); |
541 | } |
542 | } |
543 | catch (StandardException se) |
544 | { |
545 | if (foundInCache) |
546 | ((GenericLanguageConnectionContext)lcc).removeStatement(this); |
547 | throw se; |
548 | } |
549 | finally |
550 | { |
551 | synchronized (preparedStmt) { |
552 | preparedStmt.compilingStatement = false; |
553 | preparedStmt.notifyAll(); |
554 | } |
555 | } |
556 | |
557 | lcc.commitNestedTransaction(); |
558 | |
559 | if (statementContext != null) |
560 | lcc.popStatementContext(statementContext, null); |
561 | |
562 | return preparedStmt; |
563 | } |
564 | |
565 | /** |
566 | * Generates an execution plan given a set of named parameters. |
567 | * Does so for a storable prepared statement. |
568 | * |
569 | * @param paramDefaults Parameter defaults |
570 | * |
571 | * @return A PreparedStatement that allows execution of the execution |
572 | * plan. |
573 | * @exception StandardException Thrown if this is an |
574 | * execution-only version of the module (the prepare() method |
575 | * relies on compilation). |
576 | */ |
577 | public PreparedStatement prepareStorable( |
578 | LanguageConnectionContext lcc, |
579 | PreparedStatement ps, |
580 | Object[] paramDefaults, |
581 | SchemaDescriptor spsSchema, |
582 | boolean internalSQL) |
583 | throws StandardException |
584 | { |
585 | if (ps == null) |
586 | ps = new GenericStorablePreparedStatement(this); |
587 | else |
588 | ((GenericPreparedStatement) ps).statement = this; |
589 | |
590 | this.preparedStmt = (GenericPreparedStatement) ps; |
591 | return prepMinion(lcc, false, paramDefaults, spsSchema, internalSQL); |
592 | } |
593 | |
594 | public String getSource() { |
595 | return statementText; |
596 | } |
597 | |
598 | public String getCompilationSchema() { |
599 | return compilationSchema.getDescriptorName(); |
600 | } |
601 | |
602 | private static long getCurrentTimeMillis(LanguageConnectionContext lcc) |
603 | { |
604 | if (lcc.getStatisticsTiming()) |
605 | { |
606 | return System.currentTimeMillis(); |
607 | } |
608 | else |
609 | { |
610 | return 0; |
611 | } |
612 | } |
613 | |
614 | private static long getElapsedTimeMillis(long beginTime) |
615 | { |
616 | if (beginTime != 0) |
617 | { |
618 | return System.currentTimeMillis() - beginTime; |
619 | } |
620 | else |
621 | { |
622 | return 0; |
623 | } |
624 | } |
625 | |
626 | /* |
627 | ** Identity |
628 | */ |
629 | |
630 | public boolean equals(Object other) { |
631 | |
632 | if (other instanceof GenericStatement) { |
633 | |
634 | GenericStatement os = (GenericStatement) other; |
635 | |
636 | return statementText.equals(os.statementText) && isForReadOnly==os.isForReadOnly |
637 | && compilationSchema.equals(os.compilationSchema) && |
638 | (prepareIsolationLevel == os.prepareIsolationLevel); |
639 | } |
640 | |
641 | return false; |
642 | } |
643 | |
644 | public int hashCode() { |
645 | |
646 | return statementText.hashCode(); |
647 | } |
648 | } |