diff --git a/NEXT_CHANGELOG.md b/NEXT_CHANGELOG.md index d27a25e26..ac81de5d9 100644 --- a/NEXT_CHANGELOG.md +++ b/NEXT_CHANGELOG.md @@ -5,6 +5,7 @@ ### Added ### Updated +- `EnableGeoSpatialSupport` no longer requires `EnableComplexDatatypeSupport=1`. Geospatial types (GEOMETRY, GEOGRAPHY) can now be enabled independently of complex type support (ARRAY, MAP, STRUCT). ### Fixed diff --git a/src/main/java/com/databricks/jdbc/api/impl/DatabricksConnectionContext.java b/src/main/java/com/databricks/jdbc/api/impl/DatabricksConnectionContext.java index 9dc8d4d02..ea9e15b26 100644 --- a/src/main/java/com/databricks/jdbc/api/impl/DatabricksConnectionContext.java +++ b/src/main/java/com/databricks/jdbc/api/impl/DatabricksConnectionContext.java @@ -995,9 +995,7 @@ public boolean isComplexDatatypeSupportEnabled() { @Override public boolean isGeoSpatialSupportEnabled() { - // Geospatial support requires complex datatype support to be enabled - return isComplexDatatypeSupportEnabled() - && getParameter(DatabricksJdbcUrlParams.ENABLE_GEOSPATIAL_SUPPORT).equals("1"); + return getParameter(DatabricksJdbcUrlParams.ENABLE_GEOSPATIAL_SUPPORT).equals("1"); } @Override diff --git a/src/main/java/com/databricks/jdbc/api/impl/DatabricksResultSet.java b/src/main/java/com/databricks/jdbc/api/impl/DatabricksResultSet.java index f9a73208e..a83956528 100644 --- a/src/main/java/com/databricks/jdbc/api/impl/DatabricksResultSet.java +++ b/src/main/java/com/databricks/jdbc/api/impl/DatabricksResultSet.java @@ -526,6 +526,10 @@ public Object getObject(int columnIndex) throws SQLException { } int columnType = resultSetMetaData.getColumnType(columnIndex); String columnTypeName = resultSetMetaData.getColumnTypeName(columnIndex); + // Geospatial types: handle independently of complex datatype flag + if (isGeospatialType(columnTypeName)) { + return handleGeospatialType(obj, columnTypeName); + } // separate handling for complex data types if (isComplexType(columnTypeName)) { return handleComplexDataTypes(obj, columnTypeName); @@ -541,6 +545,25 @@ public Object getObject(int columnIndex) throws SQLException { return ConverterHelper.convertSqlTypeToJavaType(columnType, obj); } + private Object handleGeospatialType(Object obj, String columnName) throws DatabricksSQLException { + if (resultSetType == ResultSetType.SEA_INLINE) { + obj = convertGeospatialForSEAInline(obj, columnName); + } + return obj; + } + + private Object convertGeospatialForSEAInline(Object obj, String columnName) + throws DatabricksSQLException { + if (columnName.startsWith(GEOMETRY)) { + return ConverterHelper.getConverterForColumnType(Types.OTHER, GEOMETRY) + .toDatabricksGeometry(obj); + } else if (columnName.startsWith(GEOGRAPHY)) { + return ConverterHelper.getConverterForColumnType(Types.OTHER, GEOGRAPHY) + .toDatabricksGeography(obj); + } + return obj; + } + private Object handleComplexDataTypes(Object obj, String columnName) throws DatabricksSQLException { if (resultSetType == ResultSetType.SEA_INLINE) { @@ -558,12 +581,6 @@ private Object convertToComplexDataTypesForSEAInline(Object obj, String columnNa return parser.parseJsonStringToDbMap(obj.toString(), columnName); } else if (columnName.startsWith(STRUCT)) { return parser.parseJsonStringToDbStruct(obj.toString(), columnName); - } else if (columnName.startsWith(GEOMETRY)) { - return ConverterHelper.getConverterForColumnType(Types.OTHER, GEOMETRY) - .toDatabricksGeometry(obj); - } else if (columnName.startsWith(GEOGRAPHY)) { - return ConverterHelper.getConverterForColumnType(Types.OTHER, GEOGRAPHY) - .toDatabricksGeography(obj); } throw new DatabricksParsingException( "Unexpected metadata format. Type is not a COMPLEX: " + columnName, diff --git a/src/main/java/com/databricks/jdbc/api/impl/arrow/ArrowStreamResult.java b/src/main/java/com/databricks/jdbc/api/impl/arrow/ArrowStreamResult.java index a10de8e1e..1569b7efb 100644 --- a/src/main/java/com/databricks/jdbc/api/impl/arrow/ArrowStreamResult.java +++ b/src/main/java/com/databricks/jdbc/api/impl/arrow/ArrowStreamResult.java @@ -253,9 +253,7 @@ public Object getObject(int columnIndex) throws DatabricksSQLException { public static boolean isComplexType(ColumnInfoTypeName type) { return type == ColumnInfoTypeName.ARRAY || type == ColumnInfoTypeName.MAP - || type == ColumnInfoTypeName.STRUCT - || type == ColumnInfoTypeName.GEOMETRY - || type == ColumnInfoTypeName.GEOGRAPHY; + || type == ColumnInfoTypeName.STRUCT; } /** diff --git a/src/main/java/com/databricks/jdbc/api/internal/IDatabricksConnectionContext.java b/src/main/java/com/databricks/jdbc/api/internal/IDatabricksConnectionContext.java index 71ab0ab58..83867e267 100644 --- a/src/main/java/com/databricks/jdbc/api/internal/IDatabricksConnectionContext.java +++ b/src/main/java/com/databricks/jdbc/api/internal/IDatabricksConnectionContext.java @@ -308,10 +308,7 @@ public interface IDatabricksConnectionContext { /** Returns true if driver return complex data type java objects natively as opposed to string */ boolean isComplexDatatypeSupportEnabled(); - /** - * Returns true if driver returns GEOMETRY and GEOGRAPHY types natively. Requires - * isComplexDatatypeSupportEnabled() to be true - */ + /** Returns true if driver returns GEOMETRY and GEOGRAPHY types natively. */ boolean isGeoSpatialSupportEnabled(); /** Returns the size for HTTP connection pool */ diff --git a/src/main/java/com/databricks/jdbc/common/DatabricksJdbcUrlParams.java b/src/main/java/com/databricks/jdbc/common/DatabricksJdbcUrlParams.java index b67a5023b..518676457 100644 --- a/src/main/java/com/databricks/jdbc/common/DatabricksJdbcUrlParams.java +++ b/src/main/java/com/databricks/jdbc/common/DatabricksJdbcUrlParams.java @@ -130,7 +130,7 @@ public enum DatabricksJdbcUrlParams { "0"), ENABLE_GEOSPATIAL_SUPPORT( "EnableGeoSpatialSupport", - "flag to enable native support of GEOMETRY and GEOGRAPHY data types. Requires EnableComplexDatatypeSupport=1", + "flag to enable native support of GEOMETRY and GEOGRAPHY data types", "0"), ROWS_FETCHED_PER_BLOCK( "RowsFetchedPerBlock", diff --git a/src/main/java/com/databricks/jdbc/common/util/DatabricksThriftUtil.java b/src/main/java/com/databricks/jdbc/common/util/DatabricksThriftUtil.java index 53a7e383b..e50e20366 100644 --- a/src/main/java/com/databricks/jdbc/common/util/DatabricksThriftUtil.java +++ b/src/main/java/com/databricks/jdbc/common/util/DatabricksThriftUtil.java @@ -233,7 +233,8 @@ public static ColumnInfo getColumnInfoFromTColumnDesc( String typeText = getTypeTextFromTypeDesc(columnDesc.getTypeDesc()); - if (arrowMetadata != null && isComplexType(arrowMetadata)) { + if (arrowMetadata != null + && (isComplexType(arrowMetadata) || isGeospatialType(arrowMetadata))) { typeText = arrowMetadata; if (arrowMetadata.startsWith(GEOMETRY)) { columnInfoTypeName = ColumnInfoTypeName.GEOMETRY; diff --git a/src/main/java/com/databricks/jdbc/common/util/DatabricksTypeUtil.java b/src/main/java/com/databricks/jdbc/common/util/DatabricksTypeUtil.java index 635059718..9d7dce54c 100644 --- a/src/main/java/com/databricks/jdbc/common/util/DatabricksTypeUtil.java +++ b/src/main/java/com/databricks/jdbc/common/util/DatabricksTypeUtil.java @@ -561,18 +561,22 @@ public static String getDecimalTypeString(BigDecimal bd) { } /** - * Checks if the given type name represents a complex type (ARRAY, MAP, STRUCT, GEOMETRY, or - * GEOGRAPHY). + * Checks if the given type name represents a complex type (ARRAY, MAP, STRUCT). * * @param typeName The type name to check - * @return true if the type name starts with ARRAY, MAP, STRUCT, GEOMETRY, or GEOGRAPHY, false - * otherwise + * @return true if the type name starts with ARRAY, MAP, or STRUCT */ public static boolean isComplexType(String typeName) { - return typeName.startsWith(ARRAY) - || typeName.startsWith(MAP) - || typeName.startsWith(STRUCT) - || typeName.startsWith(GEOMETRY) - || typeName.startsWith(GEOGRAPHY); + return typeName.startsWith(ARRAY) || typeName.startsWith(MAP) || typeName.startsWith(STRUCT); + } + + /** + * Checks if the given type name represents a geospatial type (GEOMETRY, GEOGRAPHY). + * + * @param typeName The type name to check + * @return true if the type name starts with GEOMETRY or GEOGRAPHY + */ + public static boolean isGeospatialType(String typeName) { + return typeName.startsWith(GEOMETRY) || typeName.startsWith(GEOGRAPHY); } } diff --git a/src/test/java/com/databricks/jdbc/api/impl/DatabricksConnectionContextTest.java b/src/test/java/com/databricks/jdbc/api/impl/DatabricksConnectionContextTest.java index cc61fad69..3be5ae52f 100644 --- a/src/test/java/com/databricks/jdbc/api/impl/DatabricksConnectionContextTest.java +++ b/src/test/java/com/databricks/jdbc/api/impl/DatabricksConnectionContextTest.java @@ -1488,6 +1488,59 @@ public void testUseQueryForMetadataExplicitFalseOnWarehouse() throws DatabricksS assertFalse(ctx.useQueryForMetadata()); } + // --------------------------------------------------------------------------- + // Geospatial flag independence from complex datatype flag + // --------------------------------------------------------------------------- + + @Test + public void testGeospatialEnabled_complexDisabled() throws DatabricksSQLException { + IDatabricksConnectionContext ctx = + DatabricksConnectionContext.parse( + TestConstants.VALID_URL_1 + ";EnableGeoSpatialSupport=1;EnableComplexDatatypeSupport=0", + properties); + assertTrue(ctx.isGeoSpatialSupportEnabled()); + assertFalse(ctx.isComplexDatatypeSupportEnabled()); + } + + @Test + public void testGeospatialDisabled_complexEnabled() throws DatabricksSQLException { + IDatabricksConnectionContext ctx = + DatabricksConnectionContext.parse( + TestConstants.VALID_URL_1 + ";EnableGeoSpatialSupport=0;EnableComplexDatatypeSupport=1", + properties); + assertFalse(ctx.isGeoSpatialSupportEnabled()); + assertTrue(ctx.isComplexDatatypeSupportEnabled()); + } + + @Test + public void testGeospatialAndComplexBothEnabled() throws DatabricksSQLException { + IDatabricksConnectionContext ctx = + DatabricksConnectionContext.parse( + TestConstants.VALID_URL_1 + ";EnableGeoSpatialSupport=1;EnableComplexDatatypeSupport=1", + properties); + assertTrue(ctx.isGeoSpatialSupportEnabled()); + assertTrue(ctx.isComplexDatatypeSupportEnabled()); + } + + @Test + public void testGeospatialAndComplexBothDisabled() throws DatabricksSQLException { + IDatabricksConnectionContext ctx = + DatabricksConnectionContext.parse( + TestConstants.VALID_URL_1 + ";EnableGeoSpatialSupport=0;EnableComplexDatatypeSupport=0", + properties); + assertFalse(ctx.isGeoSpatialSupportEnabled()); + assertFalse(ctx.isComplexDatatypeSupportEnabled()); + } + + @Test + public void testGeospatialDefaultDisabled() throws DatabricksSQLException { + // Neither flag set — both default to disabled + IDatabricksConnectionContext ctx = + DatabricksConnectionContext.parse(TestConstants.VALID_URL_1, properties); + assertFalse(ctx.isGeoSpatialSupportEnabled()); + assertFalse(ctx.isComplexDatatypeSupportEnabled()); + } + // --------------------------------------------------------------------------- // Client type selection with Thrift-native metadata params // --------------------------------------------------------------------------- diff --git a/src/test/java/com/databricks/jdbc/api/impl/DatabricksResultSetMetaDataTest.java b/src/test/java/com/databricks/jdbc/api/impl/DatabricksResultSetMetaDataTest.java index 51474e21a..0485e96a8 100644 --- a/src/test/java/com/databricks/jdbc/api/impl/DatabricksResultSetMetaDataTest.java +++ b/src/test/java/com/databricks/jdbc/api/impl/DatabricksResultSetMetaDataTest.java @@ -765,9 +765,9 @@ public void testJsonArrayFormatMetadataWithGeospatialFlagDisabled() throws SQLEx @Test public void testJsonArrayWithComplexTypesEnabledButGeospatialDisabled() throws SQLException { - // This test validates the important scenario where EnableComplexDatatypeSupport=1 - // but EnableGeoSpatialSupport=0 (disabled). This simulates real-world usage where - // users want complex types (ARRAY, MAP, STRUCT) but want geospatial data as strings. + // This test validates that with EnableGeoSpatialSupport=0, geospatial columns + // report as STRING in metadata regardless of the EnableComplexDatatypeSupport setting. + // The two flags are independent. // // Expected behavior: // - GEOMETRY/GEOGRAPHY column types should report as STRING in metadata diff --git a/src/test/java/com/databricks/jdbc/api/impl/InlineJsonResultTest.java b/src/test/java/com/databricks/jdbc/api/impl/InlineJsonResultTest.java index 1d8c3386b..13e376153 100644 --- a/src/test/java/com/databricks/jdbc/api/impl/InlineJsonResultTest.java +++ b/src/test/java/com/databricks/jdbc/api/impl/InlineJsonResultTest.java @@ -367,9 +367,9 @@ void testJsonArrayFormatWithGeospatialTypesWhenFlagEnabled() throws DatabricksSQ @Test void testJsonArrayWithComplexTypesEnabledButGeospatialDisabled() throws DatabricksSQLException { - // This test validates the scenario where EnableComplexDatatypeSupport=1 but - // EnableGeoSpatialSupport=0 (disabled). This simulates a real-world scenario where - // users enable complex types for ARRAY/MAP/STRUCT but want geospatial data as strings. + // This test validates that with EnableGeoSpatialSupport=0, geospatial columns + // return as strings regardless of EnableComplexDatatypeSupport setting. + // The two flags are independent. // // Expected behavior: // - Geospatial column TYPES in metadata should report as STRING diff --git a/src/test/java/com/databricks/jdbc/api/impl/arrow/ArrowStreamResultTest.java b/src/test/java/com/databricks/jdbc/api/impl/arrow/ArrowStreamResultTest.java index 3ede51f43..e52404953 100644 --- a/src/test/java/com/databricks/jdbc/api/impl/arrow/ArrowStreamResultTest.java +++ b/src/test/java/com/databricks/jdbc/api/impl/arrow/ArrowStreamResultTest.java @@ -195,8 +195,15 @@ public void testComplexTypeHandling() { assertTrue(ArrowStreamResult.isComplexType(ColumnInfoTypeName.ARRAY)); assertTrue(ArrowStreamResult.isComplexType(ColumnInfoTypeName.MAP)); assertTrue(ArrowStreamResult.isComplexType(ColumnInfoTypeName.STRUCT)); - assertTrue(ArrowStreamResult.isComplexType(ColumnInfoTypeName.GEOMETRY)); - assertTrue(ArrowStreamResult.isComplexType(ColumnInfoTypeName.GEOGRAPHY)); + + // Geospatial types are NOT complex types — they have independent handling + assertFalse(ArrowStreamResult.isComplexType(ColumnInfoTypeName.GEOMETRY)); + assertFalse(ArrowStreamResult.isComplexType(ColumnInfoTypeName.GEOGRAPHY)); + + // Geospatial type check is separate + assertTrue(ArrowStreamResult.isGeospatialType(ColumnInfoTypeName.GEOMETRY)); + assertTrue(ArrowStreamResult.isGeospatialType(ColumnInfoTypeName.GEOGRAPHY)); + assertFalse(ArrowStreamResult.isGeospatialType(ColumnInfoTypeName.ARRAY)); // Non-complex types should return false assertFalse(ArrowStreamResult.isComplexType(ColumnInfoTypeName.INT)); @@ -338,8 +345,8 @@ private Schema createTestSchema() { @Test public void testGeospatialTypeWithGeoSpatialSupportDisabled() throws Exception { - // Setup connection context with geospatial support disabled - // (EnableComplexDatatypeSupport=1, but EnableGeoSpatialSupport=0) + // Setup connection context with geospatial support disabled (EnableGeoSpatialSupport=0) + // Complex datatype flag is independent and has no effect on geospatial behavior Properties props = new Properties(); props.setProperty("EnableComplexDatatypeSupport", "1"); props.setProperty("EnableGeoSpatialSupport", "0"); @@ -396,32 +403,35 @@ public void testGeospatialTypeWithGeoSpatialSupportDisabled() throws Exception { } @Test - public void testGeospatialTypeWithBothFlagsEnabled() throws Exception { - // Setup connection context with both complex datatype and geospatial support enabled + public void testGeospatialEnabledIndependentlyOfComplexDatatype() throws Exception { + // Geospatial can be enabled with or without complex datatype support Properties props = new Properties(); props.setProperty("EnableComplexDatatypeSupport", "1"); props.setProperty("EnableGeoSpatialSupport", "1"); - IDatabricksConnectionContext connectionContext = - DatabricksConnectionContextFactory.create(JDBC_URL, props); - - // Verify both flags are enabled - assertTrue(connectionContext.isComplexDatatypeSupportEnabled()); - assertTrue(connectionContext.isGeoSpatialSupportEnabled()); + IDatabricksConnectionContext ctx1 = DatabricksConnectionContextFactory.create(JDBC_URL, props); + assertTrue(ctx1.isComplexDatatypeSupportEnabled()); + assertTrue(ctx1.isGeoSpatialSupportEnabled()); + + // Geospatial enabled without complex datatypes + Properties props2 = new Properties(); + props2.setProperty("EnableComplexDatatypeSupport", "0"); + props2.setProperty("EnableGeoSpatialSupport", "1"); + IDatabricksConnectionContext ctx2 = DatabricksConnectionContextFactory.create(JDBC_URL, props2); + assertFalse(ctx2.isComplexDatatypeSupportEnabled()); + assertTrue(ctx2.isGeoSpatialSupportEnabled()); } @Test - public void testGeospatialSupportRequiresComplexDatatypeSupport() throws Exception { - // Test that EnableGeoSpatialSupport=1 alone (without EnableComplexDatatypeSupport) doesn't - // enable geospatial + public void testGeospatialSupportIndependentOfComplexDatatypeSupport() throws Exception { + // Geospatial support is independent of complex datatype support — can be enabled alone Properties props = new Properties(); props.setProperty("EnableComplexDatatypeSupport", "0"); props.setProperty("EnableGeoSpatialSupport", "1"); IDatabricksConnectionContext connectionContext = DatabricksConnectionContextFactory.create(JDBC_URL, props); - // Verify that geospatial support is disabled because complex datatype support is disabled assertFalse(connectionContext.isComplexDatatypeSupportEnabled()); - assertFalse(connectionContext.isGeoSpatialSupportEnabled()); + assertTrue(connectionContext.isGeoSpatialSupportEnabled()); } @Test diff --git a/src/test/java/com/databricks/jdbc/integration/e2e/GeospatialTests.java b/src/test/java/com/databricks/jdbc/integration/e2e/GeospatialTests.java index 8b3bf9e4d..65e255ff6 100644 --- a/src/test/java/com/databricks/jdbc/integration/e2e/GeospatialTests.java +++ b/src/test/java/com/databricks/jdbc/integration/e2e/GeospatialTests.java @@ -19,9 +19,9 @@ * (Thrift/SEA), Serialization (Arrow/Inline), CloudFetch, GeoSpatial support, and Complex type * support. * - *
Geospatial objects (IGeometry/IGeography) are returned only when both EnableGeoSpatialSupport - * and EnableComplexDatatypeSupport are enabled AND not in Thrift+Inline mode. Otherwise, returns as - * STRING. + *
Geospatial objects (IGeometry/IGeography) are returned when EnableGeoSpatialSupport is enabled + * AND not in Thrift+Inline mode. EnableComplexDatatypeSupport is independent and not required. + * Otherwise, returns as STRING. */ public class GeospatialTests { @@ -138,10 +138,10 @@ void testGeospatialPoint( assertTrue(rs.next(), "Should have at least one row for config: " + desc); // Geospatial objects returned only when: - // 1. Both EnableGeoSpatialSupport=1 AND EnableComplexDatatypeSupport=1 + // 1. EnableGeoSpatialSupport=1 (independent of EnableComplexDatatypeSupport) // 2. NOT in Thrift + Inline mode (Thrift doesn't support geospatial without Arrow) boolean shouldReturnGeospatialObjects = - enableGeoSupport == 1 && enableComplexSupport == 1 && !(useThrift == 1 && enableArrow == 0); + enableGeoSupport == 1 && !(useThrift == 1 && enableArrow == 0); if (shouldReturnGeospatialObjects) { validateGeospatialEnabled(rs, rsm); @@ -193,10 +193,10 @@ void testGeometryAny( } // Geospatial objects returned only when: - // 1. Both EnableGeoSpatialSupport=1 AND EnableComplexDatatypeSupport=1 + // 1. EnableGeoSpatialSupport=1 (independent of EnableComplexDatatypeSupport) // 2. NOT in Thrift + Inline mode boolean shouldReturnGeospatialObjects = - enableGeoSupport == 1 && enableComplexSupport == 1 && !(useThrift == 1 && enableArrow == 0); + enableGeoSupport == 1 && !(useThrift == 1 && enableArrow == 0); if (shouldReturnGeospatialObjects) { validateGeometryAnyEnabled(rs, rsm);