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 | } |