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 }