/* Copyright (c) 2001-2008, The HSQL Development Group * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the HSQL Development Group nor the names of its * contributors may be used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG, * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.hsqldb; import org.hsqldb.HsqlNameManager.HsqlName; import org.hsqldb.lib.IntKeyHashMap; import org.hsqldb.lib.IntKeyIntValueHashMap; import org.hsqldb.lib.IntValueHashMap; import org.hsqldb.lib.Iterator; /** * This class manages the reuse of CompiledStatement objects for prepared * statements for a Database instance.
* * A compiled statement is registered by a session to be managed. Once * registered, it is linked with one or more sessions.
* * The sql statement text distinguishes different compiled statements and acts * as lookup key when a session initially looks for an existing instance of * the compiled sql statement.
* * Once a session is linked with a statement, it uses the uniqe compiled * statement id for the sql statement to access the statement.
* * Changes to database structure via DDL statements, will result in all * registered CompiledStatement objects to become invalidated. This is done by * setting to null all the managed CompiledStatement instances, while keeping * their id and sql string. When a session subsequently attempts to use an * invalidated (null) CompiledStatement via its id, it will reinstantiate the * CompiledStatement using its sql statement still held by this class.
* * This class keeps count of the number of different sessions that are linked * to each registered compiled statement, and the number of times each session * is linked. It unregisters a compiled statement when no session remains * linked to it.
* * Modified by fredt@users from the original by boucherb@users to simplify, * support multiple identical prepared statements per session, and avoid * keeping references to CompiledStatement objects after DDL changes which * could result in memory leaks. Modified further to support schemas.
*
* @author boucherb@users
* @author fredt@users
*
* @since 1.7.2
* @version 1.8.0
*/
final public class CompiledStatementManager {
/**
* The Database for which this object is managing
* CompiledStatement objects.
*/
private Database database;
/** Map: Schema id (int) => {Map: SQL String => Compiled Statement id (int)} */
private IntKeyHashMap schemaMap;
/** Map: Compiled Statement id (int) => SQL String */
private IntKeyHashMap sqlLookup;
/** Map: Compiled statment id (int) => CompiledStatement object. */
private IntKeyHashMap csidMap;
/** Map: Session id (int) => {Map: compiled statement id (int) => use count in session} */
private IntKeyHashMap sessionUseMap;
/** Map: Compiled statment id (int) => number of sessions that use the statement */
private IntKeyIntValueHashMap useMap;
/**
* Monotonically increasing counter used to assign unique ids to compiled
* statements.
*/
private int next_cs_id;
/**
* Constructs a new instance of CompiledStatementManager
.
*
* @param database the Database instance for which this object is to
* manage compiled statement objects.
*/
CompiledStatementManager(Database database) {
this.database = database;
schemaMap = new IntKeyHashMap();
sqlLookup = new IntKeyHashMap();
csidMap = new IntKeyHashMap();
sessionUseMap = new IntKeyHashMap();
useMap = new IntKeyIntValueHashMap();
next_cs_id = 0;
}
/**
* Clears all internal data structures, removing any references to compiled statements.
*/
synchronized void reset() {
schemaMap.clear();
sqlLookup.clear();
csidMap.clear();
sessionUseMap.clear();
useMap.clear();
next_cs_id = 0;
}
/**
* Used after a DDL change that could impact the compiled statements.
* Clears references to CompiledStatement objects while keeping the counts
* and references to the sql strings.
*/
synchronized void resetStatements() {
Iterator it = csidMap.values().iterator();
while (it.hasNext()) {
CompiledStatement cs = (CompiledStatement) it.next();
cs.clearVariables();
}
}
/**
* Retrieves the next compiled statement identifier in the sequence.
*
* @return the next compiled statement identifier in the sequence.
*/
private int nextID() {
next_cs_id++;
return next_cs_id;
}
/**
* Retrieves the registered compiled statement identifier associated with
* the specified SQL String, or a value less than zero, if no such
* statement has been registered.
*
* @param schema the schema id
* @param sql the SQL String
* @return the compiled statement identifier associated with the
* specified SQL String
*/
private int getStatementID(HsqlName schema, String sql) {
IntValueHashMap sqlMap =
(IntValueHashMap) schemaMap.get(schema.hashCode());
if (sqlMap == null) {
return -1;
}
return sqlMap.get(sql, -1);
}
/**
* Returns an existing CompiledStatement object with the given
* statement identifier. Returns null if the CompiledStatement object
* has been invalidated and cannot be recompiled
*
* @param session the session
* @param csid the identifier of the requested CompiledStatement object
* @return the requested CompiledStatement object
*/
synchronized CompiledStatement getStatement(Session session, int csid) {
CompiledStatement cs = (CompiledStatement) csidMap.get(csid);
if (cs == null) {
return null;
}
if (!cs.isValid) {
String sql = (String) sqlLookup.get(csid);
// revalidate with the original schema
try {
cs = compileSql(session, sql, cs.schemaHsqlName.name);
cs.id = csid;
csidMap.put(csid, cs);
} catch (Exception t) {
freeStatement(csid, session.getId(), true);
return null;
}
}
return cs;
}
/**
* Links a session with a registered compiled statement.
*
* If this session has not already been linked with the given
* statement, then the statement use count is incremented.
*
* @param csid the compiled statement identifier
* @param sid the session identifier
*/
private void linkSession(int csid, int sid) {
IntKeyIntValueHashMap scsMap;
scsMap = (IntKeyIntValueHashMap) sessionUseMap.get(sid);
if (scsMap == null) {
scsMap = new IntKeyIntValueHashMap();
sessionUseMap.put(sid, scsMap);
}
int count = scsMap.get(csid, 0);
scsMap.put(csid, count + 1);
if (count == 0) {
useMap.put(csid, useMap.get(csid, 0) + 1);
}
}
/**
* Registers a compiled statement to be managed.
*
* The only caller should be a Session that is attempting to prepare
* a statement for the first time or process a statement that has been
* invalidated due to DDL changes.
*
* @param csid existing id or negative if the statement is not yet managed
* @param cs The CompiledStatement to add
* @return The compiled statement id assigned to the CompiledStatement
* object
*/
private int registerStatement(int csid, CompiledStatement cs) {
if (csid < 0) {
csid = nextID();
int schemaid = cs.schemaHsqlName.hashCode();
IntValueHashMap sqlMap =
(IntValueHashMap) schemaMap.get(schemaid);
if (sqlMap == null) {
sqlMap = new IntValueHashMap();
schemaMap.put(schemaid, sqlMap);
}
sqlMap.put(cs.sql, csid);
sqlLookup.put(csid, cs.sql);
}
cs.id = csid;
csidMap.put(csid, cs);
return csid;
}
/**
* Removes one (or all) of the links between a session and a compiled statement.
*
* If the statement is not linked with any other session, it is removed
* from management.
*
* @param csid the compiled statment identifier
* @param sid the session identifier
* @param freeAll if true, remove all links to the session
*/
void freeStatement(int csid, int sid, boolean freeAll) {
if (csid == -1) {
// statement was never added
return;
}
IntKeyIntValueHashMap scsMap =
(IntKeyIntValueHashMap) sessionUseMap.get(sid);
if (scsMap == null) {
// statement already removed due to invalidation
return;
}
int sessionUseCount = scsMap.get(csid, 0);
if (sessionUseCount == 0) {
// statement already removed due to invalidation
} else if (sessionUseCount == 1 || freeAll) {
scsMap.remove(csid);
int usecount = useMap.get(csid, 0);
if (usecount == 0) {
// statement already removed due to invalidation
} else if (usecount == 1) {
CompiledStatement cs =
(CompiledStatement) csidMap.remove(csid);
if (cs != null) {
int schemaid = cs.schemaHsqlName.hashCode();
IntValueHashMap sqlMap =
(IntValueHashMap) schemaMap.get(schemaid);
String sql = (String) sqlLookup.remove(csid);
sqlMap.remove(sql);
}
useMap.remove(csid);
} else {
useMap.put(csid, usecount - 1);
}
} else {
scsMap.put(csid, sessionUseCount - 1);
}
}
/**
* Releases the link betwen the session and all compiled statement objects
* it is linked to.
*
* If any such statement is not linked with any other session, it is
* removed from management.
*
* @param sid the session identifier
*/
synchronized void removeSession(int sid) {
IntKeyIntValueHashMap scsMap;
int csid;
Iterator i;
scsMap = (IntKeyIntValueHashMap) sessionUseMap.remove(sid);
if (scsMap == null) {
return;
}
i = scsMap.keySet().iterator();
while (i.hasNext()) {
csid = i.nextInt();
int usecount = useMap.get(csid, 1) - 1;
if (usecount == 0) {
CompiledStatement cs =
(CompiledStatement) csidMap.remove(csid);
if (cs != null) {
int schemaid = cs.schemaHsqlName.hashCode();
IntValueHashMap sqlMap =
(IntValueHashMap) schemaMap.get(schemaid);
String sql = (String) sqlLookup.remove(csid);
sqlMap.remove(sql);
}
useMap.remove(csid);
} else {
useMap.put(csid, usecount);
}
}
}
/**
* Retrieves a MULTI Result describing three aspects of the
* CompiledStatement prepared from the SQL argument for execution
* in this session context.
* *