Skip to content

Commit cdf35c3

Browse files
authored
Enhance ComplexDataTypeParser with formatting methods for complex types when param is disabled (string handling) (#839)
* Enhance ComplexDataTypeParser with formatting methods for complex types - Added `formatComplexTypeString` method to standardize JSON string representation for complex types when support is disabled. - Introduced `formatMapString` method to convert map JSON strings into a consistent {key:value} format. - Updated `ArrowStreamResult` to utilize the new formatting methods for handling complex types when complex datatype support is disabled. * unit test * Refactor formatMapString method in ComplexDataTypeParser to improve map metadata handling - Changed the initialization of the key-value array in the formatMapString method to use a dynamic array instead of a fixed size. - Removed commented-out code for clarity and improved readability. * fix * addresses comments
1 parent 58b850e commit cdf35c3

File tree

3 files changed

+144
-2
lines changed

3 files changed

+144
-2
lines changed

src/main/java/com/databricks/jdbc/api/impl/ComplexDataTypeParser.java

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,4 +198,97 @@ private Object convertPrimitive(String text, String type) {
198198
return text;
199199
}
200200
}
201+
202+
/**
203+
* Formats a complex type JSON string into a consistent string format. This is primarily used when
204+
* complex datatype support is disabled.
205+
*
206+
* @param jsonString The JSON string representation of the complex type
207+
* @param complexType The type of complex data (MAP, ARRAY, STRUCT)
208+
* @param typeMetadata The metadata for the type (e.g., "MAP<INT,INT>")
209+
* @return A consistently formatted string representation
210+
*/
211+
public String formatComplexTypeString(
212+
String jsonString, String complexType, String typeMetadata) {
213+
if (jsonString == null || complexType == null) {
214+
return jsonString;
215+
}
216+
217+
try {
218+
if (complexType.equals(DatabricksTypeUtil.MAP)) {
219+
return formatMapString(jsonString, typeMetadata);
220+
}
221+
} catch (Exception e) {
222+
LOGGER.warn("Failed to format complex type representation: {}", e.getMessage());
223+
}
224+
225+
return jsonString;
226+
}
227+
228+
/**
229+
* Formats a map JSON string into the standard {key:value} format.
230+
*
231+
* @param jsonString The JSON string representation of the map
232+
* @param mapMetadata The metadata for the map type (e.g., "MAP<INT,INT>")
233+
* @return A map string in the format {key:value,key:value}
234+
*/
235+
public String formatMapString(String jsonString, String mapMetadata) {
236+
try {
237+
JsonNode node = JsonUtil.getMapper().readTree(jsonString);
238+
if (node.isArray() && node.size() > 0 && node.get(0).has("key")) {
239+
String[] kv = new String[] {"STRING", "STRING"};
240+
if (mapMetadata != null && mapMetadata.startsWith(DatabricksTypeUtil.MAP)) {
241+
kv = MetadataParser.parseMapMetadata(mapMetadata).split(",", 2);
242+
}
243+
244+
String keyType = kv[0].trim();
245+
String valueType = kv[1].trim();
246+
boolean isStringKey = keyType.equalsIgnoreCase(DatabricksTypeUtil.STRING);
247+
boolean isStringValue = valueType.equalsIgnoreCase(DatabricksTypeUtil.STRING);
248+
249+
StringBuilder result = new StringBuilder("{");
250+
251+
for (int i = 0; i < node.size(); i++) {
252+
JsonNode entry = node.get(i);
253+
JsonNode keyNode = entry.get("key");
254+
JsonNode valueNode = entry.get("value");
255+
256+
// Throw error if keyNode is null
257+
if (keyNode == null || keyNode.isNull()) {
258+
throw new DatabricksParsingException(
259+
"Map entry found with null key in JSON: " + entry.toString(),
260+
DatabricksDriverErrorCode.JSON_PARSING_ERROR);
261+
}
262+
263+
if (i > 0) {
264+
result.append(",");
265+
}
266+
267+
if (isStringKey) {
268+
result.append("\"").append(keyNode.asText()).append("\"");
269+
} else {
270+
result.append(keyNode.asText());
271+
}
272+
273+
result.append(":");
274+
275+
// Handle null valueNode
276+
if (valueNode == null || valueNode.isNull()) {
277+
result.append("null");
278+
} else if (isStringValue) {
279+
result.append("\"").append(valueNode.asText()).append("\"");
280+
} else {
281+
result.append(valueNode.asText());
282+
}
283+
}
284+
285+
result.append("}");
286+
return result.toString();
287+
}
288+
} catch (Exception e) {
289+
LOGGER.warn("Failed to format map representation: {}", e.getMessage());
290+
}
291+
292+
return jsonString;
293+
}
201294
}

