Skip to content

Commit 13046cb

Browse files
authored
Geospatial datatype support (#988)
## Description <!-- Provide a brief summary of the changes made and the issue they aim to address.--> Added support for geospatial datatype ## Testing <!-- Describe how the changes have been tested--> Tested in all paths - combinations of thrift/sea, arrow/non-arrow, complex/non-complex datatype - ensuring similar behaviour with other datatypes. ## Additional Notes to the Reviewer <!-- Share any additional context or insights that may help the reviewer understand the changes better. This could include challenges faced, limitations, or compromises made during the development process. Also, mention any areas of the code that you would like the reviewer to focus on specifically. --> --------- Signed-off-by: Sreekanth Vadigi <sreekanth.vadigi@databricks.com>
1 parent b9a793f commit 13046cb

39 files changed

Lines changed: 1950 additions & 21 deletions

File tree

NEXT_CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
## [Unreleased]
44

55
### Added
6+
- Added support for geospatial data types.
67
* Added support for telemetry log levels, which can be controlled via the connection parameter `TelemetryLogLevel`. This allows users to configure the verbosity of telemetry logging from OFF to TRACE.
78

8-
99
### Updated
1010

1111
### Fixed

NOTICE

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,12 @@ jakartaee/common-annotations-api - https://github.com/jakartaee/common-annotatio
8080
Copyright common-annotations-api authors
8181
Notice - https://github.com/jakartaee/common-annotations-api/blob/master/NOTICE.md
8282

83+
locationtech/jts - https://github.com/locationtech/jts
84+
Copyright (c) 2007, Eclipse Foundation, Inc. and its licensors
85+
Copyright LocationTech JTS contributors
86+
License: Eclipse Public License v2.0 (EPL-2.0) and Eclipse Distribution License v1.0 (EDL-1.0)
87+
License URLs: https://github.com/locationtech/jts/blob/master/LICENSE_EPLv2.txt, https://github.com/locationtech/jts/blob/master/LICENSE_EDLv1.txt
88+
8389
________________
8490
This Software contains code from the following open source projects, licensed under the GNU Lesser General Public License (https://www.gnu.org/licenses/lgpl-3.0.html):
8591

pom.xml

Lines changed: 9 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>
@@ -539,6 +544,10 @@
539544
<pattern>org.json</pattern>
540545
<shadedPattern>com.databricks.internal.json</shadedPattern>
541546
</relocation>
547+
<relocation>
548+
<pattern>org.locationtech.jts</pattern>
549+
<shadedPattern>com.databricks.internal.jts</shadedPattern>
550+
</relocation>
542551
<relocation>
543552
<pattern>org.osgi</pattern>
544553
<shadedPattern>com.databricks.internal.osgi</shadedPattern>
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package com.databricks.jdbc.api;
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 IDatabricksGeospatial {
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+
54+
/**
55+
* Returns the data type of the geospatial object.
56+
*
57+
* @return the type as a string, either "GEOMETRY" or "GEOGRAPHY"
58+
*/
59+
String getType();
60+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package com.databricks.jdbc.api;
2+
3+
/** Interface for GEOGRAPHY data types in Databricks JDBC driver. */
4+
public interface IGeography extends IDatabricksGeospatial {}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package com.databricks.jdbc.api;
2+
3+
/** Interface for GEOMETRY data types in Databricks JDBC driver. */
4+
public interface IGeometry extends IDatabricksGeospatial {}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
package com.databricks.jdbc.api.impl;
2+
3+
import com.databricks.jdbc.api.IDatabricksGeospatial;
4+
import com.databricks.jdbc.api.impl.converters.WKTConverter;
5+
import com.databricks.jdbc.exception.DatabricksValidationException;
6+
import com.databricks.jdbc.log.JdbcLogger;
7+
import com.databricks.jdbc.log.JdbcLoggerFactory;
8+
import java.util.Objects;
9+
10+
/**
11+
* Abstract base class for geospatial data types in Databricks JDBC driver.
12+
*
13+
* <p>This class provides common functionality for both GEOMETRY and GEOGRAPHY types, including
14+
* storage of WKT (Well-Known Text) format data and access to both WKT and WKB representations.
15+
*/
16+
public abstract class AbstractDatabricksGeospatial implements IDatabricksGeospatial {
17+
18+
private static final JdbcLogger LOGGER =
19+
JdbcLoggerFactory.getLogger(AbstractDatabricksGeospatial.class);
20+
21+
private final String wkt;
22+
private final int srid; // Spatial Reference System Identifier
23+
24+
/**
25+
* Constructs an AbstractDatabricksGeospatial with the specified WKT and SRID.
26+
*
27+
* @param wkt the Well-Known Text representation of the geospatial object
28+
* @param srid the Spatial Reference System Identifier
29+
* @throws DatabricksValidationException if the WKT is invalid
30+
*/
31+
protected AbstractDatabricksGeospatial(String wkt, int srid)
32+
throws DatabricksValidationException {
33+
if (wkt == null || wkt.trim().isEmpty()) {
34+
LOGGER.error("WKT string cannot be null or empty");
35+
throw new DatabricksValidationException("WKT string cannot be null or empty");
36+
}
37+
38+
this.wkt = wkt.trim();
39+
this.srid = srid;
40+
}
41+
42+
/**
43+
* Returns the Well-Known Binary (WKB) representation of the geospatial object.
44+
*
45+
* @return the WKB representation as a byte array
46+
* @throws DatabricksValidationException if WKT to WKB conversion fails
47+
*/
48+
@Override
49+
public byte[] getWKB() throws DatabricksValidationException {
50+
return WKTConverter.toWKB(wkt);
51+
}
52+
53+
/**
54+
* Returns the Spatial Reference System Identifier (SRID) of the geospatial object.
55+
*
56+
* @return the SRID value
57+
*/
58+
@Override
59+
public int getSRID() {
60+
return srid;
61+
}
62+
63+
/**
64+
* Returns the Well-Known Text (WKT) representation of the geospatial object.
65+
*
66+
* @return the WKT string
67+
*/
68+
@Override
69+
public String getWKT() {
70+
return wkt;
71+
}
72+
73+
/**
74+
* Returns a string representation of the geospatial object in EWKT format.
75+
*
76+
* @return the EWKT string representation
77+
*/
78+
@Override
79+
public String toString() {
80+
return String.format("SRID=%d;%s", srid, wkt);
81+
}
82+
83+
/**
84+
* Checks if this geospatial object is equal to another object.
85+
*
86+
* @param obj the object to compare
87+
* @return true if the objects are equal, false otherwise
88+
*/
89+
@Override
90+
public boolean equals(Object obj) {
91+
if (this == obj) {
92+
return true;
93+
}
94+
if (obj == null || getClass() != obj.getClass()) {
95+
return false;
96+
}
97+
98+
AbstractDatabricksGeospatial that = (AbstractDatabricksGeospatial) obj;
99+
return srid == that.srid && wkt.equals(that.wkt);
100+
}
101+
102+
/**
103+
* Returns the hash code for this geospatial object.
104+
*
105+
* @return the hash code
106+
*/
107+
@Override
108+
public int hashCode() {
109+
return Objects.hash(wkt, srid);
110+
}
111+
112+
/**
113+
* Returns the data type of the geospatial object.
114+
*
115+
* @return the type as a string, either "GEOMETRY" or "GEOGRAPHY"
116+
*/
117+
@Override
118+
public abstract String getType();
119+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.databricks.jdbc.api.impl;
2+
3+
import static com.databricks.jdbc.common.util.DatabricksTypeUtil.GEOGRAPHY;
4+
5+
import com.databricks.jdbc.api.IGeography;
6+
import com.databricks.jdbc.exception.DatabricksValidationException;
7+
8+
public class DatabricksGeography extends AbstractDatabricksGeospatial implements IGeography {
9+
10+
/**
11+
* Constructs a DatabricksGeography with the specified WKT and SRID.
12+
*
13+
* @param wkt the Well-Known Text representation of the geography
14+
* @param srid the Spatial Reference System Identifier
15+
* @throws DatabricksValidationException if the WKT is invalid
16+
*/
17+
public DatabricksGeography(String wkt, int srid) throws DatabricksValidationException {
18+
super(wkt, srid);
19+
}
20+
21+
@Override
22+
public String getType() {
23+
return GEOGRAPHY;
24+
}
25+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.databricks.jdbc.api.impl;
2+
3+
import static com.databricks.jdbc.common.util.DatabricksTypeUtil.GEOMETRY;
4+
5+
import com.databricks.jdbc.api.IGeometry;
6+
import com.databricks.jdbc.exception.DatabricksValidationException;
7+
8+
public class DatabricksGeometry extends AbstractDatabricksGeospatial implements IGeometry {
9+
10+
/**
11+
* Constructs a DatabricksGeometry with the specified WKT and SRID.
12+
*
13+
* @param wkt the Well-Known Text representation of the geometry
14+
* @param srid the Spatial Reference System Identifier
15+
* @throws DatabricksValidationException if the WKT is invalid
16+
*/
17+
public DatabricksGeometry(String wkt, int srid) throws DatabricksValidationException {
18+
super(wkt, srid);
19+
}
20+
21+
@Override
22+
public String getType() {
23+
return GEOMETRY;
24+
}
25+
}

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

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +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;
7-
import static com.databricks.jdbc.common.util.DatabricksTypeUtil.VARIANT;
4+
import static com.databricks.jdbc.common.util.DatabricksTypeUtil.*;
85

96
import com.databricks.jdbc.api.IDatabricksResultSet;
107
import com.databricks.jdbc.api.IExecutionStatus;
@@ -480,13 +477,19 @@ public ResultSetMetaData getMetaData() throws SQLException {
480477
}
481478

482479
/**
483-
* 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).
484482
*
485483
* @param typeName The type name to check
486-
* @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
487486
*/
488487
private static boolean isComplexType(String typeName) {
489-
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);
490493
}
491494

492495
@Override
@@ -531,6 +534,10 @@ private Object handleComplexDataTypesForSEAInline(Object obj, String columnName)
531534
return parser.parseJsonStringToDbMap(obj.toString(), columnName).toString();
532535
} else if (columnName.startsWith(STRUCT)) {
533536
return parser.parseJsonStringToDbStruct(obj.toString(), columnName).toString();
537+
} else if (columnName.startsWith(GEOMETRY)) {
538+
return obj;
539+
} else if (columnName.startsWith(GEOGRAPHY)) {
540+
return obj;
534541
}
535542
throw new DatabricksParsingException(
536543
"Unexpected metadata format. Type is not a COMPLEX: " + columnName,
@@ -1969,7 +1976,11 @@ private <T> T getConvertedObject(
19691976
return defaultValue.get();
19701977
}
19711978
int columnType = resultSetMetaData.getColumnType(columnIndex);
1972-
ObjectConverter converter = ConverterHelper.getConverterForSqlType(columnType);
1979+
String columnTypeName = resultSetMetaData.getColumnTypeName(columnIndex);
1980+
1981+
// Use metadata-aware converter selection for proper handling of databricks-specific types
1982+
ObjectConverter converter =
1983+
ConverterHelper.getConverterForColumnType(columnType, columnTypeName);
19731984
return convertMethod.apply(converter, obj);
19741985
}
19751986

0 commit comments

Comments
 (0)