Skip to content

Commit ab7556e

Browse files
committed
Geospatial datatype support
Signed-off-by: Sreekanth Vadigi <sreekanth.vadigi@databricks.com>
1 parent 4c418e4 commit ab7556e

16 files changed

Lines changed: 786 additions & 19 deletions

pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,11 @@
287287
<artifactId>resilience4j-core</artifactId>
288288
<version>${resilience4j.version}</version>
289289
</dependency>
290+
<dependency>
291+
<groupId>org.locationtech.jts</groupId>
292+
<artifactId>jts-core</artifactId>
293+
<version>1.20.0</version>
294+
</dependency>
290295
</dependencies>
291296

292297
<build>
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package com.databricks.jdbc.api.impl;
2+
3+
import com.databricks.jdbc.api.impl.converters.WKTConverter;
4+
import com.databricks.jdbc.exception.DatabricksValidationException;
5+
import com.databricks.jdbc.log.JdbcLogger;
6+
import com.databricks.jdbc.log.JdbcLoggerFactory;
7+
import java.util.Objects;
8+
9+
/**
10+
* Abstract base class for geospatial data types in Databricks JDBC driver.
11+
*
12+
* <p>This class provides common functionality for both GEOMETRY and GEOGRAPHY types, including
13+
* storage of WKT (Well-Known Text) format data and access to both WKT and WKB representations.
14+
*/
15+
public abstract class AbstractDatabricksGeospatial implements DatabricksGeospatial {
16+
17+
private static final JdbcLogger LOGGER =
18+
JdbcLoggerFactory.getLogger(AbstractDatabricksGeospatial.class);
19+
20+
private final String wkt;
21+
private final int srid;
22+
23+
/**
24+
* Constructs an AbstractDatabricksGeospatial with the specified WKT and SRID.
25+
*
26+
* @param wkt the Well-Known Text representation of the geospatial object
27+
* @param srid the Spatial Reference System Identifier
28+
* @throws DatabricksValidationException if the WKT is invalid
29+
*/
30+
protected AbstractDatabricksGeospatial(String wkt, int srid)
31+
throws DatabricksValidationException {
32+
if (wkt == null || wkt.trim().isEmpty()) {
33+
throw new DatabricksValidationException("WKT string cannot be null or empty");
34+
}
35+
36+
this.wkt = wkt.trim();
37+
this.srid = srid;
38+
}
39+
40+
/**
41+
* Returns the Well-Known Binary (WKB) representation of the geospatial object.
42+
*
43+
* @return the WKB representation as a byte array
44+
* @throws DatabricksValidationException if WKT to WKB conversion fails
45+
*/
46+
@Override
47+
public byte[] getWkb() throws DatabricksValidationException {
48+
return WKTConverter.toWKB(wkt);
49+
}
50+
51+
/**
52+
* Returns the Spatial Reference System Identifier (SRID) of the geospatial object.
53+
*
54+
* @return the SRID value
55+
*/
56+
@Override
57+
public int getSrid() {
58+
return srid;
59+
}
60+
61+
/**
62+
* Returns the Well-Known Text (WKT) representation of the geospatial object.
63+
*
64+
* @return the WKT string
65+
*/
66+
@Override
67+
public String getWkt() {
68+
return wkt;
69+
}
70+
71+
/**
72+
* Returns a string representation of the geospatial object in EWKT format.
73+
*
74+
* @return the EWKT string representation
75+
*/
76+
@Override
77+
public String toString() {
78+
return String.format("SRID=%d;%s", srid, wkt);
79+
}
80+
81+
/**
82+
* Checks if this geospatial object is equal to another object.
83+
*
84+
* @param obj the object to compare
85+
* @return true if the objects are equal, false otherwise
86+
*/
87+
@Override
88+
public boolean equals(Object obj) {
89+
if (this == obj) {
90+
return true;
91+
}
92+
if (obj == null || getClass() != obj.getClass()) {
93+
return false;
94+
}
95+
96+
AbstractDatabricksGeospatial that = (AbstractDatabricksGeospatial) obj;
97+
return srid == that.srid && wkt.equals(that.wkt);
98+
}
99+
100+
/**
101+
* Returns the hash code for this geospatial object.
102+
*
103+
* @return the hash code
104+
*/
105+
@Override
106+
public int hashCode() {
107+
return Objects.hash(wkt, srid);
108+
}
109+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.databricks.jdbc.api.impl;
2+
3+
import com.databricks.jdbc.exception.DatabricksValidationException;
4+
5+
public class DatabricksGeography extends AbstractDatabricksGeospatial {
6+
7+
/**
8+
* Constructs a DatabricksGeography with the specified WKT and SRID.
9+
*
10+
* @param wkt the Well-Known Text representation of the geography
11+
* @param srid the Spatial Reference System Identifier
12+
* @throws DatabricksValidationException if the WKT is invalid
13+
*/
14+
public DatabricksGeography(String wkt, int srid) throws DatabricksValidationException {
15+
super(wkt, srid);
16+
}
17+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.databricks.jdbc.api.impl;
2+
3+
import com.databricks.jdbc.exception.DatabricksValidationException;
4+
5+
public class DatabricksGeometry extends AbstractDatabricksGeospatial {
6+
7+
/**
8+
* Constructs a DatabricksGeometry with the specified WKT and SRID.
9+
*
10+
* @param wkt the Well-Known Text representation of the geometry
11+
* @param srid the Spatial Reference System Identifier
12+
* @throws DatabricksValidationException if the WKT is invalid
13+
*/
14+
public DatabricksGeometry(String wkt, int srid) throws DatabricksValidationException {
15+
super(wkt, srid);
16+
}
17+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package com.databricks.jdbc.api.impl;
2+
3+
import com.databricks.jdbc.exception.DatabricksValidationException;
4+
5+
/**
6+
* Interface for geospatial data types in Databricks JDBC driver.
7+
*
8+
* <p>This interface provides common functionality for both GEOMETRY and GEOGRAPHY types, allowing
9+
* access to Well-Known Text (WKT), Well-Known Binary (WKB) representation and Spatial Reference
10+
* System Identifier (SRID).
11+
*
12+
* <p>Following the established patterns of DatabricksStruct, DatabricksArray, and DatabricksMap,
13+
* this interface enables consistent handling of geospatial data across the JDBC driver.
14+
*/
15+
public interface DatabricksGeospatial {
16+
17+
/**
18+
* Returns the Well-Known Binary (WKB) representation of the geospatial object.
19+
*
20+
* <p>WKB is a binary format for representing geometry data that is compact and suitable for
21+
* storage and transmission. This method converts the internal representation to WKB format on
22+
* demand.
23+
*
24+
* @return the WKB representation as a byte array
25+
* @throws DatabricksValidationException if WKT to WKB conversion fails
26+
*/
27+
byte[] getWkb() throws DatabricksValidationException;
28+
29+
/**
30+
* Returns the Spatial Reference System Identifier (SRID) of the geospatial object.
31+
*
32+
* <p>SRID identifies the coordinate system used by the geometry. Common values include:
33+
*
34+
* <ul>
35+
* <li>4326 - WGS 84 (World Geodetic System 1984)
36+
* <li>3857 - Web Mercator
37+
* <li>0 - No SRID specified
38+
* </ul>
39+
*
40+
* @return the SRID value
41+
*/
42+
int getSrid();
43+
44+
/**
45+
* Returns the Well-Known Text (WKT) representation of the geospatial object.
46+
*
47+
* <p>WKT is a human-readable text format for representing geometry data. This provides a
48+
* complement to the binary WKB format, allowing easy inspection and debugging of geospatial data.
49+
*
50+
* @return the WKT string representation
51+
*/
52+
String getWkt();
53+
}

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

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
package com.databricks.jdbc.api.impl;
22

33
import static com.databricks.jdbc.common.DatabricksJdbcConstants.EMPTY_STRING;
4-
import static com.databricks.jdbc.common.util.DatabricksTypeUtil.ARRAY;
5-
import static com.databricks.jdbc.common.util.DatabricksTypeUtil.MAP;
6-
import static com.databricks.jdbc.common.util.DatabricksTypeUtil.STRUCT;
4+
import static com.databricks.jdbc.common.util.DatabricksTypeUtil.*;
75

86
import com.databricks.jdbc.api.IDatabricksResultSet;
97
import com.databricks.jdbc.api.IExecutionStatus;
@@ -479,13 +477,19 @@ public ResultSetMetaData getMetaData() throws SQLException {
479477
}
480478

481479
/**
482-
* Checks if the given type name represents a complex type (ARRAY, MAP, or STRUCT).
480+
* Checks if the given type name represents a complex type (ARRAY, MAP, STRUCT, GEOMETRY, or
481+
* GEOGRAPHY).
483482
*
484483
* @param typeName The type name to check
485-
* @return true if the type name starts with ARRAY, MAP, or STRUCT, false otherwise
484+
* @return true if the type name starts with ARRAY, MAP, STRUCT, GEOMETRY, or GEOGRAPHY, false
485+
* otherwise
486486
*/
487487
private static boolean isComplexType(String typeName) {
488-
return typeName.startsWith(ARRAY) || typeName.startsWith(MAP) || typeName.startsWith(STRUCT);
488+
return typeName.startsWith(ARRAY)
489+
|| typeName.startsWith(MAP)
490+
|| typeName.startsWith(STRUCT)
491+
|| typeName.startsWith(GEOMETRY)
492+
|| typeName.startsWith(GEOGRAPHY);
489493
}
490494

491495
@Override
@@ -523,6 +527,10 @@ private Object handleComplexDataTypesForSEAInline(Object obj, String columnName)
523527
return parser.parseJsonStringToDbMap(obj.toString(), columnName).toString();
524528
} else if (columnName.startsWith(STRUCT)) {
525529
return parser.parseJsonStringToDbStruct(obj.toString(), columnName).toString();
530+
} else if (columnName.startsWith(GEOMETRY)) {
531+
return obj;
532+
} else if (columnName.startsWith(GEOGRAPHY)) {
533+
return obj;
526534
}
527535
throw new DatabricksParsingException(
528536
"Unexpected metadata format. Type is not a COMPLEX: " + columnName,
@@ -1961,7 +1969,11 @@ private <T> T getConvertedObject(
19611969
return defaultValue.get();
19621970
}
19631971
int columnType = resultSetMetaData.getColumnType(columnIndex);
1964-
ObjectConverter converter = ConverterHelper.getConverterForSqlType(columnType);
1972+
String columnTypeName = resultSetMetaData.getColumnTypeName(columnIndex);
1973+
1974+
// Use metadata-aware converter selection for proper handling of databricks-specific types
1975+
ObjectConverter converter =
1976+
ConverterHelper.getConverterForColumnType(columnType, columnTypeName);
19651977
return convertMethod.apply(converter, obj);
19661978
}
19671979

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

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,7 @@
55
import static com.databricks.jdbc.common.MetadataResultConstants.LARGE_DISPLAY_COLUMNS;
66
import static com.databricks.jdbc.common.MetadataResultConstants.REMARKS_COLUMN;
77
import static com.databricks.jdbc.common.util.DatabricksThriftUtil.*;
8-
import static com.databricks.jdbc.common.util.DatabricksTypeUtil.TIMESTAMP;
9-
import static com.databricks.jdbc.common.util.DatabricksTypeUtil.TIMESTAMP_NTZ;
10-
import static com.databricks.jdbc.common.util.DatabricksTypeUtil.VARIANT;
11-
import static com.databricks.jdbc.common.util.DatabricksTypeUtil.getBasePrecisionAndScale;
8+
import static com.databricks.jdbc.common.util.DatabricksTypeUtil.*;
129

1310
import com.databricks.jdbc.api.internal.IDatabricksConnectionContext;
1411
import com.databricks.jdbc.common.AccessType;
@@ -178,6 +175,7 @@ public DatabricksResultSetMetaData(
178175
columnIndex < resultManifest.getSchema().getColumnsSize();
179176
columnIndex++) {
180177
TColumnDesc columnDesc = resultManifest.getSchema().getColumns().get(columnIndex);
178+
181179
ColumnInfo columnInfo = getColumnInfoFromTColumnDesc(columnDesc);
182180
int[] precisionAndScale = getPrecisionAndScale(columnInfo);
183181
int precision = precisionAndScale[0];
@@ -205,6 +203,16 @@ public DatabricksResultSetMetaData(
205203
.columnTypeClassName("java.lang.String")
206204
.columnType(Types.OTHER)
207205
.columnTypeText(VARIANT);
206+
} else if (isGeometryColumn(arrowMetadata, columnIndex)) {
207+
columnBuilder
208+
.columnTypeClassName("com.databricks.jdbc.api.impl.DatabricksGeometry")
209+
.columnType(Types.OTHER)
210+
.columnTypeText(GEOMETRY);
211+
} else if (isGeographyColumn(arrowMetadata, columnIndex)) {
212+
columnBuilder
213+
.columnTypeClassName("com.databricks.jdbc.api.impl.DatabricksGeography")
214+
.columnType(Types.OTHER)
215+
.columnTypeText(GEOGRAPHY);
208216
}
209217
columnsBuilder.add(columnBuilder.build());
210218
columnNameToIndexMap.putIfAbsent(columnInfo.getName(), ++currIndex);
@@ -642,6 +650,20 @@ private boolean isVariantColumn(List<String> arrowMetadata, int i) {
642650
&& arrowMetadata.get(i).equalsIgnoreCase(VARIANT);
643651
}
644652

653+
private boolean isGeometryColumn(List<String> arrowMetadata, int i) {
654+
return arrowMetadata != null
655+
&& arrowMetadata.size() > i
656+
&& arrowMetadata.get(i) != null
657+
&& arrowMetadata.get(i).contains(GEOMETRY);
658+
}
659+
660+
private boolean isGeographyColumn(List<String> arrowMetadata, int i) {
661+
return arrowMetadata != null
662+
&& arrowMetadata.size() > i
663+
&& arrowMetadata.get(i) != null
664+
&& arrowMetadata.get(i).contains(GEOGRAPHY);
665+
}
666+
645667
private ImmutableDatabricksColumn.Builder getColumnBuilder() {
646668
return ImmutableDatabricksColumn.builder()
647669
.isAutoIncrement(false)

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ public Object getObject(int columnIndex) throws DatabricksSQLException {
160160
}
161161

162162
/**
163-
* Checks if the given type is a complex type (ARRAY, MAP, or STRUCT).
163+
* Checks if the given type is a complex type (ARRAY, MAP, STRUCT, GEOMETRY, or GEOGRAPHY).
164164
*
165165
* @param type The type to check
166166
* @return true if the type is a complex type, false otherwise
@@ -169,7 +169,9 @@ public Object getObject(int columnIndex) throws DatabricksSQLException {
169169
public static boolean isComplexType(ColumnInfoTypeName type) {
170170
return type == ColumnInfoTypeName.ARRAY
171171
|| type == ColumnInfoTypeName.MAP
172-
|| type == ColumnInfoTypeName.STRUCT;
172+
|| type == ColumnInfoTypeName.STRUCT
173+
|| type == ColumnInfoTypeName.GEOMETRY
174+
|| type == ColumnInfoTypeName.GEOGRAPHY;
173175
}
174176

175177
/** {@inheritDoc} */

0 commit comments

Comments
 (0)