diff --git a/src/main/java/org/opensearch/jdbc/ConnectionImpl.java b/src/main/java/org/opensearch/jdbc/ConnectionImpl.java
index e6a8587..6ad7e47 100644
--- a/src/main/java/org/opensearch/jdbc/ConnectionImpl.java
+++ b/src/main/java/org/opensearch/jdbc/ConnectionImpl.java
@@ -436,7 +436,7 @@ public Array createArrayOf(String typeName, Object[] elements) throws SQLExcepti
@Override
public Struct createStruct(String typeName, Object[] attributes) throws SQLException {
- throw new SQLFeatureNotSupportedException("Struct is not supported.");
+ return new StructImpl(typeName, attributes);
}
@Override
diff --git a/src/main/java/org/opensearch/jdbc/StructImpl.java b/src/main/java/org/opensearch/jdbc/StructImpl.java
new file mode 100644
index 0000000..6ae2831
--- /dev/null
+++ b/src/main/java/org/opensearch/jdbc/StructImpl.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+
+package org.opensearch.jdbc;
+
+import java.sql.SQLException;
+import java.sql.Struct;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.List;
+
+
+/**
+ * This class implements the {@link java.sql.Struct} interface.
+ *
+ * {@code StructImpl} provides a simple implementation of a struct data type.
+ *
+ */
+public class StructImpl implements Struct {
+ private final String typeName;
+ private final Object[] attributes;
+
+ /**
+ * Constructs a new {@code StructImpl} object with the specified parameter values.
+ *
+ * @param typeName the SQL type name of the struct
+ * @param attributes the attributes of the struct, each attribute is a {@code Map.Entry}(key-value pair)
+ */
+ public StructImpl(String typeName, Object[] attributes) {
+ this.typeName = typeName;
+ this.attributes = attributes;
+ }
+
+ /**
+ * Returns the SQL type name of the struct.
+ *
+ * @return the SQL type name of the struct
+ * @throws SQLException if a database access error occurs
+ */
+ @Override
+ public String getSQLTypeName() throws SQLException {
+ return this.typeName;
+ }
+
+ /**
+ * Returns an array containing the attributes of the struct.
+ *
+ * @return an array containing the attribute values of the struct
+ * @throws SQLException if a database access error occurs
+ */
+ @Override
+ public Object[] getAttributes() throws SQLException {
+ return attributes;
+ }
+
+ /**
+ * @throws java.lang.UnsupportedOperationException because functionality is not supported yet
+ */
+ @Override
+ public Object[] getAttributes(Map> map) throws SQLException {
+ throw new java.lang.UnsupportedOperationException("Not supported yet.");
+ }
+
+ /**
+ * Compares this StructImpl object with the specified object for equality.
+ *
+ *
+ * Two StructImpl objects are considered equal if they have the same typeName, same number of attributes,
+ * and contain the same attributes.
+ *
+ *
+ * @param obj the object to compare with this StructImpl object for equality.
+ * @return {@code true} if the specified object is equal to this StructImpl object, {@code false} otherwise.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof Struct)) {
+ return false;
+ }
+ if (obj == this) {
+ return true;
+ }
+ Struct other = (Struct) obj;
+ try {
+ if (!typeName.equals(other.getSQLTypeName()) || attributes.length != other.getAttributes().length) {
+ return false;
+ }
+ List otherAttributes = Arrays.asList(other.getAttributes());
+ return otherAttributes.containsAll(Arrays.asList(attributes));
+ }
+ catch (SQLException e) {
+ return false;
+ }
+ }
+}
diff --git a/src/main/java/org/opensearch/jdbc/types/BaseTypeConverter.java b/src/main/java/org/opensearch/jdbc/types/BaseTypeConverter.java
index bbc114a..7e286ed 100644
--- a/src/main/java/org/opensearch/jdbc/types/BaseTypeConverter.java
+++ b/src/main/java/org/opensearch/jdbc/types/BaseTypeConverter.java
@@ -8,6 +8,7 @@
import java.sql.Date;
import java.sql.SQLException;
+import java.sql.Struct;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.HashMap;
@@ -35,6 +36,8 @@ public abstract class BaseTypeConverter implements TypeConverter {
typeHandlerMap.put(Date.class, DateType.INSTANCE);
typeHandlerMap.put(Time.class, TimeType.INSTANCE);
+ typeHandlerMap.put(Struct.class, StructType.INSTANCE);
+
}
@Override
diff --git a/src/main/java/org/opensearch/jdbc/types/OpenSearchType.java b/src/main/java/org/opensearch/jdbc/types/OpenSearchType.java
index e7329a7..36f6623 100644
--- a/src/main/java/org/opensearch/jdbc/types/OpenSearchType.java
+++ b/src/main/java/org/opensearch/jdbc/types/OpenSearchType.java
@@ -90,6 +90,7 @@ public enum OpenSearchType {
jdbcTypeToOpenSearchTypeMap.put(JDBCType.TIME, TIME);
jdbcTypeToOpenSearchTypeMap.put(JDBCType.DATE, DATE);
jdbcTypeToOpenSearchTypeMap.put(JDBCType.VARBINARY, BINARY);
+ jdbcTypeToOpenSearchTypeMap.put(JDBCType.STRUCT, OBJECT);
}
/**
diff --git a/src/main/java/org/opensearch/jdbc/types/StructType.java b/src/main/java/org/opensearch/jdbc/types/StructType.java
new file mode 100644
index 0000000..faee44b
--- /dev/null
+++ b/src/main/java/org/opensearch/jdbc/types/StructType.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.opensearch.jdbc.types;
+
+import java.sql.Struct;
+import java.util.Map;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.LinkedHashMap;
+import java.util.Collections;
+
+
+
+import org.opensearch.jdbc.StructImpl;
+
+public class StructType implements TypeHelper {
+
+ public static final StructType INSTANCE = new StructType();
+
+ private StructType() {
+
+ }
+
+ @Override
+ public String getTypeName() {
+ return "Struct";
+ }
+
+ @Override
+ public Struct fromValue(Object value, Map conversionParams) {
+ if (value == null || !(value instanceof Map, ?>)) {
+ return null;
+ }
+ Map structKeyValues = (Map) value;
+ return new StructImpl(getTypeName(), structKeyValues.entrySet().toArray());
+ }
+ }
diff --git a/src/main/java/org/opensearch/jdbc/types/TypeConverters.java b/src/main/java/org/opensearch/jdbc/types/TypeConverters.java
index 189e970..847e264 100644
--- a/src/main/java/org/opensearch/jdbc/types/TypeConverters.java
+++ b/src/main/java/org/opensearch/jdbc/types/TypeConverters.java
@@ -9,6 +9,7 @@
import java.sql.Date;
import java.sql.JDBCType;
import java.sql.SQLException;
+import java.sql.Struct;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.Arrays;
@@ -56,12 +57,34 @@ public class TypeConverters {
tcMap.put(JDBCType.BINARY, new BinaryTypeConverter());
tcMap.put(JDBCType.NULL, new NullTypeConverter());
+
+ // Adding Struct Support
+ tcMap.put(JDBCType.STRUCT, new StructTypeConverter());
}
public static TypeConverter getInstance(JDBCType jdbcType) {
return tcMap.get(jdbcType);
}
+ public static class StructTypeConverter extends BaseTypeConverter {
+
+ private static final Set supportedJavaClasses = Collections.singleton(Struct.class);
+
+ StructTypeConverter() {
+
+ }
+
+ @Override
+ public Class getDefaultJavaClass() {
+ return Struct.class;
+ }
+
+ @Override
+ public Set getSupportedJavaClasses() {
+ return supportedJavaClasses;
+ }
+ }
+
public static class TimestampTypeConverter extends BaseTypeConverter {
private static final Set supportedJavaClasses = Collections.unmodifiableSet(
diff --git a/src/test/java/org/opensearch/jdbc/ResultSetTests.java b/src/test/java/org/opensearch/jdbc/ResultSetTests.java
index 0ef1753..60733db 100644
--- a/src/test/java/org/opensearch/jdbc/ResultSetTests.java
+++ b/src/test/java/org/opensearch/jdbc/ResultSetTests.java
@@ -13,6 +13,7 @@
import org.opensearch.jdbc.test.TestResources;
import org.opensearch.jdbc.test.mocks.MockOpenSearch;
import org.opensearch.jdbc.types.OpenSearchType;
+import org.opensearch.jdbc.types.StructType;
import org.opensearch.jdbc.test.PerTestWireMockServerExtension;
import org.opensearch.jdbc.test.WireMockServerHelpers;
import org.opensearch.jdbc.test.mocks.MockResultSet;
@@ -32,6 +33,8 @@
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
+import java.util.HashMap;
+import java.util.Map;
import java.util.stream.Stream;
import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
@@ -176,6 +179,18 @@ void testNullableFieldsQuery(WireMockServer mockServer) throws SQLException, IOE
Statement st = con.createStatement();
ResultSet rs = assertDoesNotThrow(() -> st.executeQuery(queryMock.getSql()));
+ Map attributes = new HashMap() {{
+ put("attribute1", "value1");
+ put("attribute2", 2);
+ put("attribute3", 15.0);
+ }};
+
+ Map nestedAttributes = new HashMap() {{
+ put("struct", attributes);
+ put("string", "hello");
+ put("int", 1);
+ }};
+
assertNotNull(rs);
MockResultSetMetaData mockResultSetMetaData = MockResultSetMetaData.builder()
@@ -191,6 +206,7 @@ void testNullableFieldsQuery(WireMockServer mockServer) throws SQLException, IOE
.column("testKeyword", OpenSearchType.KEYWORD)
.column("testText", OpenSearchType.TEXT)
.column("testDouble", OpenSearchType.DOUBLE)
+ .column("testStruct", OpenSearchType.OBJECT)
.build();
MockResultSetRows mockResultSetRows = MockResultSetRows.builder()
@@ -207,6 +223,21 @@ void testNullableFieldsQuery(WireMockServer mockServer) throws SQLException, IOE
.column("Test String", false)
.column("document3", false)
.column((double) 0, true)
+ .column(StructType.INSTANCE.fromValue(attributes, null), false)
+ .row()
+ .column(true, false)
+ .column("1", false)
+ .column((byte) 126, false)
+ .column((float) 0, true)
+ .column((long) 32000320003200030L, false)
+ .column((short) 29000, false)
+ .column((float) 0, true)
+ .column(null, true)
+ .column((double) 0, true)
+ .column(null, true)
+ .column(null, true)
+ .column((double) 22.312423148903218, false)
+ .column(null, true)
.row()
.column(true, false)
.column("1", false)
@@ -220,6 +251,7 @@ void testNullableFieldsQuery(WireMockServer mockServer) throws SQLException, IOE
.column(null, true)
.column(null, true)
.column((double) 22.312423148903218, false)
+ .column(StructType.INSTANCE.fromValue(nestedAttributes, null), false)
.build();
MockResultSet mockResultSet = new MockResultSet(mockResultSetMetaData, mockResultSetRows);
diff --git a/src/test/java/org/opensearch/jdbc/types/StructTypeTests.java b/src/test/java/org/opensearch/jdbc/types/StructTypeTests.java
new file mode 100644
index 0000000..6280358
--- /dev/null
+++ b/src/test/java/org/opensearch/jdbc/types/StructTypeTests.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+
+ package org.opensearch.jdbc.types;
+
+ import static org.junit.jupiter.api.Assertions.*;
+ import org.junit.jupiter.api.Test;
+ import org.opensearch.jdbc.types.StructType;
+ import org.opensearch.jdbc.StructImpl;
+
+ import java.sql.Struct;
+ import java.sql.SQLException;
+ import java.util.Arrays;
+ import java.util.Map;
+ import java.util.HashMap;
+
+ public class StructTypeTests {
+
+ @Test
+ public void testStructTypeFromValue() throws SQLException {
+ Map attributes = new HashMap() {{
+ put("attribute1", "value1");
+ put("attribute2", 2);
+ put("attribute3", 15.0);
+ }};
+
+ Map attributes2 = new HashMap() {{
+ put("attribute1", "value1");
+ put("attribute2", 2);
+ put("attribute3", 15.0);
+ put("attribute4", "value4");
+ }};
+
+ Struct actualStruct = StructType.INSTANCE.fromValue(attributes, null);
+ Struct actualStruct2 = StructType.INSTANCE.fromValue(attributes2, null);
+
+ assertTrue(Arrays.equals(actualStruct.getAttributes(), attributes.entrySet().toArray()));
+ assertEquals(actualStruct.getAttributes().length, 3);
+ assertEquals(actualStruct2.getAttributes().length, 4);
+ assertEquals(actualStruct, new StructImpl(StructType.INSTANCE.getTypeName(), attributes.entrySet().toArray()));
+ assertNotEquals(actualStruct, actualStruct2);
+
+ Map nestedAttributes = new HashMap() {{
+ put("struct", attributes);
+ put("string", "hello");
+ put("int", 1);
+ }};
+
+ Struct actualNestedStruct = StructType.INSTANCE.fromValue(nestedAttributes, null);
+ assertTrue(Arrays.equals(actualNestedStruct.getAttributes(), nestedAttributes.entrySet().toArray()));
+ assertEquals(actualNestedStruct, new StructImpl(StructType.INSTANCE.getTypeName(), nestedAttributes.entrySet().toArray()));
+ assertNotEquals(actualStruct, actualNestedStruct);
+ }
+ }
diff --git a/src/test/resources/mock/protocol/json/queryresponse_nullablefields.json b/src/test/resources/mock/protocol/json/queryresponse_nullablefields.json
index 1068e6c..932ab8d 100644
--- a/src/test/resources/mock/protocol/json/queryresponse_nullablefields.json
+++ b/src/test/resources/mock/protocol/json/queryresponse_nullablefields.json
@@ -47,6 +47,10 @@
{
"name": "testDouble",
"type": "double"
+ },
+ {
+ "name": "testStruct",
+ "type": "object"
}
],
"total": 2,
@@ -63,6 +67,26 @@
24.324234543532153,
"Test String",
"document3",
+ null,
+ {
+ "attribute1": "value1",
+ "attribute2": 2,
+ "attribute3": 15.0
+ }
+ ],
+ [
+ true,
+ "1",
+ 126,
+ null,
+ 32000320003200030,
+ 29000,
+ null,
+ null,
+ null,
+ null,
+ null,
+ 22.312423148903218,
null
],
[
@@ -77,9 +101,14 @@
null,
null,
null,
- 22.312423148903218
+ 22.312423148903218,
+ {
+ "struct": {"attribute1": "value1", "attribute2": 2, "attribute3": 15.0},
+ "string": "hello",
+ "int": 1
+ }
]
],
"size": 2,
"status": 200
-}
\ No newline at end of file
+}