/* Copyright (c) 2001-2019, 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.LongKeyIntValueHashMap;
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.

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 unique compiled statement id for the sql statement to access the statement.

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.

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.

Modified by fredt@users from the original by campbell-burnet@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

Author:Campbell Burnet (campbell-burnet@users dot sourceforge.net), Fred Toussi (fredt@users dot sourceforge.net)
Version:2.3.5
Since:1.7.2
/** * 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 unique 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 campbell-burnet@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 Burnet (campbell-burnet@users dot sourceforge.net) * @author Fred Toussi (fredt@users dot sourceforge.net) * * @version 2.3.5 * @since 1.7.2 */
public final class StatementManager {
The Database for which this object is managing CompiledStatement objects.
/** * The Database for which this object is managing * CompiledStatement objects. */
private Database database;
Map: Schema id (int) => {Map: SQL String => Compiled Statement id (long)}
/** Map: Schema id (int) => {Map: SQL String => Compiled Statement id (long)} */
private IntKeyHashMap schemaMap;
Map: Compiled statement id (int) => CompiledStatement object.
/** Map: Compiled statement id (int) => CompiledStatement object. */
private LongKeyHashMap csidMap;
Map: Compiled statement id (int) => number of uses of the statement
/** Map: Compiled statement id (int) => number of uses of the statement */
private LongKeyIntValueHashMap useMap;
Monotonically increasing counter used to assign unique ids to compiled statements.
/** * Monotonically increasing counter used to assign unique ids to compiled * statements. */
private long next_cs_id;
Constructs a new instance of CompiledStatementManager.
Params:
  • database – the Database instance for which this object is to manage compiled statement objects.
/** * 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(); csidMap = new LongKeyHashMap(); useMap = new LongKeyIntValueHashMap(); next_cs_id = 0; }
Clears all internal data structures, removing any references to compiled statements.
/** * Clears all internal data structures, removing any references to compiled statements. */
synchronized void reset() { schemaMap.clear(); csidMap.clear(); useMap.clear(); next_cs_id = 0; }
Retrieves the next compiled statement identifier in the sequence.
Returns:the next compiled statement identifier in the sequence.
/** * 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.
Params:
  • schema – the schema id
  • sql – the SQL String
Returns:the compiled statement identifier associated with the specified SQL String
/** * 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
Params:
  • session – the session
  • csid – the identifier of the requested CompiledStatement object
Returns:the requested CompiledStatement object
/** * 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()) { Statement newStatement = recompileStatement(session, cs); if (newStatement == null) { freeStatement(csid); return null; } registerStatement(cs.getID(), newStatement); return newStatement; } return cs; }
Recompiles an existing statement
Params:
  • session – the session
  • statement – the old CompiledStatement object
Returns:the requested CompiledStatement object
/** * Recompiles an existing statement * * @param session the session * @param statement the old CompiledStatement object * @return the requested CompiledStatement object */
public synchronized Statement getStatement(Session session, Statement statement) { long csid = statement.getID(); Statement cs = (Statement) csidMap.get(csid); if (cs != null) { return getStatement(session, csid); } cs = recompileStatement(session, statement); return cs; } private Statement recompileStatement(Session session, Statement cs) { HsqlName oldSchema = session.getCurrentSchemaHsqlName(); Statement newStatement; // revalidate with the original schema try { HsqlName schema = cs.getSchemaName(); int props = cs.getCursorPropertiesRequest(); if (schema != null) { // checks the old schema exists session.setSchema(schema.name); } boolean setGenerated = cs.generatedResultMetaData() != null; newStatement = session.compileStatement(cs.getSQL(), props); newStatement.setCursorPropertiesRequest(props); if (!cs.getResultMetaData().areTypesCompatible( newStatement.getResultMetaData())) { return null; } if (!cs.getParametersMetaData().areTypesCompatible( newStatement.getParametersMetaData())) { return null; } newStatement.setCompileTimestamp( database.txManager.getGlobalChangeTimestamp()); if (setGenerated) { StatementDML si = (StatementDML) cs; newStatement.setGeneratedColumnInfo(si.generatedType, si.generatedInputMetaData); } } catch (Throwable t) { return null; } finally { session.setCurrentSchemaHsqlName(oldSchema); } return newStatement; }
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.
Params:
  • csid – existing id or negative if the statement is not yet managed
  • cs – The CompiledStatement to add
Returns:The compiled statement id assigned to the CompiledStatement object
/** * 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) { cs.setCompileTimestamp(database.txManager.getGlobalChangeTimestamp()); int schemaid = cs.getSchemaName().hashCode(); LongValueHashMap sqlMap = (LongValueHashMap) schemaMap.get(schemaid); if (sqlMap == null) { sqlMap = new LongValueHashMap(); schemaMap.put(schemaid, sqlMap); } if (csid < 0) { csid = nextID(); } cs.setID(csid); sqlMap.put(cs.getSQL(), 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.
Params:
  • csid – the compiled statement identifier
/** * 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 statement identifier */
synchronized void freeStatement(long csid) { if (csid == -1) { // statement was never added return; } int useCount = useMap.get(csid, 1); if (useCount > 1) { useMap.put(csid, useCount - 1); return; } Statement cs = (Statement) csidMap.remove(csid); if (cs != null) { int schemaid = cs.getSchemaName().hashCode(); LongValueHashMap sqlMap = (LongValueHashMap) schemaMap.get(schemaid); String sql = cs.getSQL(); sqlMap.remove(sql); } useMap.remove(csid); }
Compiles an SQL statement and returns a CompiledStatement Object
Params:
  • session – the session
Throws:
Returns:CompiledStatement
/** * 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 { int props = cmd.getExecuteProperties(); Statement cs = null; String sql = cmd.getMainString(); long csid = getStatementID(session.currentSchema, sql); if (csid >= 0) { cs = (Statement) csidMap.get(csid); } // generated result props still overwrite earlier version if (cs == null || !cs.isValid() || cs.getCompileTimestamp() < database .schemaManager.getSchemaChangeTimestamp() || cs .getCursorPropertiesRequest() != props) { cs = session.compileStatement(sql, props); cs.setCursorPropertiesRequest(props); csid = registerStatement(csid, cs); } int useCount = useMap.get(csid, 0) + 1; useMap.put(csid, useCount); cs.setGeneratedColumnInfo(cmd.getGeneratedResultType(), cmd.getGeneratedResultMetaData()); return cs; } }