66
77import static org .assertj .core .api .Assertions .assertThat ;
88import static org .assertj .core .api .Assertions .assertThatThrownBy ;
9+ import static org .junit .jupiter .api .Assertions .assertFalse ;
10+ import static org .junit .jupiter .api .Assertions .assertThrows ;
11+ import static org .junit .jupiter .api .Assertions .assertTrue ;
912
13+ import com .salesforce .datacloud .jdbc .core .MetadataSchemas ;
1014import com .salesforce .datacloud .jdbc .protocol .data .ColumnMetadata ;
1115import com .salesforce .datacloud .jdbc .protocol .data .HyperType ;
16+ import java .sql .ResultSet ;
17+ import java .sql .ResultSetMetaData ;
18+ import java .sql .SQLException ;
1219import java .util .Arrays ;
1320import java .util .Collections ;
1421import java .util .List ;
22+ import lombok .SneakyThrows ;
1523import lombok .val ;
24+ import org .junit .jupiter .api .BeforeEach ;
1625import org .junit .jupiter .api .Test ;
1726
1827/**
19- * Pin the {@link MetadataResultSets#of(List, List)} arity contract: rows must match the schema
20- * column count, otherwise a short row would silently produce trailing Arrow-null cells (almost
21- * always a caller bug). A {@code null} row is allowed and is interpreted as an all-nulls row,
22- * matching the legacy {@code coerceRows} convention.
28+ * Tests for {@link MetadataResultSets}. Two slices:
29+ * <ul>
30+ * <li><b>Arity contract</b> — rows must match the schema column count; null rows are allowed
31+ * as the all-nulls shape (matching the legacy {@code coerceRows} convention).
32+ * <li><b>JDBC ResultSet shape</b> — empty metadata result sets expose the standard JDBC shape
33+ * (row=0, closeable, forward-only, holdability, etc.).
34+ * </ul>
2335 */
2436class MetadataResultSetsTest {
2537
@@ -28,6 +40,8 @@ class MetadataResultSetsTest {
2840 new ColumnMetadata ("b" , HyperType .int32 (true )),
2941 new ColumnMetadata ("c" , HyperType .bool (true )));
3042
43+ // --- Arity contract ---
44+
3145 @ Test
3246 void shortRowRejected () {
3347 val rows = Collections .singletonList (Arrays .<Object >asList ("only-one" ));
@@ -74,4 +88,89 @@ void emptyRowsAccepted() throws Exception {
7488 assertThat (rs .next ()).isFalse ();
7589 }
7690 }
91+
92+ // --- JDBC ResultSet shape on an empty metadata result set ---
93+
94+ private ResultSet emptyMetadataResultSet ;
95+
96+ @ BeforeEach
97+ public void initEmptyMetadataResultSet () throws SQLException {
98+ emptyMetadataResultSet = MetadataResultSets .empty (MetadataSchemas .COLUMNS );
99+ }
100+
101+ @ Test
102+ void getRow () throws SQLException {
103+ assertThat (emptyMetadataResultSet .getRow ()).isEqualTo (0 );
104+
105+ emptyMetadataResultSet .close ();
106+ assertThrows (SQLException .class , () -> emptyMetadataResultSet .next ());
107+ }
108+
109+ @ Test
110+ void next () throws SQLException {
111+ emptyMetadataResultSet .close ();
112+ assertThrows (SQLException .class , () -> emptyMetadataResultSet .next ());
113+ }
114+
115+ @ Test
116+ void isClosed () throws SQLException {
117+ assertFalse (emptyMetadataResultSet .isClosed ());
118+ emptyMetadataResultSet .close ();
119+ assertTrue (emptyMetadataResultSet .isClosed ());
120+ }
121+
122+ @ Test
123+ void getStatement () throws SQLException {
124+ assertThat (emptyMetadataResultSet .getStatement ()).isNull ();
125+ }
126+
127+ @ Test
128+ void unwrap () {
129+ assertThrows (SQLException .class , () -> emptyMetadataResultSet .unwrap (ResultSetMetaData .class ));
130+ }
131+
132+ @ Test
133+ void isWrapperFor () throws SQLException {
134+ // StreamingResultSet implements DataCloudResultSet / ResultSet; it is not a wrapper for
135+ // arbitrary unrelated types.
136+ assertThat (emptyMetadataResultSet .isWrapperFor (ResultSetMetaData .class )).isFalse ();
137+ }
138+
139+ @ Test
140+ void getHoldability () throws SQLException {
141+ assertThat (emptyMetadataResultSet .getHoldability ()).isEqualTo (ResultSet .HOLD_CURSORS_OVER_COMMIT );
142+ }
143+
144+ @ Test
145+ void getFetchSize () throws SQLException {
146+ assertThat (emptyMetadataResultSet .getFetchSize ()).isEqualTo (0 );
147+ }
148+
149+ @ Test
150+ void setFetchSize () throws SQLException {
151+ // StreamingResultSet controls its own fetch size and ignores caller-supplied hints.
152+ emptyMetadataResultSet .setFetchSize (0 );
153+ }
154+
155+ @ SneakyThrows
156+ @ Test
157+ void getWarnings () {
158+ assertThat ((Iterable <? extends Throwable >) emptyMetadataResultSet .getWarnings ())
159+ .isNull ();
160+ }
161+
162+ @ Test
163+ void getConcurrency () throws SQLException {
164+ assertThat (emptyMetadataResultSet .getConcurrency ()).isEqualTo (ResultSet .CONCUR_READ_ONLY );
165+ }
166+
167+ @ Test
168+ void getType () throws SQLException {
169+ assertThat (emptyMetadataResultSet .getType ()).isEqualTo (ResultSet .TYPE_FORWARD_ONLY );
170+ }
171+
172+ @ Test
173+ void getFetchDirection () throws SQLException {
174+ assertThat (emptyMetadataResultSet .getFetchDirection ()).isEqualTo (ResultSet .FETCH_FORWARD );
175+ }
77176}
0 commit comments