1 /*
2 * Copyright (c) 2012-2023, jcabi.com
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met: 1) Redistributions of source code must retain the above
8 * copyright notice, this list of conditions and the following
9 * disclaimer. 2) Redistributions in binary form must reproduce the above
10 * copyright notice, this list of conditions and the following
11 * disclaimer in the documentation and/or other materials provided
12 * with the distribution. 3) Neither the name of the jcabi.com nor
13 * the names of its contributors may be used to endorse or promote
14 * products derived from this software without specific prior written
15 * permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
19 * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
20 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
21 * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
22 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
26 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
28 * OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30 package com.jcabi.jdbc;
31
32 import java.sql.ResultSet;
33 import java.sql.SQLException;
34 import java.sql.Statement;
35 import java.util.Date;
36 import java.util.UUID;
37 import lombok.EqualsAndHashCode;
38 import lombok.RequiredArgsConstructor;
39 import lombok.ToString;
40
41 /**
42 * Outcome that returns first column in the first row.
43 *
44 * <p>Use it when you need the first column in the first row:
45 *
46 * <pre> Long id = new JdbcSession(source)
47 * .sql("SELECT id FROM user WHERE name = ?")
48 * .set("Jeff Lebowski")
49 * .select(new SingleOutcome<Long>(Long.class));</pre>
50 *
51 * <p>Supported types are: {@link String}, {@link Long}, {@link Boolean},
52 * {@link Byte}, {@link Date}, {@link UUID}, and {@link Utc}.
53 *
54 * <p>By default, the outcome throws {@link SQLException} if no records
55 * are found in the {@link ResultSet}. You can change this behavior by using
56 * a two-arguments constructor ({@code null} will be returned if
57 * {@link ResultSet} is empty):
58 *
59 * <pre> String name = new JdbcSession(source)
60 * .sql("SELECT name FROM user WHERE id = ?")
61 * .set(555)
62 * .select(new SingleOutcome<Long>(Long.class), true);
63 * if (name == null) {
64 * // such a record wasn't found in the database
65 * }</pre>
66 *
67 * @param <T> Type of items
68 * @since 0.1.8
69 */
70 @ToString
71 @EqualsAndHashCode(of = {"mapping", "silently"})
72 @RequiredArgsConstructor
73 public final class SingleOutcome<T> implements Outcome<T> {
74
75 /**
76 * The type.
77 */
78 private final Mapping<? extends T> mapping;
79
80 /**
81 * Silently return NULL if no row found.
82 */
83 private final boolean silently;
84
85 /**
86 * Public ctor.
87 *
88 * @param tpe The type to convert to
89 */
90 public SingleOutcome(final Class<T> tpe) {
91 this(tpe, false);
92 }
93
94 /**
95 * Public ctor.
96 *
97 * @param tpe The type to convert to
98 * @param slnt Silently return NULL if there is no row
99 */
100 @SuppressWarnings("PMD.ConstructorOnlyInitializesOrCallOtherConstructors")
101 public SingleOutcome(final Class<T> tpe, final boolean slnt) {
102 this(
103 tpe,
104 Outcome.DEFAULT_MAPPINGS,
105 slnt
106 );
107 }
108
109 /**
110 * Public ctor.
111 *
112 * @param tpe The type to convert to
113 * @param mps The mappings
114 * @param slnt Silently return NULL if there is no row
115 */
116 public SingleOutcome(final Class<T> tpe, final Mappings mps, final boolean slnt) {
117 this(mps.forType(tpe), slnt);
118 }
119
120 @Override
121 public T handle(final ResultSet rset, final Statement stmt)
122 throws SQLException {
123 T result = null;
124 if (rset.next()) {
125 result = this.fetch(rset);
126 } else if (!this.silently) {
127 throw new SQLException("No records found");
128 }
129 return result;
130 }
131
132 /**
133 * Fetch the value from result set.
134 *
135 * @param rset Result set
136 * @return The result
137 * @throws SQLException If some error inside
138 */
139 private T fetch(final ResultSet rset) throws SQLException {
140 return this.mapping.map(rset);
141 }
142 }