/*

   Derby - Class org.apache.derby.client.am.stmtcache.StatementKey

   Licensed to the Apache Software Foundation (ASF) under one or more
   contributor license agreements.  See the NOTICE file distributed with
   this work for additional information regarding copyright ownership.
   The ASF licenses this file to You under the Apache License, Version 2.0
   (the "License"); you may not use this file except in compliance with
   the License.  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

 */

package org.apache.derby.client.am.stmtcache;

import java.sql.ResultSet;
import java.sql.Statement;
import org.apache.derby.shared.common.sanity.SanityManager;

/**
 * A key representing a <code>java.sql.PreparedStatement</code> or a
 * <code>java.sql.CallableStatement</code>.
 * <p>
 * The key takes a number of statement related attributes into account, and is
 * used to insert and look up cached statement objects in the JDBC statement
 * cache.
 * <p>
 * Key instances are created by a statement key factory.
 *
 * @see StatementKeyFactory
 */
//@Immutable
public class StatementKey {

    /** Tells if the key represents a <code>CallableStatement</code>. */
    private final boolean isCallableStatement;
    /** The SQL query of the statement. */
    private final String sql;
    /** The compilation schema for the statement. */
    private final String schema;
    /** The result set type for the statement. */
    private final int type;
    /** The result set concurrency for the statement. */
    private final int concurrency;
    /** Result set holdability for the statement. */
    private final int holdability;
    /** Tells if the associated statement returns auto-generated keys. */
    private final int autogeneratedKeys;

    /**
     * Creates a statement key with all the common properties.
     *
     * @param isCallableStatement <code>true</code> is this is a key for a
     *      <code>java.sql.CallableStatement</code>
     * @param sql SQL query string
     * @param schema compilation schema
     * @param rsType result set type
     * @param rsConcurrency result set concurrency
     * @param rsHoldability result set holdability
     * @param autogeneratedKeys if auto-generated keys are returned
     *
     * @throws IllegalArgumentException if <code>sql</code> and/or
     *      <code>schema</code> is <code>null</code>
     */
    StatementKey(boolean isCallableStatement, String sql, String schema,
            int rsType, int rsConcurrency, int rsHoldability,
            int autogeneratedKeys) {
        if (sql == null || schema == null) {
            // Not localized (yet), because this should never reach the user.
            throw new IllegalArgumentException(
                    "sql and/or schema is <null>: sql=" + (sql == null) +
                    ", schema=" + (schema == null));
        }
        this.isCallableStatement = isCallableStatement;
        this.sql = sql;
        this.schema = schema;
        this.type = rsType;
        this.concurrency = rsConcurrency;
        this.holdability = rsHoldability;
        this.autogeneratedKeys = autogeneratedKeys;
        // In sane builds, make sure valid JDBC values are passed.
        if (SanityManager.DEBUG) {
            SanityManager.ASSERT(
                    rsType == ResultSet.TYPE_FORWARD_ONLY ||
                    rsType == ResultSet.TYPE_SCROLL_INSENSITIVE ||
                    rsType == ResultSet.TYPE_SCROLL_SENSITIVE,
                    "Invalid result set type: " + rsType);
            SanityManager.ASSERT(
                    rsConcurrency == ResultSet.CONCUR_READ_ONLY ||
                    rsConcurrency == ResultSet.CONCUR_UPDATABLE,
                    "Invalid result set concurrency: " + rsConcurrency);
            SanityManager.ASSERT(
                    rsHoldability == ResultSet.HOLD_CURSORS_OVER_COMMIT ||
                    rsHoldability == ResultSet.CLOSE_CURSORS_AT_COMMIT,
                    "Invalid result set holdability: " + rsHoldability);
            SanityManager.ASSERT(
                    autogeneratedKeys == Statement.NO_GENERATED_KEYS ||
                    autogeneratedKeys == Statement.RETURN_GENERATED_KEYS,
                    "Invalid autogenerated key value: " + autogeneratedKeys);
        }
    }

    public boolean equals(Object obj) {
        if (!(obj instanceof StatementKey)) {
            return false;
        }
        final StatementKey other = (StatementKey)obj;
        if (this.holdability != other.holdability) {
            return false;
        }
        if (this.autogeneratedKeys != other.autogeneratedKeys) {
            return false;
        }
        if (this.isCallableStatement != other.isCallableStatement) {
            return false;
        }
        if (!this.schema.equals(other.schema)) {
            return false;
        }
        if (!this.sql.equals(other.sql)) {
            return false;
        }
        if (this.type != other.type) {
            return false;
        }
        if (this.concurrency != other.concurrency) {
            return false;
        }
        return true;
    }

    public int hashCode() {
        int hash = 7;
        hash = 47 * hash + (this.isCallableStatement ? 1 : 0);
        hash = 47 * hash + this.sql.hashCode();
        hash = 47 * hash + this.schema.hashCode();
        hash = 47 * hash + this.type;
        hash = 47 * hash + this.concurrency;
        hash = 47 * hash + this.holdability;
        hash = 47 * hash + this.autogeneratedKeys;
        return hash;
    }

    public String toString() {
        return "'" + sql + "' in '" + schema + "', rsh = " + holdability +
                ", rst = " + type + ", rsc = " + concurrency +
                ", autogenKeys = " + autogeneratedKeys +
                ", isCallableStatement = " + isCallableStatement;
    }
}

@@ -2648,84 +2648,38 @@
  if 
-%3C
+%7B@
 code
-%3Esql%3C/code%3E and/or%0A     *      %3Ccode%3Eschema%3C/code%3E
+ schema%7D
  is 
-%3C
+%7B@
 code
-%3E
+ 
 null
-%3C/code%3E
+%7D
 %0A   
@@ -2871,23 +2871,8 @@
 if (
-sql == null %7C%7C 
 sche
@@ -3014,133 +3014,26 @@
 ion(
-%0A                    %22sql and/or schema is %3Cnull%3E: sql=%22 + (sql == null) +%0A                    %22, schema=%22 + (schema == null)
+%22schema is %3Cnull%3E%22
 );%0A 
@@ -5017,32 +5017,121 @@
 alse;%0A        %7D%0A
+        if (this.sql == null && other.sql != null) %7B%0A            return false;%0A        %7D%0A
         if (!thi
@@ -5520,24 +5520,48 @@
  47 * hash +
+ (this.sql == null ? 3 :
  this.sql.ha
@@ -5560,32 +5560,33 @@
 s.sql.hashCode()
+)
 ;%0A        hash =

Allow SQL query text to be null in StatementKey. Patch file: derby-3438-1a-allow_sql_null.diff