Skip to content

Commit c0ce14f

Browse files
authored
Adding Struct Support (#73)
* Adding Struct Support Signed-off-by: Andrey Kuzin <[email protected]> * Added unit tests for Struct type Signed-off-by: Andrey Kuzin <[email protected]> * Minor refactoring changes Signed-off-by: Andrey Kuzin <[email protected]> * Added Java docs for StructImpl Signed-off-by: Andrey Kuzin <[email protected]> --------- Signed-off-by: Andrey Kuzin <[email protected]>
1 parent 17db733 commit c0ce14f

File tree

9 files changed

+286
-3
lines changed

9 files changed

+286
-3
lines changed

src/main/java/org/opensearch/jdbc/ConnectionImpl.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -436,7 +436,7 @@ public Array createArrayOf(String typeName, Object[] elements) throws SQLExcepti
436436

437437
@Override
438438
public Struct createStruct(String typeName, Object[] attributes) throws SQLException {
439-
throw new SQLFeatureNotSupportedException("Struct is not supported.");
439+
return new StructImpl(typeName, attributes);
440440
}
441441

442442
@Override
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
7+
package org.opensearch.jdbc;
8+
9+
import java.sql.SQLException;
10+
import java.sql.Struct;
11+
import java.util.Arrays;
12+
import java.util.Map;
13+
import java.util.List;
14+
15+
16+
/**
17+
* This class implements the {@link java.sql.Struct} interface.
18+
* <p>
19+
* {@code StructImpl} provides a simple implementation of a struct data type.
20+
* </p>
21+
*/
22+
public class StructImpl implements Struct {
23+
private final String typeName;
24+
private final Object[] attributes;
25+
26+
/**
27+
* Constructs a new {@code StructImpl} object with the specified parameter values.
28+
*
29+
* @param typeName the SQL type name of the struct
30+
* @param attributes the attributes of the struct, each attribute is a {@code Map.Entry<K, V>}(key-value pair)
31+
*/
32+
public StructImpl(String typeName, Object[] attributes) {
33+
this.typeName = typeName;
34+
this.attributes = attributes;
35+
}
36+
37+
/**
38+
* Returns the SQL type name of the struct.
39+
*
40+
* @return the SQL type name of the struct
41+
* @throws SQLException if a database access error occurs
42+
*/
43+
@Override
44+
public String getSQLTypeName() throws SQLException {
45+
return this.typeName;
46+
}
47+
48+
/**
49+
* Returns an array containing the attributes of the struct.
50+
*
51+
* @return an array containing the attribute values of the struct
52+
* @throws SQLException if a database access error occurs
53+
*/
54+
@Override
55+
public Object[] getAttributes() throws SQLException {
56+
return attributes;
57+
}
58+
59+
/**
60+
* @throws java.lang.UnsupportedOperationException because functionality is not supported yet
61+
*/
62+
@Override
63+
public Object[] getAttributes(Map<String,Class<?>> map) throws SQLException {
64+
throw new java.lang.UnsupportedOperationException("Not supported yet.");
65+
}
66+
67+
/**
68+
* Compares this StructImpl object with the specified object for equality.
69+
*
70+
* <p>
71+
* Two StructImpl objects are considered equal if they have the same typeName, same number of attributes,
72+
* and contain the same attributes.
73+
* </p>
74+
*
75+
* @param obj the object to compare with this StructImpl object for equality.
76+
* @return {@code true} if the specified object is equal to this StructImpl object, {@code false} otherwise.
77+
*/
78+
@Override
79+
public boolean equals(Object obj) {
80+
if (!(obj instanceof Struct)) {
81+
return false;
82+
}
83+
if (obj == this) {
84+
return true;
85+
}
86+
Struct other = (Struct) obj;
87+
try {
88+
if (!typeName.equals(other.getSQLTypeName()) || attributes.length != other.getAttributes().length) {
89+
return false;
90+
}
91+
List otherAttributes = Arrays.asList(other.getAttributes());
92+
return otherAttributes.containsAll(Arrays.asList(attributes));
93+
}
94+
catch (SQLException e) {
95+
return false;
96+
}
97+
}
98+
}

src/main/java/org/opensearch/jdbc/types/BaseTypeConverter.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import java.sql.Date;
1010
import java.sql.SQLException;
11+
import java.sql.Struct;
1112
import java.sql.Time;
1213
import java.sql.Timestamp;
1314
import java.util.HashMap;
@@ -35,6 +36,8 @@ public abstract class BaseTypeConverter implements TypeConverter {
3536
typeHandlerMap.put(Date.class, DateType.INSTANCE);
3637
typeHandlerMap.put(Time.class, TimeType.INSTANCE);
3738

39+
typeHandlerMap.put(Struct.class, StructType.INSTANCE);
40+
3841
}
3942

4043
@Override

src/main/java/org/opensearch/jdbc/types/OpenSearchType.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ public enum OpenSearchType {
9090
jdbcTypeToOpenSearchTypeMap.put(JDBCType.TIME, TIME);
9191
jdbcTypeToOpenSearchTypeMap.put(JDBCType.DATE, DATE);
9292
jdbcTypeToOpenSearchTypeMap.put(JDBCType.VARBINARY, BINARY);
93+
jdbcTypeToOpenSearchTypeMap.put(JDBCType.STRUCT, OBJECT);
9394
}
9495

9596
/**
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
package org.opensearch.jdbc.types;
6+
7+
import java.sql.Struct;
8+
import java.util.Map;
9+
import java.util.ArrayList;
10+
import java.util.Arrays;
11+
import java.util.List;
12+
import java.util.LinkedHashMap;
13+
import java.util.Collections;
14+
15+
16+
17+
import org.opensearch.jdbc.StructImpl;
18+
19+
public class StructType implements TypeHelper<Struct> {
20+
21+
public static final StructType INSTANCE = new StructType();
22+
23+
private StructType() {
24+
25+
}
26+
27+
@Override
28+
public String getTypeName() {
29+
return "Struct";
30+
}
31+
32+
@Override
33+
public Struct fromValue(Object value, Map<String, Object> conversionParams) {
34+
if (value == null || !(value instanceof Map<?, ?>)) {
35+
return null;
36+
}
37+
Map<String, Object> structKeyValues = (Map<String, Object>) value;
38+
return new StructImpl(getTypeName(), structKeyValues.entrySet().toArray());
39+
}
40+
}

src/main/java/org/opensearch/jdbc/types/TypeConverters.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import java.sql.Date;
1010
import java.sql.JDBCType;
1111
import java.sql.SQLException;
12+
import java.sql.Struct;
1213
import java.sql.Time;
1314
import java.sql.Timestamp;
1415
import java.util.Arrays;
@@ -56,12 +57,34 @@ public class TypeConverters {
5657
tcMap.put(JDBCType.BINARY, new BinaryTypeConverter());
5758

5859
tcMap.put(JDBCType.NULL, new NullTypeConverter());
60+
61+
// Adding Struct Support
62+
tcMap.put(JDBCType.STRUCT, new StructTypeConverter());
5963
}
6064

6165
public static TypeConverter getInstance(JDBCType jdbcType) {
6266
return tcMap.get(jdbcType);
6367
}
6468

69+
public static class StructTypeConverter extends BaseTypeConverter {
70+
71+
private static final Set<Class> supportedJavaClasses = Collections.singleton(Struct.class);
72+
73+
StructTypeConverter() {
74+
75+
}
76+
77+
@Override
78+
public Class getDefaultJavaClass() {
79+
return Struct.class;
80+
}
81+
82+
@Override
83+
public Set<Class> getSupportedJavaClasses() {
84+
return supportedJavaClasses;
85+
}
86+
}
87+
6588
public static class TimestampTypeConverter extends BaseTypeConverter {
6689

6790
private static final Set<Class> supportedJavaClasses = Collections.unmodifiableSet(

src/test/java/org/opensearch/jdbc/ResultSetTests.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import org.opensearch.jdbc.test.TestResources;
1414
import org.opensearch.jdbc.test.mocks.MockOpenSearch;
1515
import org.opensearch.jdbc.types.OpenSearchType;
16+
import org.opensearch.jdbc.types.StructType;
1617
import org.opensearch.jdbc.test.PerTestWireMockServerExtension;
1718
import org.opensearch.jdbc.test.WireMockServerHelpers;
1819
import org.opensearch.jdbc.test.mocks.MockResultSet;
@@ -32,6 +33,8 @@
3233
import java.sql.SQLException;
3334
import java.sql.Statement;
3435
import java.sql.Timestamp;
36+
import java.util.HashMap;
37+
import java.util.Map;
3538
import java.util.stream.Stream;
3639

3740
import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
@@ -176,6 +179,18 @@ void testNullableFieldsQuery(WireMockServer mockServer) throws SQLException, IOE
176179
Statement st = con.createStatement();
177180
ResultSet rs = assertDoesNotThrow(() -> st.executeQuery(queryMock.getSql()));
178181

182+
Map<String, Object> attributes = new HashMap<String, Object>() {{
183+
put("attribute1", "value1");
184+
put("attribute2", 2);
185+
put("attribute3", 15.0);
186+
}};
187+
188+
Map<String, Object> nestedAttributes = new HashMap<String, Object>() {{
189+
put("struct", attributes);
190+
put("string", "hello");
191+
put("int", 1);
192+
}};
193+
179194
assertNotNull(rs);
180195

181196
MockResultSetMetaData mockResultSetMetaData = MockResultSetMetaData.builder()
@@ -191,6 +206,7 @@ void testNullableFieldsQuery(WireMockServer mockServer) throws SQLException, IOE
191206
.column("testKeyword", OpenSearchType.KEYWORD)
192207
.column("testText", OpenSearchType.TEXT)
193208
.column("testDouble", OpenSearchType.DOUBLE)
209+
.column("testStruct", OpenSearchType.OBJECT)
194210
.build();
195211

196212
MockResultSetRows mockResultSetRows = MockResultSetRows.builder()
@@ -207,6 +223,21 @@ void testNullableFieldsQuery(WireMockServer mockServer) throws SQLException, IOE
207223
.column("Test String", false)
208224
.column("document3", false)
209225
.column((double) 0, true)
226+
.column(StructType.INSTANCE.fromValue(attributes, null), false)
227+
.row()
228+
.column(true, false)
229+
.column("1", false)
230+
.column((byte) 126, false)
231+
.column((float) 0, true)
232+
.column((long) 32000320003200030L, false)
233+
.column((short) 29000, false)
234+
.column((float) 0, true)
235+
.column(null, true)
236+
.column((double) 0, true)
237+
.column(null, true)
238+
.column(null, true)
239+
.column((double) 22.312423148903218, false)
240+
.column(null, true)
210241
.row()
211242
.column(true, false)
212243
.column("1", false)
@@ -220,6 +251,7 @@ void testNullableFieldsQuery(WireMockServer mockServer) throws SQLException, IOE
220251
.column(null, true)
221252
.column(null, true)
222253
.column((double) 22.312423148903218, false)
254+
.column(StructType.INSTANCE.fromValue(nestedAttributes, null), false)
223255
.build();
224256

225257
MockResultSet mockResultSet = new MockResultSet(mockResultSetMetaData, mockResultSetRows);
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
7+
package org.opensearch.jdbc.types;
8+
9+
import static org.junit.jupiter.api.Assertions.*;
10+
import org.junit.jupiter.api.Test;
11+
import org.opensearch.jdbc.types.StructType;
12+
import org.opensearch.jdbc.StructImpl;
13+
14+
import java.sql.Struct;
15+
import java.sql.SQLException;
16+
import java.util.Arrays;
17+
import java.util.Map;
18+
import java.util.HashMap;
19+
20+
public class StructTypeTests {
21+
22+
@Test
23+
public void testStructTypeFromValue() throws SQLException {
24+
Map<String, Object> attributes = new HashMap<String, Object>() {{
25+
put("attribute1", "value1");
26+
put("attribute2", 2);
27+
put("attribute3", 15.0);
28+
}};
29+
30+
Map<String, Object> attributes2 = new HashMap<String, Object>() {{
31+
put("attribute1", "value1");
32+
put("attribute2", 2);
33+
put("attribute3", 15.0);
34+
put("attribute4", "value4");
35+
}};
36+
37+
Struct actualStruct = StructType.INSTANCE.fromValue(attributes, null);
38+
Struct actualStruct2 = StructType.INSTANCE.fromValue(attributes2, null);
39+
40+
assertTrue(Arrays.equals(actualStruct.getAttributes(), attributes.entrySet().toArray()));
41+
assertEquals(actualStruct.getAttributes().length, 3);
42+
assertEquals(actualStruct2.getAttributes().length, 4);
43+
assertEquals(actualStruct, new StructImpl(StructType.INSTANCE.getTypeName(), attributes.entrySet().toArray()));
44+
assertNotEquals(actualStruct, actualStruct2);
45+
46+
Map<String, Object> nestedAttributes = new HashMap<String, Object>() {{
47+
put("struct", attributes);
48+
put("string", "hello");
49+
put("int", 1);
50+
}};
51+
52+
Struct actualNestedStruct = StructType.INSTANCE.fromValue(nestedAttributes, null);
53+
assertTrue(Arrays.equals(actualNestedStruct.getAttributes(), nestedAttributes.entrySet().toArray()));
54+
assertEquals(actualNestedStruct, new StructImpl(StructType.INSTANCE.getTypeName(), nestedAttributes.entrySet().toArray()));
55+
assertNotEquals(actualStruct, actualNestedStruct);
56+
}
57+
}

0 commit comments

Comments
 (0)