Skip to content

Commit a99ab70

Browse files
committed
Merge branch 'main' into PECOBLR-840-GetColumns
Merged the latest changes from main
2 parents 53ddb7f + 30c2476 commit a99ab70

3 files changed

Lines changed: 131 additions & 6 deletions

File tree

NEXT_CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,6 @@
2020
- Fixed `ResultSet.getString` for Boolean columns in Metadata result set.
2121
- Fixed volume operations not completing unless the ResultSet is fully iterated.
2222
- Fixed `connection.getMetadata().getColumns()` to return the correct SQL data type code for complex type columns.
23+
- Fixed a bug in the JDBC driver's metadata parsing for nested decimal fields within struct types.
2324
---
2425
*Note: When making changes, please add your change under the appropriate section with a brief description.*

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

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -94,25 +94,33 @@ public static String parseMapMetadata(String metadata) {
9494
* @return an array of field definitions in the STRUCT
9595
*/
9696
private static String[] splitFields(String metadata) {
97-
int depth = 0;
97+
int angleBracketDepth = 0;
98+
int parenDepth = 0;
9899
StringBuilder currentField = new StringBuilder();
99100
java.util.List<String> fields = new java.util.ArrayList<>();
100101

101102
for (char ch : metadata.toCharArray()) {
102103
if (ch == '<') {
103-
depth++;
104+
angleBracketDepth++;
104105
} else if (ch == '>') {
105-
depth--;
106+
angleBracketDepth--;
107+
} else if (ch == '(') {
108+
parenDepth++;
109+
} else if (ch == ')') {
110+
parenDepth--;
106111
}
107112

108-
if (ch == ',' && depth == 0) {
109-
fields.add(currentField.toString().trim());
113+
// Only split on commas when we're at the top level (both depths are 0)
114+
if (ch == ',' && angleBracketDepth == 0 && parenDepth == 0) {
115+
String field = currentField.toString().trim();
116+
fields.add(field);
110117
currentField.setLength(0);
111118
} else {
112119
currentField.append(ch);
113120
}
114121
}
115-
fields.add(currentField.toString().trim());
122+
String finalField = currentField.toString().trim();
123+
fields.add(finalField);
116124
return fields.toArray(new String[0]);
117125
}
118126

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

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,4 +292,120 @@ public void testParseMapMetadata_EmptyMap() {
292292
exception.getMessage().contains("Invalid MAP metadata"),
293293
"Exception message should indicate invalid MAP metadata.");
294294
}
295+
296+
/**
297+
* Test parsing of STRUCT metadata with DECIMAL precision and scale - regression test for
298+
* parentheses handling.
299+
*/
300+
@Test
301+
@DisplayName("parseStructMetadata with DECIMAL precision and scale")
302+
public void testParseStructMetadata_WithDecimalPrecisionScale() {
303+
String metadata = "STRUCT<name:STRING, amount:DECIMAL(18,2)>";
304+
Map<String, String> expected = new LinkedHashMap<>();
305+
expected.put("name", "STRING");
306+
expected.put("amount", "DECIMAL(18,2)");
307+
308+
Map<String, String> actual = MetadataParser.parseStructMetadata(metadata);
309+
assertEquals(
310+
expected,
311+
actual,
312+
"Parsed struct metadata with DECIMAL precision and scale should handle parentheses correctly.");
313+
}
314+
315+
/**
316+
* Test parsing of STRUCT metadata with multiple DECIMAL fields with different precision/scale.
317+
*/
318+
@Test
319+
@DisplayName("parseStructMetadata with multiple DECIMAL fields")
320+
public void testParseStructMetadata_MultipleDecimalFields() {
321+
String metadata = "STRUCT<id:INT, price:DECIMAL(10,2), amount:DECIMAL(18,4), name:STRING>";
322+
Map<String, String> expected = new LinkedHashMap<>();
323+
expected.put("id", "INT");
324+
expected.put("price", "DECIMAL(10,2)");
325+
expected.put("amount", "DECIMAL(18,4)");
326+
expected.put("name", "STRING");
327+
328+
Map<String, String> actual = MetadataParser.parseStructMetadata(metadata);
329+
assertEquals(
330+
expected,
331+
actual,
332+
"Parsed struct metadata with multiple DECIMAL fields should handle all parentheses correctly.");
333+
}
334+
335+
/** Test parsing of complex nested STRUCT with DECIMAL fields - comprehensive regression test. */
336+
@Test
337+
@DisplayName("parseStructMetadata with nested STRUCT containing DECIMAL")
338+
public void testParseStructMetadata_NestedStructWithDecimal() {
339+
String metadata =
340+
"STRUCT<id:INT, financial:STRUCT<balance:DECIMAL(15,2), credit:DECIMAL(10,2)>, active:BOOLEAN>";
341+
Map<String, String> expected = new LinkedHashMap<>();
342+
expected.put("id", "INT");
343+
expected.put("financial", "STRUCT<balance:DECIMAL(15,2), credit:DECIMAL(10,2)>");
344+
expected.put("active", "BOOLEAN");
345+
346+
Map<String, String> actual = MetadataParser.parseStructMetadata(metadata);
347+
assertEquals(
348+
expected,
349+
actual,
350+
"Parsed struct metadata with nested STRUCT containing DECIMAL should preserve all type information.");
351+
}
352+
353+
/** Test parsing of deeply nested STRUCT with multiple DECIMAL fields at different levels. */
354+
@Test
355+
@DisplayName("parseStructMetadata with deeply nested STRUCT and DECIMAL fields")
356+
public void testParseStructMetadata_DeeplyNestedStructWithDecimals() {
357+
String metadata =
358+
"STRUCT<id:INT, account:STRUCT<balance:DECIMAL(18,4), details:STRUCT<fee:DECIMAL(5,2), rate:DECIMAL(10,6)>>, status:STRING>";
359+
Map<String, String> expected = new LinkedHashMap<>();
360+
expected.put("id", "INT");
361+
expected.put(
362+
"account",
363+
"STRUCT<balance:DECIMAL(18,4), details:STRUCT<fee:DECIMAL(5,2), rate:DECIMAL(10,6)>>");
364+
expected.put("status", "STRING");
365+
366+
Map<String, String> actual = MetadataParser.parseStructMetadata(metadata);
367+
assertEquals(
368+
expected,
369+
actual,
370+
"Parsed struct metadata with deeply nested STRUCT and DECIMAL fields should handle all levels correctly.");
371+
}
372+
373+
/** Test parsing of STRUCT with mixed complex types and DECIMAL fields. */
374+
@Test
375+
@DisplayName("parseStructMetadata with mixed complex types and DECIMAL")
376+
public void testParseStructMetadata_MixedComplexTypesWithDecimal() {
377+
String metadata =
378+
"STRUCT<id:INT, prices:ARRAY<DECIMAL(12,2)>, accounts:MAP<STRING, STRUCT<balance:DECIMAL(15,2)>>, summary:STRUCT<total:DECIMAL(20,4), count:INT>>";
379+
Map<String, String> expected = new LinkedHashMap<>();
380+
expected.put("id", "INT");
381+
expected.put("prices", "ARRAY<DECIMAL(12,2)>");
382+
expected.put("accounts", "MAP<STRING, STRUCT<balance:DECIMAL(15,2)>>");
383+
expected.put("summary", "STRUCT<total:DECIMAL(20,4), count:INT>");
384+
385+
Map<String, String> actual = MetadataParser.parseStructMetadata(metadata);
386+
assertEquals(
387+
expected,
388+
actual,
389+
"Parsed struct metadata with mixed complex types and DECIMAL fields should handle all combinations correctly.");
390+
}
391+
392+
/** Test parsing of STRUCT with other parenthesized types to ensure fix applies broadly. */
393+
@Test
394+
@DisplayName("parseStructMetadata with various parenthesized types")
395+
public void testParseStructMetadata_VariousParenthesizedTypes() {
396+
String metadata =
397+
"STRUCT<id:INT, name:STRING, amount:DECIMAL(38,18), price:DECIMAL(10,2), active:BOOLEAN>";
398+
Map<String, String> expected = new LinkedHashMap<>();
399+
expected.put("id", "INT");
400+
expected.put("name", "STRING");
401+
expected.put("amount", "DECIMAL(38,18)");
402+
expected.put("price", "DECIMAL(10,2)");
403+
expected.put("active", "BOOLEAN");
404+
405+
Map<String, String> actual = MetadataParser.parseStructMetadata(metadata);
406+
assertEquals(
407+
expected,
408+
actual,
409+
"Parsed struct metadata with various parenthesized types should handle all type parameters correctly.");
410+
}
295411
}

0 commit comments

Comments
 (0)