| Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
| SingleColumnRowMapper |
|
| 6.428571428571429;6.429 |
| 1 | package liquibase.executor.jvm; | |
| 2 | ||
| 3 | import java.math.BigDecimal; | |
| 4 | import java.sql.Blob; | |
| 5 | import java.sql.Clob; | |
| 6 | import java.sql.ResultSet; | |
| 7 | import java.sql.ResultSetMetaData; | |
| 8 | import java.sql.SQLException; | |
| 9 | ||
| 10 | import liquibase.util.JdbcUtils; | |
| 11 | import liquibase.util.NumberUtils; | |
| 12 | ||
| 13 | /** | |
| 14 | * RowMapper implementation that converts a single column into a single result value per row. Expects to work on a | |
| 15 | * ResultSet that just contains a single column. | |
| 16 | * <p/> | |
| 17 | * <p> | |
| 18 | * The type of the result value for each row can be specified. The value for the single column will be extracted from | |
| 19 | * the ResultSet and converted into the specified target type. | |
| 20 | * | |
| 21 | * @author Spring Framework | |
| 22 | */ | |
| 23 | class SingleColumnRowMapper implements RowMapper { | |
| 24 | ||
| 25 | private Class requiredType; | |
| 26 | ||
| 27 | /** | |
| 28 | * Create a new SingleColumnRowMapper. | |
| 29 | * | |
| 30 | * @see #setRequiredType | |
| 31 | */ | |
| 32 | 0 | public SingleColumnRowMapper() { |
| 33 | 0 | } |
| 34 | ||
| 35 | /** | |
| 36 | * Create a new SingleColumnRowMapper. | |
| 37 | * | |
| 38 | * @param requiredType | |
| 39 | * the type that each result object is expected to match | |
| 40 | */ | |
| 41 | 0 | public SingleColumnRowMapper(Class requiredType) { |
| 42 | 0 | this.requiredType = requiredType; |
| 43 | 0 | } |
| 44 | ||
| 45 | /** | |
| 46 | * Set the type that each result object is expected to match. | |
| 47 | * <p> | |
| 48 | * If not specified, the column value will be exposed as returned by the JDBC driver. | |
| 49 | */ | |
| 50 | public void setRequiredType(Class requiredType) { | |
| 51 | 0 | this.requiredType = requiredType; |
| 52 | 0 | } |
| 53 | ||
| 54 | /** | |
| 55 | * Extract a value for the single column in the current row. | |
| 56 | * <p> | |
| 57 | * Validates that there is only one column selected, then delegates to <code>getColumnValue()</code> and also | |
| 58 | * <code>convertValueToRequiredType</code>, if necessary. | |
| 59 | * | |
| 60 | * @see java.sql.ResultSetMetaData#getColumnCount() | |
| 61 | * @see #getColumnValue(java.sql.ResultSet,int,Class) | |
| 62 | * @see #convertValueToRequiredType(Object,Class) | |
| 63 | */ | |
| 64 | @Override | |
| 65 | public Object mapRow(ResultSet rs, int rowNum) throws SQLException { | |
| 66 | // Validate column count. | |
| 67 | 0 | ResultSetMetaData rsmd = rs.getMetaData(); |
| 68 | 0 | int nrOfColumns = rsmd.getColumnCount(); |
| 69 | 0 | if (nrOfColumns != 1) { |
| 70 | 0 | throw new SQLException("Returned too many rows"); |
| 71 | } | |
| 72 | ||
| 73 | // Extract column value from JDBC ResultSet | |
| 74 | 0 | Object result = getColumnValue(rs, 1, this.requiredType); |
| 75 | 0 | if (result != null && this.requiredType != null && !this.requiredType.isInstance(result)) { |
| 76 | // Extracted value does not match already: try to convert it. | |
| 77 | try { | |
| 78 | 0 | return convertValueToRequiredType(result, this.requiredType); |
| 79 | 0 | } catch (IllegalArgumentException ex) { |
| 80 | 0 | throw new SQLException("Type mismatch affecting row number " + rowNum + " and column type '" |
| 81 | + rsmd.getColumnTypeName(1) + "': " + ex.getMessage()); | |
| 82 | } | |
| 83 | } | |
| 84 | 0 | return result; |
| 85 | } | |
| 86 | ||
| 87 | /** | |
| 88 | * Retrieve a JDBC object value for the specified column. | |
| 89 | * <p> | |
| 90 | * The default implementation calls <code>ResultSet.getString(index)</code> etc for all standard value types | |
| 91 | * (String, Boolean, number types, date types, etc). It calls <code>ResultSet.getObject(index)</code> else. | |
| 92 | * <p> | |
| 93 | * If no required type has been specified, this method delegates to <code>getColumnValue(rs, index)</code>, which | |
| 94 | * basically calls <code>ResultSet.getObject(index)</code> but applies some additional default conversion to | |
| 95 | * appropriate value types. | |
| 96 | * <p> | |
| 97 | * Explicit extraction of a String is necessary to properly extract an Oracle RAW value as a String, for example. | |
| 98 | * For the other given types, it is also recommendable to extract the desired types explicitly, to let the JDBC | |
| 99 | * driver perform appropriate (potentially database-specific) conversion. | |
| 100 | * | |
| 101 | * @param rs | |
| 102 | * is the ResultSet holding the data | |
| 103 | * @param index | |
| 104 | * is the column index | |
| 105 | * @param requiredType | |
| 106 | * the type that each result object is expected to match (or <code>null</code> if none specified) | |
| 107 | * @return the Object value | |
| 108 | * @see java.sql.ResultSet#getString(int) | |
| 109 | * @see java.sql.ResultSet#getObject(int) | |
| 110 | * @see #getColumnValue(java.sql.ResultSet,int) | |
| 111 | */ | |
| 112 | protected Object getColumnValue(ResultSet rs, int index, Class requiredType) throws SQLException { | |
| 113 | 0 | if (requiredType != null) { |
| 114 | Object value; | |
| 115 | 0 | boolean wasNullCheck = false; |
| 116 | ||
| 117 | // Explicitly extract typed value, as far as possible. | |
| 118 | 0 | if (String.class.equals(requiredType)) { |
| 119 | 0 | value = rs.getString(index); |
| 120 | 0 | } else if (Boolean.class.equals(requiredType)) { |
| 121 | 0 | value = (rs.getBoolean(index) ? Boolean.TRUE : Boolean.FALSE); |
| 122 | 0 | wasNullCheck = true; |
| 123 | 0 | } else if (Byte.class.equals(requiredType)) { |
| 124 | 0 | value = rs.getByte(index); |
| 125 | 0 | wasNullCheck = true; |
| 126 | 0 | } else if (Short.class.equals(requiredType)) { |
| 127 | 0 | value = rs.getShort(index); |
| 128 | 0 | wasNullCheck = true; |
| 129 | 0 | } else if (Integer.class.equals(requiredType)) { |
| 130 | 0 | value = rs.getInt(index); |
| 131 | 0 | wasNullCheck = true; |
| 132 | 0 | } else if (Long.class.equals(requiredType)) { |
| 133 | 0 | value = rs.getLong(index); |
| 134 | 0 | wasNullCheck = true; |
| 135 | 0 | } else if (Float.class.equals(requiredType)) { |
| 136 | 0 | value = rs.getFloat(index); |
| 137 | 0 | wasNullCheck = true; |
| 138 | 0 | } else if (Double.class.equals(requiredType) || Number.class.equals(requiredType)) { |
| 139 | 0 | value = rs.getDouble(index); |
| 140 | 0 | wasNullCheck = true; |
| 141 | 0 | } else if (byte[].class.equals(requiredType)) { |
| 142 | 0 | value = rs.getBytes(index); |
| 143 | 0 | } else if (java.sql.Date.class.equals(requiredType)) { |
| 144 | 0 | value = rs.getDate(index); |
| 145 | 0 | } else if (java.sql.Time.class.equals(requiredType)) { |
| 146 | 0 | value = rs.getTime(index); |
| 147 | 0 | } else if (java.sql.Timestamp.class.equals(requiredType) || java.util.Date.class.equals(requiredType)) { |
| 148 | 0 | value = rs.getTimestamp(index); |
| 149 | 0 | } else if (BigDecimal.class.equals(requiredType)) { |
| 150 | 0 | value = rs.getBigDecimal(index); |
| 151 | 0 | } else if (Blob.class.equals(requiredType)) { |
| 152 | 0 | value = rs.getBlob(index); |
| 153 | 0 | } else if (Clob.class.equals(requiredType)) { |
| 154 | 0 | value = rs.getClob(index); |
| 155 | } else { | |
| 156 | // Some unknown type desired -> rely on getObject. | |
| 157 | 0 | value = rs.getObject(index); |
| 158 | } | |
| 159 | ||
| 160 | // Perform was-null check if demanded (for results that the | |
| 161 | // JDBC driver returns as primitives). | |
| 162 | 0 | if (wasNullCheck && value != null && rs.wasNull()) { |
| 163 | 0 | value = null; |
| 164 | } | |
| 165 | 0 | return value; |
| 166 | } else { | |
| 167 | // No required type specified -> perform default extraction. | |
| 168 | 0 | return getColumnValue(rs, index); |
| 169 | } | |
| 170 | } | |
| 171 | ||
| 172 | /** | |
| 173 | * Retrieve a JDBC object value for the specified column, using the most appropriate value type. Called if no | |
| 174 | * required type has been specified. | |
| 175 | * <p> | |
| 176 | * The default implementation delegates to <code>JdbcUtils.getResultSetValue()</code>, which uses the | |
| 177 | * <code>ResultSet.getObject(index)</code> method. Additionally, it includes a "hack" to get around Oracle returning | |
| 178 | * a non-standard object for their TIMESTAMP datatype. See the <code>JdbcUtils#getResultSetValue()</code> javadoc | |
| 179 | * for details. | |
| 180 | * | |
| 181 | * @param rs | |
| 182 | * is the ResultSet holding the data | |
| 183 | * @param index | |
| 184 | * is the column index | |
| 185 | * @return the Object value | |
| 186 | */ | |
| 187 | protected Object getColumnValue(ResultSet rs, int index) throws SQLException { | |
| 188 | 0 | return JdbcUtils.getResultSetValue(rs, index); |
| 189 | } | |
| 190 | ||
| 191 | /** | |
| 192 | * Convert the given column value to the specified required type. Only called if the extracted column value does not | |
| 193 | * match already. | |
| 194 | * <p> | |
| 195 | * If the required type is String, the value will simply get stringified via <code>toString()</code>. In case of a | |
| 196 | * Number, the value will be converted into a Number, either through number conversion or through String parsing | |
| 197 | * (depending on the value type). | |
| 198 | * | |
| 199 | * @param value | |
| 200 | * the column value as extracted from <code>getColumnValue()</code> (never <code>null</code>) | |
| 201 | * @param requiredType | |
| 202 | * the type that each result object is expected to match (never <code>null</code>) | |
| 203 | * @return the converted value | |
| 204 | * @see #getColumnValue(java.sql.ResultSet,int,Class) | |
| 205 | */ | |
| 206 | protected Object convertValueToRequiredType(Object value, Class requiredType) { | |
| 207 | 0 | if (String.class.equals(this.requiredType)) { |
| 208 | 0 | return value.toString(); |
| 209 | 0 | } else if (Number.class.isAssignableFrom(this.requiredType)) { |
| 210 | 0 | if (value instanceof Number) { |
| 211 | // Convert original Number to target Number class. | |
| 212 | 0 | return NumberUtils.convertNumberToTargetClass(((Number) value), this.requiredType); |
| 213 | } else { | |
| 214 | // Convert stringified value to target Number class. | |
| 215 | 0 | return NumberUtils.parseNumber(value.toString(), this.requiredType); |
| 216 | } | |
| 217 | } else { | |
| 218 | 0 | throw new IllegalArgumentException("Value [" + value + "] is of type [" + value.getClass().getName() |
| 219 | + "] and cannot be converted to required type [" + this.requiredType.getName() + "]"); | |
| 220 | } | |
| 221 | } | |
| 222 | } |