src/main/java/com/databricks/jdbc/api/impl/arrow/ArrowStreamResult.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import static com.databricks.jdbc.common.util.DatabricksThriftUtil.getTypeFromTypeDesc;
44

5+
import com.databricks.jdbc.api.impl.ComplexDataTypeParser;
56
import com.databricks.jdbc.api.impl.IExecutionResult;
67
import com.databricks.jdbc.api.internal.IDatabricksSession;
78
import com.databricks.jdbc.api.internal.IDatabricksStatementInternal;
@@ -138,8 +139,12 @@ public Object getObject(int columnIndex) throws DatabricksSQLException {
138139
this.session.getConnectionContext().isComplexDatatypeSupportEnabled();
139140
if (!isComplexDatatypeSupportEnabled && isComplexType(requiredType)) {
140141
LOGGER.debug("Complex datatype support is disabled, converting complex type to STRING");
141-
requiredType = ColumnInfoTypeName.STRING;
142-
arrowMetadata = "STRING";
142+
143+
Object result =
144+
chunkIterator.getColumnObjectAtCurrentRow(
145+
columnIndex, ColumnInfoTypeName.STRING, "STRING");
146+
ComplexDataTypeParser parser = new ComplexDataTypeParser();
147+
return parser.formatComplexTypeString(result.toString(), requiredType.name(), arrowMetadata);
143148
}
144149

145150
return chunkIterator.getColumnObjectAtCurrentRow(columnIndex, requiredType, arrowMetadata);

src/test/java/com/databricks/jdbc/api/impl/ComplexDataTypeParserTest.java

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,4 +129,48 @@ void testComplexPrimitiveConversions() throws DatabricksParsingException {
129129
fail("Should not throw: " + e.getMessage());
130130
}
131131
}
132+
133+
@Test
134+
void testFormatComplexTypeString_withMapType() {
135+
String jsonString = "[{\"key\":1,\"value\":2},{\"key\":3,\"value\":4}]";
136+
String expected = "{1:2,3:4}";
137+
138+
String result = parser.formatComplexTypeString(jsonString, "MAP", "MAP<INT,INT>");
139+
assertEquals(expected, result);
140+
}
141+
142+
@Test
143+
void testFormatComplexTypeString_withNonMapType() {
144+
String jsonString = "[1,2,3]";
145+
146+
String result = parser.formatComplexTypeString(jsonString, "ARRAY", "ARRAY<INT>");
147+
assertEquals(jsonString, result);
148+
}
149+
150+
@Test
151+
void testFormatMapString_withIntKeyAndValue() throws DatabricksParsingException {
152+
String jsonString = "[{\"key\":1,\"value\":2},{\"key\":3,\"value\":4}]";
153+
String expected = "{1:2,3:4}";
154+
155+
String result = parser.formatMapString(jsonString, "MAP<INT,INT>");
156+
assertEquals(expected, result);
157+
}
158+
159+
@Test
160+
void testFormatMapString_withStringKeyAndValue() throws DatabricksParsingException {
161+
String jsonString = "[{\"key\":\"a\",\"value\":\"b\"},{\"key\":\"c\",\"value\":\"d\"}]";
162+
String expected = "{\"a\":\"b\",\"c\":\"d\"}";
163+
164+
String result = parser.formatMapString(jsonString, "MAP<STRING,STRING>");
165+
assertEquals(expected, result);
166+
}
167+
168+
@Test
169+
void testFormatMapString_withMixedTypes() throws DatabricksParsingException {
170+
String jsonString = "[{\"key\":\"a\",\"value\":100},{\"key\":\"b\",\"value\":200}]";
171+
String expected = "{\"a\":100,\"b\":200}";
172+
173+
String result = parser.formatMapString(jsonString, "MAP<STRING,INT>");
174+
assertEquals(expected, result);
175+
}
132176
}

0 commit comments

Comments
 (0)