Skip to content

Commit cea5f48

Browse files
author
Anthony Yeh
committed
java: Fix handling of SQL NULL in Row class.
Previously, getters that convert to primitive types (like `long getLong()`) would throw NullPointerException if the value was actually SQL NULL. To better match the expectations set by the JDBC ResultSet interface, which we intend to mimic, we now will instead return 0 when asked to convert SQL NULL to a primitive type. To distinguish between 0 and SQL NULL, the JDBC ResultSet interface provides two mechanisms, which we now support as well: 1. Call `wasNull()` after `getLong()` returns 0. 2. Call `getObject(..., Long.class)` instead of `getLong()` to get a (possibly-null) `Long` instead of a `long`.
1 parent 10afe90 commit cea5f48

2 files changed

Lines changed: 218 additions & 15 deletions

File tree

  • java/client/src

java/client/src/main/java/com/youtube/vitess/client/cursor/Row.java

Lines changed: 143 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ public class Row {
2929
private FieldMap fieldMap;
3030
private List<ByteString> values;
3131
private Query.Row rawRow;
32+
private boolean lastGetWasNull;
3233

3334
/**
3435
* Construct a Row from {@link com.youtube.vitess.proto.Query.Row}
@@ -101,96 +102,223 @@ public Object getObject(int columnIndex) throws SQLException {
101102
if (columnIndex >= values.size()) {
102103
throw new SQLDataException("invalid columnIndex: " + columnIndex);
103104
}
104-
return convertFieldValue(fieldMap.get(columnIndex), values.get(columnIndex));
105+
Object value = convertFieldValue(fieldMap.get(columnIndex), values.get(columnIndex));
106+
lastGetWasNull = (value == null);
107+
return value;
105108
}
106109

110+
/**
111+
* Returns the column value, or 0 if the value is SQL NULL.
112+
*
113+
* <p>To distinguish between 0 and SQL NULL, use either {@link #wasNull()}
114+
* or {@link #getObject(String,Class)}.
115+
*/
107116
public int getInt(String columnLabel) throws SQLException {
108117
return getInt(findColumn(columnLabel));
109118
}
110119

120+
/**
121+
* Returns the column value, or 0 if the value is SQL NULL.
122+
*
123+
* <p>To distinguish between 0 and SQL NULL, use either {@link #wasNull()}
124+
* or {@link #getObject(int,Class)}.
125+
*/
111126
public int getInt(int columnIndex) throws SQLException {
112-
return (Integer) getAndCheckType(columnIndex, Integer.class);
127+
Integer value = getObject(columnIndex, Integer.class);
128+
return value == null ? 0 : value;
113129
}
114130

115131
public UnsignedLong getULong(String columnLabel) throws SQLException {
116132
return getULong(findColumn(columnLabel));
117133
}
118134

119135
public UnsignedLong getULong(int columnIndex) throws SQLException {
120-
return (UnsignedLong) getAndCheckType(columnIndex, UnsignedLong.class);
136+
return getObject(columnIndex, UnsignedLong.class);
121137
}
122138

123139
public String getString(String columnLabel) throws SQLException {
124140
return getString(findColumn(columnLabel));
125141
}
126142

127143
public String getString(int columnIndex) throws SQLException {
128-
return (String) getAndCheckType(columnIndex, String.class);
144+
return getObject(columnIndex, String.class);
129145
}
130146

147+
/**
148+
* Returns the column value, or 0 if the value is SQL NULL.
149+
*
150+
* <p>To distinguish between 0 and SQL NULL, use either {@link #wasNull()}
151+
* or {@link #getObject(String,Class)}.
152+
*/
131153
public long getLong(String columnLabel) throws SQLException {
132154
return getLong(findColumn(columnLabel));
133155
}
134156

157+
/**
158+
* Returns the column value, or 0 if the value is SQL NULL.
159+
*
160+
* <p>To distinguish between 0 and SQL NULL, use either {@link #wasNull()}
161+
* or {@link #getObject(int,Class)}.
162+
*/
135163
public long getLong(int columnIndex) throws SQLException {
136-
return (Long) getAndCheckType(columnIndex, Long.class);
164+
Long value = getObject(columnIndex, Long.class);
165+
return value == null ? 0 : value;
137166
}
138167

168+
/**
169+
* Returns the column value, or 0 if the value is SQL NULL.
170+
*
171+
* <p>To distinguish between 0 and SQL NULL, use either {@link #wasNull()}
172+
* or {@link #getObject(String,Class)}.
173+
*/
139174
public double getDouble(String columnLabel) throws SQLException {
140175
return getDouble(findColumn(columnLabel));
141176
}
142177

178+
/**
179+
* Returns the column value, or 0 if the value is SQL NULL.
180+
*
181+
* <p>To distinguish between 0 and SQL NULL, use either {@link #wasNull()}
182+
* or {@link #getObject(int,Class)}.
183+
*/
143184
public double getDouble(int columnIndex) throws SQLException {
144-
return (Double) getAndCheckType(columnIndex, Double.class);
185+
Double value = getObject(columnIndex, Double.class);
186+
return value == null ? 0 : value;
145187
}
146188

189+
/**
190+
* Returns the column value, or 0 if the value is SQL NULL.
191+
*
192+
* <p>To distinguish between 0 and SQL NULL, use either {@link #wasNull()}
193+
* or {@link #getObject(String,Class)}.
194+
*/
147195
public float getFloat(String columnLabel) throws SQLException {
148196
return getFloat(findColumn(columnLabel));
149197
}
150198

199+
/**
200+
* Returns the column value, or 0 if the value is SQL NULL.
201+
*
202+
* <p>To distinguish between 0 and SQL NULL, use either {@link #wasNull()}
203+
* or {@link #getObject(int,Class)}.
204+
*/
151205
public float getFloat(int columnIndex) throws SQLException {
152-
return (Float) getAndCheckType(columnIndex, Float.class);
206+
Float value = getObject(columnIndex, Float.class);
207+
return value == null ? 0 : value;
153208
}
154209

155210
public DateTime getDateTime(String columnLabel) throws SQLException {
156211
return getDateTime(findColumn(columnLabel));
157212
}
158213

159214
public DateTime getDateTime(int columnIndex) throws SQLException {
160-
return (DateTime) getAndCheckType(columnIndex, DateTime.class);
215+
return getObject(columnIndex, DateTime.class);
161216
}
162217

163218
public byte[] getBytes(String columnLabel) throws SQLException {
164219
return getBytes(findColumn(columnLabel));
165220
}
166221

167222
public byte[] getBytes(int columnIndex) throws SQLException {
168-
return (byte[]) getAndCheckType(columnIndex, byte[].class);
223+
return getObject(columnIndex, byte[].class);
169224
}
170225

171226
public BigDecimal getBigDecimal(String columnLabel) throws SQLException {
172227
return getBigDecimal(findColumn(columnLabel));
173228
}
174229

175230
public BigDecimal getBigDecimal(int columnIndex) throws SQLException {
176-
return (BigDecimal) getAndCheckType(columnIndex, BigDecimal.class);
231+
return getObject(columnIndex, BigDecimal.class);
177232
}
178233

234+
/**
235+
* Returns the column value, or 0 if the value is SQL NULL.
236+
*
237+
* <p>To distinguish between 0 and SQL NULL, use either {@link #wasNull()}
238+
* or {@link #getObject(String,Class)}.
239+
*/
179240
public short getShort(String columnLabel) throws SQLException {
180241
return getShort(findColumn(columnLabel));
181242
}
182243

244+
/**
245+
* Returns the column value, or 0 if the value is SQL NULL.
246+
*
247+
* <p>To distinguish between 0 and SQL NULL, use either {@link #wasNull()}
248+
* or {@link #getObject(int,Class)}.
249+
*/
183250
public short getShort(int columnIndex) throws SQLException {
184-
return (Short) getAndCheckType(columnIndex, Short.class);
251+
Short value = getObject(columnIndex, Short.class);
252+
return value == null ? 0 : value;
185253
}
186254

187-
private Object getAndCheckType(int columnIndex, Class<?> cls) throws SQLException {
255+
/**
256+
* Returns the column value, cast to the specified type.
257+
*
258+
* <p>This can be used as an alternative to getters that return primitive
259+
* types, if you need to distinguish between 0 and SQL NULL. For example:
260+
*
261+
* <blockquote><pre>
262+
* Long value = row.getObject(0, Long.class);
263+
* if (value == null) {
264+
* // The value was SQL NULL, not 0.
265+
* }
266+
* </pre></blockquote>
267+
*
268+
* @throws SQLDataException if the type doesn't match the actual value.
269+
*/
270+
@SuppressWarnings("unchecked") // by runtime check
271+
public <T> T getObject(int columnIndex, Class<T> type) throws SQLException {
188272
Object o = getObject(columnIndex);
189-
if (o != null && !cls.isInstance(o)) {
273+
if (o != null && !type.isInstance(o)) {
190274
throw new SQLDataException(
191-
"type mismatch, expected:" + cls.getName() + ", actual: " + o.getClass().getName());
275+
"type mismatch, expected:" + type.getName() + ", actual: " + o.getClass().getName());
192276
}
193-
return o;
277+
return (T) o;
278+
}
279+
280+
/**
281+
* Returns the column value, cast to the specified type.
282+
*
283+
* <p>This can be used as an alternative to getters that return primitive
284+
* types, if you need to distinguish between 0 and SQL NULL. For example:
285+
*
286+
* <blockquote><pre>
287+
* Long value = row.getObject("col0", Long.class);
288+
* if (value == null) {
289+
* // The value was SQL NULL, not 0.
290+
* }
291+
* </pre></blockquote>
292+
*
293+
* @throws SQLDataException if the type doesn't match the actual value.
294+
*/
295+
public <T> T getObject(String columnLabel, Class<T> type) throws SQLException {
296+
return getObject(findColumn(columnLabel), type);
297+
}
298+
299+
/**
300+
* Reports whether the last column read had a value of SQL NULL.
301+
*
302+
* <p>Getter methods that return primitive types, such as {@link #getLong(int)},
303+
* will return 0 if the value is SQL NULL. To distinguish 0 from SQL NULL,
304+
* you can call {@code wasNull()} immediately after retrieving the value.
305+
*
306+
* <p>Note that this is not thread-safe: the value of {@code wasNull()} is only
307+
* trustworthy if there are no concurrent calls on this {@code Row} between the
308+
* call to {@code get*()} and the call to {@code wasNull()}.
309+
*
310+
* <p>As an alternative to {@code wasNull()}, you can use {@link #getObject(int,Class)}
311+
* (e.g. {@code getObject(0, Long.class)} instead of {@code getLong(0)}) to get a
312+
* wrapped {@code Long} value that will be {@code null} if the column value was SQL NULL.
313+
*
314+
* @throws SQLException
315+
*/
316+
public boolean wasNull() throws SQLException {
317+
// Note: lastGetWasNull is currently set only in getObject(int),
318+
// which means this relies on the fact that all other get*() methods
319+
// eventually call into getObject(int). The unit tests help to ensure
320+
// this by checking wasNull() after each get*().
321+
return lastGetWasNull;
194322
}
195323

196324
private static Object convertFieldValue(Field field, ByteString value) throws SQLException {

0 commit comments

Comments
 (0)