001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.dbcp2;
018
019import java.sql.Connection;
020import java.sql.PreparedStatement;
021import java.sql.SQLException;
022import java.sql.Statement;
023import java.util.Arrays;
024
025import org.apache.commons.dbcp2.PoolingConnection.StatementType;
026
027/**
028 * A key uniquely identifying {@link java.sql.PreparedStatement PreparedStatement}s.
029 *
030 * @since 2.0
031 */
032public class PStmtKey {
033
034    /**
035     * SQL defining Prepared or Callable Statement
036     */
037    private final String sql;
038
039    /**
040     * Result set type; one of <code>ResultSet.TYPE_FORWARD_ONLY</code>, <code>ResultSet.TYPE_SCROLL_INSENSITIVE</code>,
041     * or <code>ResultSet.TYPE_SCROLL_SENSITIVE</code>.
042     */
043    private final Integer resultSetType;
044
045    /**
046     * Result set concurrency. A concurrency type; one of <code>ResultSet.CONCUR_READ_ONLY</code> or
047     * <code>ResultSet.CONCUR_UPDATABLE</code>.
048     */
049    private final Integer resultSetConcurrency;
050
051    /**
052     * Result set holdability. One of the following <code>ResultSet</code> constants:
053     * <code>ResultSet.HOLD_CURSORS_OVER_COMMIT</code> or <code>ResultSet.CLOSE_CURSORS_AT_COMMIT</code>.
054     */
055    private final Integer resultSetHoldability;
056
057    /** Database catalog */
058    private final String catalog;
059
060    /**
061     * A flag indicating whether auto-generated keys should be returned; one of
062     * <code>Statement.RETURN_GENERATED_KEYS</code> or <code>Statement.NO_GENERATED_KEYS</code>.
063     */
064    private final Integer autoGeneratedKeys;
065
066    /**
067     * An array of column indexes indicating the columns that should be returned from the inserted row or rows.
068     */
069    private final int[] columnIndexes;
070
071    /**
072     * An array of column names indicating the columns that should be returned from the inserted row or rows.
073     */
074    private final String[] columnNames;
075
076    /**
077     * Statement type, prepared or callable.
078     */
079    private final StatementType statementType;
080
081    /** Statement builder */
082    private StatementBuilder builder;
083
084    /**
085     * Constructs a key to uniquely identify a prepared statement.
086     *
087     * @param sql
088     *            The SQL statement.
089     */
090    public PStmtKey(final String sql) {
091        this(sql, null, StatementType.PREPARED_STATEMENT);
092    }
093
094    /**
095     * Constructs a key to uniquely identify a prepared statement.
096     *
097     * @param sql
098     *            The SQL statement.
099     * @param catalog
100     *            The catalog.
101     */
102    public PStmtKey(final String sql, final String catalog) {
103        this(sql, catalog, StatementType.PREPARED_STATEMENT);
104    }
105
106    /**
107     * Constructs a key to uniquely identify a prepared statement.
108     *
109     * @param sql
110     *            The SQL statement.
111     * @param catalog
112     *            The catalog.
113     * @param statementType
114     *            The SQL statement type, prepared or callable.
115     */
116    public PStmtKey(final String sql, final String catalog, final StatementType statementType) {
117        this.sql = sql;
118        this.catalog = catalog;
119        this.statementType = statementType;
120        this.autoGeneratedKeys = null;
121        this.columnIndexes = null;
122        this.columnNames = null;
123        this.resultSetType = null;
124        this.resultSetConcurrency = null;
125        this.resultSetHoldability = null;
126        // create builder
127        if (statementType == StatementType.PREPARED_STATEMENT) {
128            this.builder = new PreparedStatementSQL();
129        } else if (statementType == StatementType.CALLABLE_STATEMENT) {
130            this.builder = new PreparedCallSQL();
131        }
132    }
133
134    /**
135     * Constructs a key to uniquely identify a prepared statement.
136     *
137     * @param sql
138     *            The SQL statement.
139     * @param catalog
140     *            The catalog.
141     * @param autoGeneratedKeys
142     *            A flag indicating whether auto-generated keys should be returned; one of
143     *            <code>Statement.RETURN_GENERATED_KEYS</code> or <code>Statement.NO_GENERATED_KEYS</code>.
144     */
145    public PStmtKey(final String sql, final String catalog, final int autoGeneratedKeys) {
146        this(sql, catalog, StatementType.PREPARED_STATEMENT, Integer.valueOf(autoGeneratedKeys));
147    }
148
149    /**
150     * Constructs a key to uniquely identify a prepared statement.
151     *
152     * @param sql
153     *            The SQL statement.
154     * @param catalog
155     *            The catalog.
156     * @param statementType
157     *            The SQL statement type, prepared or callable.
158     * @param autoGeneratedKeys
159     *            A flag indicating whether auto-generated keys should be returned; one of
160     *            <code>Statement.RETURN_GENERATED_KEYS</code> or <code>Statement.NO_GENERATED_KEYS</code>.
161     */
162    public PStmtKey(final String sql, final String catalog, final StatementType statementType,
163            final Integer autoGeneratedKeys) {
164        this.sql = sql;
165        this.catalog = catalog;
166        this.statementType = statementType;
167        this.autoGeneratedKeys = autoGeneratedKeys;
168        this.columnIndexes = null;
169        this.columnNames = null;
170        this.resultSetType = null;
171        this.resultSetConcurrency = null;
172        this.resultSetHoldability = null;
173        // create builder
174        if (statementType == StatementType.PREPARED_STATEMENT) {
175            this.builder = new PreparedStatementWithAutoGeneratedKeys();
176        } else if (statementType == StatementType.CALLABLE_STATEMENT) {
177            this.builder = new PreparedCallSQL();
178        }
179    }
180
181    /**
182     * Constructs a key to uniquely identify a prepared statement.
183     *
184     * @param sql
185     *            The SQL statement.
186     * @param catalog
187     *            The catalog.
188     * @param columnIndexes
189     *            An array of column indexes indicating the columns that should be returned from the inserted row or
190     *            rows.
191     */
192    public PStmtKey(final String sql, final String catalog, final int[] columnIndexes) {
193        this.sql = sql;
194        this.catalog = catalog;
195        this.statementType = StatementType.PREPARED_STATEMENT;
196        this.autoGeneratedKeys = null;
197        this.columnIndexes = columnIndexes == null ? null : Arrays.copyOf(columnIndexes, columnIndexes.length);
198        this.columnNames = null;
199        this.resultSetType = null;
200        this.resultSetConcurrency = null;
201        this.resultSetHoldability = null;
202        // create builder
203        this.builder = new PreparedStatementWithColumnIndexes();
204    }
205
206    /**
207     * Constructs a key to uniquely identify a prepared statement.
208     *
209     * @param sql
210     *            The SQL statement.
211     * @param catalog
212     *            The catalog.
213     * @param columnNames
214     *            An array of column names indicating the columns that should be returned from the inserted row or rows.
215     */
216    public PStmtKey(final String sql, final String catalog, final String[] columnNames) {
217        this.sql = sql;
218        this.catalog = catalog;
219        this.statementType = StatementType.PREPARED_STATEMENT;
220        this.autoGeneratedKeys = null;
221        this.columnIndexes = null;
222        this.columnNames = columnNames == null ? null : Arrays.copyOf(columnNames, columnNames.length);
223        this.resultSetType = null;
224        this.resultSetConcurrency = null;
225        this.resultSetHoldability = null;
226        // create builder
227        builder = new PreparedStatementWithColumnNames();
228    }
229
230    /**
231     * Constructs a key to uniquely identify a prepared statement.
232     *
233     * @param sql
234     *            The SQL statement.
235     * @param resultSetType
236     *            A result set type; one of <code>ResultSet.TYPE_FORWARD_ONLY</code>,
237     *            <code>ResultSet.TYPE_SCROLL_INSENSITIVE</code>, or <code>ResultSet.TYPE_SCROLL_SENSITIVE</code>.
238     * @param resultSetConcurrency
239     *            A concurrency type; one of <code>ResultSet.CONCUR_READ_ONLY</code> or
240     *            <code>ResultSet.CONCUR_UPDATABLE</code>.
241     */
242    public PStmtKey(final String sql, final int resultSetType, final int resultSetConcurrency) {
243        this(sql, null, resultSetType, resultSetConcurrency, StatementType.PREPARED_STATEMENT);
244    }
245
246    /**
247     * Constructs a key to uniquely identify a prepared statement.
248     *
249     * @param sql
250     *            The SQL statement.
251     * @param catalog
252     *            The catalog.
253     * @param resultSetType
254     *            A result set type; one of <code>ResultSet.TYPE_FORWARD_ONLY</code>,
255     *            <code>ResultSet.TYPE_SCROLL_INSENSITIVE</code>, or <code>ResultSet.TYPE_SCROLL_SENSITIVE</code>.
256     * @param resultSetConcurrency
257     *            A concurrency type; one of <code>ResultSet.CONCUR_READ_ONLY</code> or
258     *            <code>ResultSet.CONCUR_UPDATABLE</code>.
259     */
260    public PStmtKey(final String sql, final String catalog, final int resultSetType, final int resultSetConcurrency) {
261        this(sql, catalog, resultSetType, resultSetConcurrency, StatementType.PREPARED_STATEMENT);
262    }
263
264    /**
265     * Constructs a key to uniquely identify a prepared statement.
266     *
267     * @param sql
268     *            The SQL statement.
269     * @param catalog
270     *            The catalog.
271     * @param resultSetType
272     *            A result set type; one of <code>ResultSet.TYPE_FORWARD_ONLY</code>,
273     *            <code>ResultSet.TYPE_SCROLL_INSENSITIVE</code>, or <code>ResultSet.TYPE_SCROLL_SENSITIVE</code>.
274     * @param resultSetConcurrency
275     *            A concurrency type; one of <code>ResultSet.CONCUR_READ_ONLY</code> or
276     *            <code>ResultSet.CONCUR_UPDATABLE</code>.
277     * @param statementType
278     *            The SQL statement type, prepared or callable.
279     */
280    public PStmtKey(final String sql, final String catalog, final int resultSetType, final int resultSetConcurrency,
281            final StatementType statementType) {
282        this.sql = sql;
283        this.catalog = catalog;
284        this.resultSetType = Integer.valueOf(resultSetType);
285        this.resultSetConcurrency = Integer.valueOf(resultSetConcurrency);
286        this.resultSetHoldability = null;
287        this.statementType = statementType;
288        this.autoGeneratedKeys = null;
289        this.columnIndexes = null;
290        this.columnNames = null;
291        // create builder
292        if (statementType == StatementType.PREPARED_STATEMENT) {
293            this.builder = new PreparedStatementWithResultSetConcurrency();
294        } else if (statementType == StatementType.CALLABLE_STATEMENT) {
295            this.builder = new PreparedCallWithResultSetConcurrency();
296        }
297    }
298
299    /**
300     * Constructs a key to uniquely identify a prepared statement.
301     *
302     * @param sql
303     *            The SQL statement.
304     * @param catalog
305     *            The catalog.
306     * @param resultSetType
307     *            a result set type; one of <code>ResultSet.TYPE_FORWARD_ONLY</code>,
308     *            <code>ResultSet.TYPE_SCROLL_INSENSITIVE</code>, or <code>ResultSet.TYPE_SCROLL_SENSITIVE</code>.
309     * @param resultSetConcurrency
310     *            A concurrency type; one of <code>ResultSet.CONCUR_READ_ONLY</code> or
311     *            <code>ResultSet.CONCUR_UPDATABLE</code>
312     * @param resultSetHoldability
313     *            One of the following <code>ResultSet</code> constants: <code>ResultSet.HOLD_CURSORS_OVER_COMMIT</code>
314     *            or <code>ResultSet.CLOSE_CURSORS_AT_COMMIT</code>.
315     */
316    public PStmtKey(final String sql, final String catalog, final int resultSetType, final int resultSetConcurrency,
317            final int resultSetHoldability) {
318        this(sql, catalog, resultSetType, resultSetConcurrency, resultSetHoldability, StatementType.PREPARED_STATEMENT);
319    }
320
321    /**
322     * Constructs a key to uniquely identify a prepared statement.
323     *
324     * @param sql
325     *            The SQL statement.
326     * @param catalog
327     *            The catalog.
328     * @param resultSetType
329     *            a result set type; one of <code>ResultSet.TYPE_FORWARD_ONLY</code>,
330     *            <code>ResultSet.TYPE_SCROLL_INSENSITIVE</code>, or <code>ResultSet.TYPE_SCROLL_SENSITIVE</code>
331     * @param resultSetConcurrency
332     *            A concurrency type; one of <code>ResultSet.CONCUR_READ_ONLY</code> or
333     *            <code>ResultSet.CONCUR_UPDATABLE</code>.
334     * @param resultSetHoldability
335     *            One of the following <code>ResultSet</code> constants: <code>ResultSet.HOLD_CURSORS_OVER_COMMIT</code>
336     *            or <code>ResultSet.CLOSE_CURSORS_AT_COMMIT</code>.
337     * @param statementType
338     *            The SQL statement type, prepared or callable.
339     */
340    public PStmtKey(final String sql, final String catalog, final int resultSetType, final int resultSetConcurrency,
341            final int resultSetHoldability, final StatementType statementType) {
342        this.sql = sql;
343        this.catalog = catalog;
344        this.resultSetType = Integer.valueOf(resultSetType);
345        this.resultSetConcurrency = Integer.valueOf(resultSetConcurrency);
346        this.resultSetHoldability = Integer.valueOf(resultSetHoldability);
347        this.statementType = statementType;
348        this.autoGeneratedKeys = null;
349        this.columnIndexes = null;
350        this.columnNames = null;
351        // create builder
352        if (statementType == StatementType.PREPARED_STATEMENT) {
353            this.builder = new PreparedStatementWithResultSetHoldability();
354        } else if (statementType == StatementType.CALLABLE_STATEMENT) {
355            this.builder = new PreparedCallWithResultSetHoldability();
356        }
357    }
358
359    /**
360     * Gets the SQL statement.
361     *
362     * @return the SQL statement.
363     */
364    public String getSql() {
365        return sql;
366    }
367
368    /**
369     * Gets the result set type, one of <code>ResultSet.TYPE_FORWARD_ONLY</code>,
370     * <code>ResultSet.TYPE_SCROLL_INSENSITIVE</code>, or <code>ResultSet.TYPE_SCROLL_SENSITIVE</code>.
371     *
372     * @return the result set type.
373     */
374    public Integer getResultSetType() {
375        return resultSetType;
376    }
377
378    /**
379     * Gets the result set concurrency type; one of <code>ResultSet.CONCUR_READ_ONLY</code> or
380     * <code>ResultSet.CONCUR_UPDATABLE</code>.
381     *
382     * @return The result set concurrency type.
383     */
384    public Integer getResultSetConcurrency() {
385        return resultSetConcurrency;
386    }
387
388    /**
389     * Gets the result set holdability, one of the following <code>ResultSet</code> constants:
390     * <code>ResultSet.HOLD_CURSORS_OVER_COMMIT</code> or <code>ResultSet.CLOSE_CURSORS_AT_COMMIT</code>.
391     *
392     * @return The result set holdability.
393     */
394    public Integer getResultSetHoldability() {
395        return resultSetHoldability;
396    }
397
398    /**
399     * Gets a flag indicating whether auto-generated keys should be returned; one of
400     * <code>Statement.RETURN_GENERATED_KEYS</code> or <code>Statement.NO_GENERATED_KEYS</code>.
401     *
402     * @return a flag indicating whether auto-generated keys should be returned.
403     */
404    public Integer getAutoGeneratedKeys() {
405        return autoGeneratedKeys;
406    }
407
408    /**
409     * Gets an array of column indexes indicating the columns that should be returned from the inserted row or rows.
410     *
411     * @return An array of column indexes.
412     */
413    public int[] getColumnIndexes() {
414        return columnIndexes;
415    }
416
417    /**
418     * Gets an array of column names indicating the columns that should be returned from the inserted row or rows.
419     *
420     * @return An array of column names.
421     */
422    public String[] getColumnNames() {
423        return columnNames;
424    }
425
426    /**
427     * The catalog.
428     *
429     * @return The catalog.
430     */
431    public String getCatalog() {
432        return catalog;
433    }
434
435    /**
436     * The SQL statement type.
437     *
438     * @return The SQL statement type.
439     */
440    public StatementType getStmtType() {
441        return statementType;
442    }
443
444    @Override
445    public boolean equals(final Object obj) {
446        if (this == obj) {
447            return true;
448        }
449        if (obj == null) {
450            return false;
451        }
452        if (getClass() != obj.getClass()) {
453            return false;
454        }
455        final PStmtKey other = (PStmtKey) obj;
456        if (catalog == null) {
457            if (other.catalog != null) {
458                return false;
459            }
460        } else if (!catalog.equals(other.catalog)) {
461            return false;
462        }
463        if (resultSetConcurrency == null) {
464            if (other.resultSetConcurrency != null) {
465                return false;
466            }
467        } else if (!resultSetConcurrency.equals(other.resultSetConcurrency)) {
468            return false;
469        }
470        if (resultSetType == null) {
471            if (other.resultSetType != null) {
472                return false;
473            }
474        } else if (!resultSetType.equals(other.resultSetType)) {
475            return false;
476        }
477        if (resultSetHoldability == null) {
478            if (other.resultSetHoldability != null) {
479                return false;
480            }
481        } else if (!resultSetHoldability.equals(other.resultSetHoldability)) {
482            return false;
483        }
484        if (autoGeneratedKeys == null) {
485            if (other.autoGeneratedKeys != null) {
486                return false;
487            }
488        } else if (!autoGeneratedKeys.equals(other.autoGeneratedKeys)) {
489            return false;
490        }
491        if (!Arrays.equals(columnIndexes, other.columnIndexes)) {
492            return false;
493        }
494        if (!Arrays.equals(columnNames, other.columnNames)) {
495            return false;
496        }
497        if (sql == null) {
498            if (other.sql != null) {
499                return false;
500            }
501        } else if (!sql.equals(other.sql)) {
502            return false;
503        }
504        if (statementType != other.statementType) {
505            return false;
506        }
507        return true;
508    }
509
510    @Override
511    public int hashCode() {
512        final int prime = 31;
513        int result = 1;
514        result = prime * result + (catalog == null ? 0 : catalog.hashCode());
515        result = prime * result + (resultSetConcurrency == null ? 0 : resultSetConcurrency.hashCode());
516        result = prime * result + (resultSetType == null ? 0 : resultSetType.hashCode());
517        result = prime * result + (resultSetHoldability == null ? 0 : resultSetHoldability.hashCode());
518        result = prime * result + (sql == null ? 0 : sql.hashCode());
519        result = prime * result + (autoGeneratedKeys == null ? 0 : autoGeneratedKeys.hashCode());
520        result = prime * result + Arrays.hashCode(columnIndexes);
521        result = prime * result + Arrays.hashCode(columnNames);
522        result = prime * result + statementType.hashCode();
523        return result;
524    }
525
526    @Override
527    public String toString() {
528        final StringBuffer buf = new StringBuffer();
529        buf.append("PStmtKey: sql=");
530        buf.append(sql);
531        buf.append(", catalog=");
532        buf.append(catalog);
533        buf.append(", resultSetType=");
534        buf.append(resultSetType);
535        buf.append(", resultSetConcurrency=");
536        buf.append(resultSetConcurrency);
537        buf.append(", resultSetHoldability=");
538        buf.append(resultSetHoldability);
539        buf.append(", autoGeneratedKeys=");
540        buf.append(autoGeneratedKeys);
541        buf.append(", columnIndexes=");
542        buf.append(Arrays.toString(columnIndexes));
543        buf.append(", columnNames=");
544        buf.append(Arrays.toString(columnNames));
545        buf.append(", statementType=");
546        buf.append(statementType);
547        return buf.toString();
548    }
549
550    /**
551     * Creates a new Statement from the given Connection.
552     *
553     * @param connection
554     *            The Connection to use to create the statement.
555     * @return The statement.
556     * @throws SQLException
557     *             Thrown when there is a problem creating the statement.
558     */
559    public Statement createStatement(final Connection connection) throws SQLException {
560        if (builder == null) {
561            throw new IllegalStateException("Prepared statement key is invalid.");
562        }
563        return builder.createStatement(connection);
564    }
565
566    /**
567     * Interface for Prepared or Callable Statement.
568     */
569    private interface StatementBuilder {
570        public Statement createStatement(Connection connection) throws SQLException;
571    }
572
573    /**
574     * Builder for prepareStatement(String sql).
575     */
576    private class PreparedStatementSQL implements StatementBuilder {
577        @Override
578        public Statement createStatement(final Connection connection) throws SQLException {
579            final PreparedStatement statement = connection.prepareStatement(sql);
580            return statement;
581        }
582    }
583
584    /**
585     * Builder for prepareStatement(String sql, int autoGeneratedKeys).
586     */
587    private class PreparedStatementWithAutoGeneratedKeys implements StatementBuilder {
588        @Override
589        public Statement createStatement(final Connection connection) throws SQLException {
590            final PreparedStatement statement = connection.prepareStatement(sql, autoGeneratedKeys.intValue());
591            return statement;
592        }
593    }
594
595    /**
596     * Builder for prepareStatement(String sql, int[] columnIndexes).
597     */
598    private class PreparedStatementWithColumnIndexes implements StatementBuilder {
599        @Override
600        public Statement createStatement(final Connection connection) throws SQLException {
601            final PreparedStatement statement = connection.prepareStatement(sql, columnIndexes);
602            return statement;
603        }
604    }
605
606    /**
607     * Builder for prepareStatement(String sql, int resultSetType, int resultSetConcurrency).
608     */
609    private class PreparedStatementWithResultSetConcurrency implements StatementBuilder {
610        @Override
611        public Statement createStatement(final Connection connection) throws SQLException {
612            final PreparedStatement statement = connection.prepareStatement(sql, resultSetType.intValue(),
613                    resultSetConcurrency.intValue());
614            return statement;
615        }
616    }
617
618    /**
619     * Builder for prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability).
620     */
621    private class PreparedStatementWithResultSetHoldability implements StatementBuilder {
622        @Override
623        public Statement createStatement(final Connection connection) throws SQLException {
624            final PreparedStatement statement = connection.prepareStatement(sql, resultSetType.intValue(),
625                    resultSetConcurrency.intValue(), resultSetHoldability.intValue());
626            return statement;
627        }
628    }
629
630    /**
631     * Builder for prepareStatement(String sql, String[] columnNames).
632     */
633    private class PreparedStatementWithColumnNames implements StatementBuilder {
634        @Override
635        public Statement createStatement(final Connection connection) throws SQLException {
636            final PreparedStatement statement = connection.prepareStatement(sql, columnNames);
637            return statement;
638        }
639    }
640
641    /**
642     * Builder for prepareCall(String sql).
643     */
644    private class PreparedCallSQL implements StatementBuilder {
645        @Override
646        public Statement createStatement(final Connection connection) throws SQLException {
647            final PreparedStatement statement = connection.prepareCall(sql);
648            return statement;
649        }
650    }
651
652    /**
653     * Builder for prepareCall(String sql, int resultSetType, int resultSetConcurrency).
654     */
655    private class PreparedCallWithResultSetConcurrency implements StatementBuilder {
656        @Override
657        public Statement createStatement(final Connection connection) throws SQLException {
658            final PreparedStatement statement = connection.prepareCall(sql, resultSetType.intValue(),
659                    resultSetConcurrency.intValue());
660            return statement;
661        }
662    }
663
664    /**
665     * Builder for prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability).
666     */
667    private class PreparedCallWithResultSetHoldability implements StatementBuilder {
668        @Override
669        public Statement createStatement(final Connection connection) throws SQLException {
670            final PreparedStatement statement = connection.prepareCall(sql, resultSetType.intValue(),
671                    resultSetConcurrency.intValue(), resultSetHoldability.intValue());
672            return statement;
673        }
674    }
675}