Skip to content

Commit 47e289e

Browse files
authored
Fix wasNull state across toString() calls in RowStruct (#3276)
This PR fixes the bug related to `wasNull` state not being preserved across `toString()` calls in a `RowStruct`. For instance, If a struct is defined as `{a -> null, b -> 1}`, `getObject(1)` should set the `wasNull` to `true` which should be uneffected if a call to `toString()` is made. Also adds tests around the same.
1 parent c8f9de1 commit 47e289e

File tree

3 files changed

+79
-1
lines changed

3 files changed

+79
-1
lines changed

fdb-relational-api/src/main/java/com/apple/foundationdb/relational/api/RelationalStruct.java

+4
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import com.apple.foundationdb.relational.api.exceptions.ErrorCode;
2424
import com.apple.foundationdb.relational.api.exceptions.InvalidColumnReferenceException;
2525

26+
import java.sql.ResultSet;
2627
import java.sql.SQLException;
2728
import java.sql.Struct;
2829
import java.sql.Wrapper;
@@ -80,6 +81,9 @@ public interface RelationalStruct extends Struct, Wrapper {
8081

8182
UUID getUUID(String fieldName) throws SQLException;
8283

84+
/**
85+
* Reports whether the last column read had a value of SQL NULL. See {@link ResultSet#wasNull()}
86+
*/
8387
boolean wasNull() throws SQLException;
8488

8589
@Override

fdb-relational-core/src/main/java/com/apple/foundationdb/relational/api/RowStruct.java

+6-1
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,9 @@ private int getZeroBasedPosition(int oneBasedPosition) {
335335

336336
@Override
337337
public String toString() {
338+
// We want to preserve the last wasNull state and not let it get effected by toString() since this too uses
339+
// the getObject() call to fetch values.
340+
final var preservedWasNullValue = wasNull();
338341
final var builder = new StringBuilder();
339342
try {
340343
final var colCount = metaData.getColumnCount();
@@ -351,8 +354,10 @@ public String toString() {
351354
builder.append("} ");
352355
} catch (SQLException e) {
353356
throw new UncheckedRelationalException(new RelationalException(e));
357+
} finally {
358+
// restore wasNull
359+
wasNull = preservedWasNullValue;
354360
}
355-
356361
return builder.toString();
357362
}
358363
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* RowStructTest.java
3+
*
4+
* This source file is part of the FoundationDB open source project
5+
*
6+
* Copyright 2015-2025 Apple Inc. and the FoundationDB project authors
7+
*
8+
* Licensed under the Apache License, Version 2.0 (the "License");
9+
* you may not use this file except in compliance with the License.
10+
* You may obtain a copy of the License at
11+
*
12+
* http://www.apache.org/licenses/LICENSE-2.0
13+
*
14+
* Unless required by applicable law or agreed to in writing, software
15+
* distributed under the License is distributed on an "AS IS" BASIS,
16+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17+
* See the License for the specific language governing permissions and
18+
* limitations under the License.
19+
*/
20+
21+
package com.apple.foundationdb.relational.recordlayer;
22+
23+
import com.apple.foundationdb.relational.api.FieldDescription;
24+
import com.apple.foundationdb.relational.api.ImmutableRowStruct;
25+
import com.apple.foundationdb.relational.api.MutableRowStruct;
26+
import com.apple.foundationdb.relational.api.RelationalStructMetaData;
27+
import com.apple.foundationdb.relational.api.RowStruct;
28+
import org.junit.jupiter.api.Assertions;
29+
import org.junit.jupiter.params.ParameterizedTest;
30+
import org.junit.jupiter.params.provider.ValueSource;
31+
32+
import java.sql.DatabaseMetaData;
33+
import java.sql.SQLException;
34+
import java.sql.Types;
35+
36+
public class RowStructTest {
37+
38+
@ParameterizedTest
39+
@ValueSource(booleans = {true, false})
40+
void wasNullWorks(boolean mutable) throws SQLException {
41+
final var struct = createStruct(mutable);
42+
struct.getObject(1);
43+
Assertions.assertTrue(struct.wasNull());
44+
}
45+
46+
@ParameterizedTest
47+
@ValueSource(booleans = {true, false})
48+
void wasNullWorksWithToString(boolean mutable) throws SQLException {
49+
final var struct = createStruct(mutable);
50+
struct.getObject(1);
51+
Assertions.assertFalse(struct.toString().isEmpty());
52+
Assertions.assertTrue(struct.wasNull());
53+
}
54+
55+
private static RowStruct createStruct(boolean mutable) {
56+
final var metadata = new RelationalStructMetaData(
57+
FieldDescription.primitive("fInt", Types.INTEGER, DatabaseMetaData.columnNullable),
58+
FieldDescription.primitive("fLong", Types.BIGINT, DatabaseMetaData.columnNullable)
59+
);
60+
final var row = new ArrayRow(null, 1L);
61+
if (mutable) {
62+
final var toReturn = new MutableRowStruct(metadata);
63+
toReturn.setRow(row);
64+
return toReturn;
65+
} else {
66+
return new ImmutableRowStruct(row, metadata);
67+
}
68+
}
69+
}

0 commit comments

Comments
 (0)