001: /* Copyright (c) 2001-2005, The HSQL Development Group
002: * All rights reserved.
003: *
004: * Redistribution and use in source and binary forms, with or without
005: * modification, are permitted provided that the following conditions are met:
006: *
007: * Redistributions of source code must retain the above copyright notice, this
008: * list of conditions and the following disclaimer.
009: *
010: * Redistributions in binary form must reproduce the above copyright notice,
011: * this list of conditions and the following disclaimer in the documentation
012: * and/or other materials provided with the distribution.
013: *
014: * Neither the name of the HSQL Development Group nor the names of its
015: * contributors may be used to endorse or promote products derived from this
016: * software without specific prior written permission.
017: *
018: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
019: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
020: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
021: * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
022: * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
023: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
024: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
025: * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
026: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
027: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
028: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029: */
030:
031: package org.hsqldb;
032:
033: import org.hsqldb.HsqlNameManager.HsqlName;
034: import org.hsqldb.lib.IntKeyHashMap;
035: import org.hsqldb.lib.IntKeyIntValueHashMap;
036: import org.hsqldb.lib.IntValueHashMap;
037: import org.hsqldb.lib.Iterator;
038:
039: /**
040: * This class manages the reuse of CompiledStatement objects for prepared
041: * statements for a Database instance.<p>
042: *
043: * A compiled statement is registered by a session to be managed. Once
044: * registered, it is linked with one or more sessions.<p>
045: *
046: * The sql statement text distinguishes different compiled statements and acts
047: * as lookup key when a session initially looks for an existing instance of
048: * the compiled sql statement.<p>
049: *
050: * Once a session is linked with a statement, it uses the uniqe compiled
051: * statement id for the sql statement to access the statement.<p>
052: *
053: * Changes to database structure via DDL statements, will result in all
054: * registered CompiledStatement objects to become invalidated. This is done by
055: * setting to null all the managed CompiledStatement instances, while keeping
056: * their id and sql string. When a session subsequently attempts to use an
057: * invalidated (null) CompiledStatement via its id, it will reinstantiate the
058: * CompiledStatement using its sql statement still held by this class.<p>
059: *
060: * This class keeps count of the number of different sessions that are linked
061: * to each registered compiled statement, and the number of times each session
062: * is linked. It unregisters a compiled statement when no session remains
063: * linked to it.<p>
064: *
065: * Modified by fredt@users from the original by boucherb@users to simplify,
066: * support multiple identical prepared statements per session, and avoid
067: * keeping references to CompiledStatement objects after DDL changes which
068: * could result in memory leaks. Modified further to support schemas.<p>
069: *
070: * @author boucherb@users
071: * @author fredt@users
072: *
073: * @since 1.7.2
074: * @version 1.8.0
075: */
076: final class CompiledStatementManager {
077:
078: /**
079: * The Database for which this object is managing
080: * CompiledStatement objects.
081: */
082: private Database database;
083:
084: /** Map: Schema id (int) => {Map: SQL String => Compiled Statement id (int)} */
085: private IntKeyHashMap schemaMap;
086:
087: /** Map: Compiled Statement id (int) => SQL String */
088: private IntKeyHashMap sqlLookup;
089:
090: /** Map: Compiled statment id (int) => CompiledStatement object. */
091: private IntKeyHashMap csidMap;
092:
093: /** Map: Session id (int) => {Map: compiled statement id (int) => use count in session} */
094: private IntKeyHashMap sessionUseMap;
095:
096: /** Map: Compiled statment id (int) => number of sessions that use the statement */
097: private IntKeyIntValueHashMap useMap;
098:
099: /**
100: * Monotonically increasing counter used to assign unique ids to compiled
101: * statements.
102: */
103: private int next_cs_id;
104:
105: /**
106: * Constructs a new instance of <code>CompiledStatementManager</code>.
107: *
108: * @param database the Database instance for which this object is to
109: * manage compiled statement objects.
110: */
111: CompiledStatementManager(Database database) {
112:
113: this .database = database;
114: schemaMap = new IntKeyHashMap();
115: sqlLookup = new IntKeyHashMap();
116: csidMap = new IntKeyHashMap();
117: sessionUseMap = new IntKeyHashMap();
118: useMap = new IntKeyIntValueHashMap();
119: next_cs_id = 0;
120: }
121:
122: /**
123: * Clears all internal data structures, removing any references to compiled statements.
124: */
125: synchronized void reset() {
126:
127: schemaMap.clear();
128: sqlLookup.clear();
129: csidMap.clear();
130: sessionUseMap.clear();
131: useMap.clear();
132:
133: next_cs_id = 0;
134: }
135:
136: /**
137: * Used after a DDL change that could impact the compiled statements.
138: * Clears references to CompiledStatement objects while keeping the counts
139: * and references to the sql strings.
140: */
141: synchronized void resetStatements() {
142:
143: Iterator it = csidMap.values().iterator();
144:
145: while (it.hasNext()) {
146: CompiledStatement cs = (CompiledStatement) it.next();
147:
148: cs.clearVariables();
149: }
150: }
151:
152: /**
153: * Retrieves the next compiled statement identifier in the sequence.
154: *
155: * @return the next compiled statement identifier in the sequence.
156: */
157: private int nextID() {
158:
159: next_cs_id++;
160:
161: return next_cs_id;
162: }
163:
164: /**
165: * Retrieves the registered compiled statement identifier associated with
166: * the specified SQL String, or a value less than zero, if no such
167: * statement has been registered.
168: *
169: * @param schema the schema id
170: * @param sql the SQL String
171: * @return the compiled statement identifier associated with the
172: * specified SQL String
173: */
174: private int getStatementID(HsqlName schema, String sql) {
175:
176: IntValueHashMap sqlMap = (IntValueHashMap) schemaMap.get(schema
177: .hashCode());
178:
179: if (sqlMap == null) {
180: return -1;
181: }
182:
183: return sqlMap.get(sql, -1);
184: }
185:
186: /**
187: * Returns an existing CompiledStatement object with the given
188: * statement identifier. Returns null if the CompiledStatement object
189: * has been invalidated and cannot be recompiled
190: *
191: * @param session the session
192: * @param csid the identifier of the requested CompiledStatement object
193: * @return the requested CompiledStatement object
194: */
195: synchronized CompiledStatement getStatement(Session session,
196: int csid) {
197:
198: CompiledStatement cs = (CompiledStatement) csidMap.get(csid);
199:
200: if (cs == null) {
201: return null;
202: }
203:
204: if (!cs.isValid) {
205: String sql = (String) sqlLookup.get(csid);
206:
207: // revalidate with the original schema
208: try {
209: cs = compileSql(session, sql, cs.schemaHsqlName.name);
210: cs.id = csid;
211:
212: csidMap.put(csid, cs);
213: } catch (Throwable t) {
214: freeStatement(csid, session.getId(), true);
215:
216: return null;
217: }
218: }
219:
220: return cs;
221: }
222:
223: /**
224: * Links a session with a registered compiled statement.
225: *
226: * If this session has not already been linked with the given
227: * statement, then the statement use count is incremented.
228: *
229: * @param csid the compiled statement identifier
230: * @param sid the session identifier
231: */
232: private void linkSession(int csid, int sid) {
233:
234: IntKeyIntValueHashMap scsMap;
235:
236: scsMap = (IntKeyIntValueHashMap) sessionUseMap.get(sid);
237:
238: if (scsMap == null) {
239: scsMap = new IntKeyIntValueHashMap();
240:
241: sessionUseMap.put(sid, scsMap);
242: }
243:
244: int count = scsMap.get(csid, 0);
245:
246: scsMap.put(csid, count + 1);
247:
248: if (count == 0) {
249: useMap.put(csid, useMap.get(csid, 0) + 1);
250: }
251: }
252:
253: /**
254: * Registers a compiled statement to be managed.
255: *
256: * The only caller should be a Session that is attempting to prepare
257: * a statement for the first time or process a statement that has been
258: * invalidated due to DDL changes.
259: *
260: * @param csid existing id or negative if the statement is not yet managed
261: * @param cs The CompiledStatement to add
262: * @return The compiled statement id assigned to the CompiledStatement
263: * object
264: */
265: private int registerStatement(int csid, CompiledStatement cs) {
266:
267: if (csid < 0) {
268: csid = nextID();
269:
270: int schemaid = cs.schemaHsqlName.hashCode();
271: IntValueHashMap sqlMap = (IntValueHashMap) schemaMap
272: .get(schemaid);
273:
274: if (sqlMap == null) {
275: sqlMap = new IntValueHashMap();
276:
277: schemaMap.put(schemaid, sqlMap);
278: }
279:
280: sqlMap.put(cs.sql, csid);
281: sqlLookup.put(csid, cs.sql);
282: }
283:
284: cs.id = csid;
285:
286: csidMap.put(csid, cs);
287:
288: return csid;
289: }
290:
291: /**
292: * Removes one (or all) of the links between a session and a compiled statement.
293: *
294: * If the statement is not linked with any other session, it is removed
295: * from management.
296: *
297: * @param csid the compiled statment identifier
298: * @param sid the session identifier
299: * @param freeAll if true, remove all links to the session
300: */
301: void freeStatement(int csid, int sid, boolean freeAll) {
302:
303: if (csid == -1) {
304:
305: // statement was never added
306: return;
307: }
308:
309: IntKeyIntValueHashMap scsMap = (IntKeyIntValueHashMap) sessionUseMap
310: .get(sid);
311:
312: if (scsMap == null) {
313:
314: // statement already removed due to invalidation
315: return;
316: }
317:
318: int sessionUseCount = scsMap.get(csid, 0);
319:
320: if (sessionUseCount == 0) {
321:
322: // statement already removed due to invalidation
323: } else if (sessionUseCount == 1 || freeAll) {
324: scsMap.remove(csid);
325:
326: int usecount = useMap.get(csid, 0);
327:
328: if (usecount == 0) {
329:
330: // statement already removed due to invalidation
331: } else if (usecount == 1) {
332: CompiledStatement cs = (CompiledStatement) csidMap
333: .remove(csid);
334:
335: if (cs != null) {
336: int schemaid = cs.schemaHsqlName.hashCode();
337: IntValueHashMap sqlMap = (IntValueHashMap) schemaMap
338: .get(schemaid);
339: String sql = (String) sqlLookup.remove(csid);
340:
341: sqlMap.remove(sql);
342: }
343:
344: useMap.remove(csid);
345: } else {
346: useMap.put(csid, usecount - 1);
347: }
348: } else {
349: scsMap.put(csid, sessionUseCount - 1);
350: }
351: }
352:
353: /**
354: * Releases the link betwen the session and all compiled statement objects
355: * it is linked to.
356: *
357: * If any such statement is not linked with any other session, it is
358: * removed from management.
359: *
360: * @param sid the session identifier
361: */
362: synchronized void removeSession(int sid) {
363:
364: IntKeyIntValueHashMap scsMap;
365: int csid;
366: Iterator i;
367:
368: scsMap = (IntKeyIntValueHashMap) sessionUseMap.remove(sid);
369:
370: if (scsMap == null) {
371: return;
372: }
373:
374: i = scsMap.keySet().iterator();
375:
376: while (i.hasNext()) {
377: csid = i.nextInt();
378:
379: int usecount = useMap.get(csid, 1) - 1;
380:
381: if (usecount == 0) {
382: CompiledStatement cs = (CompiledStatement) csidMap
383: .remove(csid);
384:
385: if (cs != null) {
386: int schemaid = cs.schemaHsqlName.hashCode();
387: IntValueHashMap sqlMap = (IntValueHashMap) schemaMap
388: .get(schemaid);
389: String sql = (String) sqlLookup.remove(csid);
390:
391: sqlMap.remove(sql);
392: }
393:
394: useMap.remove(csid);
395: } else {
396: useMap.put(csid, usecount);
397: }
398: }
399: }
400:
401: /**
402: * Retrieves a MULTI Result describing three aspects of the
403: * CompiledStatement prepared from the SQL argument for execution
404: * in this session context. <p>
405: *
406: * <ol>
407: * <li>A PREPARE_ACK mode Result describing id of the statement
408: * prepared by this request. This is used by the JDBC implementation
409: * to later identify to the engine which prepared statement to execute.
410: *
411: * <li>A DATA mode result describing the statement's result set metadata.
412: * This is used to generate the JDBC ResultSetMetaData object returned
413: * by PreparedStatement.getMetaData and CallableStatement.getMetaData.
414: *
415: * <li>A DATA mode result describing the statement's parameter metdata.
416: * This is used to by the JDBC implementation to determine
417: * how to send parameters back to the engine when executing the
418: * statement. It is also used to construct the JDBC ParameterMetaData
419: * object for PreparedStatements and CallableStatements.
420: *
421: * @param session the session
422: * @param sql a string describing the desired statement object
423: * @return a MULTI Result describing the compiled statement.
424: */
425: synchronized CompiledStatement compile(Session session, String sql)
426: throws Throwable {
427:
428: int csid = getStatementID(session.currentSchema, sql);
429: CompiledStatement cs = (CompiledStatement) csidMap.get(csid);
430:
431: if (cs == null || !cs.isValid || !session.isAdmin()) {
432: cs = compileSql(session, sql, session.currentSchema.name);
433: csid = registerStatement(csid, cs);
434: }
435:
436: linkSession(csid, session.getId());
437:
438: return cs;
439: }
440:
441: private CompiledStatement compileSql(Session session, String sql,
442: String schemaName) throws Throwable {
443:
444: Session sys = database.sessionManager.getSysSession(schemaName,
445: session.getUser());
446:
447: return sys.sqlCompileStatement(sql);
448: }
449: }
|