/* Copyright (c) 2001-2010, 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.LongKeyHashMap; import org.hsqldb.lib.LongValueHashMap; import org.hsqldb.result.Result; /** * This class manages the reuse of Statement objects for prepared * statements for a Session instance.<p> * * A compiled statement is registered by a session to be managed. Once * registered, it is linked with one or more sessions.<p> * * 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.<p> * * Once a session is linked with a statement, it uses the uniqe compiled * statement id for the sql statement to access the statement.<p> * * Changes to database structure via DDL statements, will result in all * registered Statement objects to become invalidated. This is done by * comparing the schema change and compile timestamps. When a session * subsequently attempts to use an invalidated Statement via its id, it will * reinstantiate the Statement using its sql statement still held by this class.<p> * * This class keeps count of the number of time each registered compiled * statement is linked to a session. It unregisters a compiled statement when * no session remains linked to it.<p> * * Modified by fredt@users from the original by boucherb@users to simplify, * support multiple identical prepared statements per session, and avoid * memory leaks. Modified further to support schemas. Changed implementation * in 1.9 as a session object<p> * * @author Campbell Boucher-Burnett (boucherb@users dot sourceforge.net) * @author Fred Toussi (fredt@users dot sourceforge.net) * * @version 1.9.0 * @since 1.7.2 */ public final class StatementManager { /** * The Database for which this object is managing * CompiledStatement objects. */ private Database database; /** Map: Schema id (int) => {Map: SQL String => Compiled Statement id (long)} */ private IntKeyHashMap schemaMap; /** Map: Compiled Statement id (int) => SQL String */ private LongKeyHashMap sqlLookup; /** Map: Compiled statment id (int) => CompiledStatement object. */ private LongKeyHashMap csidMap; /** * Monotonically increasing counter used to assign unique ids to compiled * statements. */ private long next_cs_id; /** * Constructs a new instance of <code>CompiledStatementManager</code>. * * @param database the Database instance for which this object is to * manage compiled statement objects. */ StatementManager(Database database) { this.database = database; schemaMap = new IntKeyHashMap(); sqlLookup = new LongKeyHashMap(); csidMap = new LongKeyHashMap(); next_cs_id = 0; } /** * Clears all internal data structures, removing any references to compiled statements. */ synchronized void reset() { schemaMap.clear(); sqlLookup.clear(); csidMap.clear(); next_cs_id = 0; } /** * Retrieves the next compiled statement identifier in the sequence. * * @return the next compiled statement identifier in the sequence. */ private long 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 long getStatementID(HsqlName schema, String sql) { LongValueHashMap sqlMap = (LongValueHashMap) 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 */ public synchronized Statement getStatement(Session session, long csid) { Statement cs = (Statement) csidMap.get(csid); if (cs == null) { return null; } if (cs.getCompileTimestamp() < database.schemaManager.getSchemaChangeTimestamp()) { String sql = (String) sqlLookup.get(csid); HsqlName oldSchema = session.getCurrentSchemaHsqlName(); // revalidate with the original schema try { HsqlName schema = cs.getSchemaName(); session.setSchema(schema.name); StatementInsert si = null; if (cs.generatedResultMetaData() != null) { si = (StatementInsert) cs; } cs = session.compileStatement(sql, cs.getResultProperties()); cs.setID(csid); cs.setCompileTimestamp( database.txManager.getGlobalChangeTimestamp()); if (si != null) { cs.setGeneratedColumnInfo(si.generatedType, si.generatedInputMetaData); } csidMap.put(csid, cs); } catch (Throwable t) { freeStatement(csid); return null; } finally { session.setSchema(oldSchema.name); } } return cs; } /** * 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 long registerStatement(long csid, Statement cs) { if (csid < 0) { csid = nextID(); int schemaid = cs.getSchemaName().hashCode(); LongValueHashMap sqlMap = (LongValueHashMap) schemaMap.get(schemaid); if (sqlMap == null) { sqlMap = new LongValueHashMap(); schemaMap.put(schemaid, sqlMap); } sqlMap.put(cs.getSQL(), csid); sqlLookup.put(csid, cs.getSQL()); } cs.setID(csid); cs.setCompileTimestamp(database.txManager.getGlobalChangeTimestamp()); 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 sessionID the session identifier * @param freeAll if true, remove all links to the session */ synchronized void freeStatement(long csid) { if (csid == -1) { // statement was never added return; } Statement cs = (Statement) csidMap.remove(csid); if (cs != null) { int schemaid = cs.getSchemaName().hashCode(); LongValueHashMap sqlMap = (LongValueHashMap) schemaMap.get(schemaid); String sql = (String) sqlLookup.remove(csid); sqlMap.remove(sql); } } /** * Compiles an SQL statement and returns a CompiledStatement Object * * @param session the session * @throws Throwable * @return CompiledStatement */ synchronized Statement compile(Session session, Result cmd) throws Throwable { String sql = cmd.getMainString(); long csid = getStatementID(session.currentSchema, sql); Statement cs = (Statement) csidMap.get(csid); int props; if (cs == null || !cs.isValid() || cs.getCompileTimestamp() < database.schemaManager.getSchemaChangeTimestamp()) { props = cmd.getExecuteProperties(); cs = session.compileStatement(sql, props); csid = registerStatement(csid, cs); } cs.setGeneratedColumnInfo(cmd.getGeneratedResultType(), cmd.getGeneratedResultMetaData()); return cs; } }