Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions NEXT_CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## [Unreleased]

### Added
- Added support for geospatial data types.

- Added `enableMultipleCatalogSupport` connection parameter to control catalog metadata behavior.

Expand Down
6 changes: 6 additions & 0 deletions NOTICE
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,12 @@ jakartaee/common-annotations-api - https://github.com/jakartaee/common-annotatio
Copyright common-annotations-api authors
Notice - https://github.com/jakartaee/common-annotations-api/blob/master/NOTICE.md

locationtech/jts - https://github.com/locationtech/jts
Copyright (c) 2007, Eclipse Foundation, Inc. and its licensors
Copyright LocationTech JTS contributors
License: Eclipse Public License v2.0 (EPL-2.0) and Eclipse Distribution License v1.0 (EDL-1.0)
License URLs: https://github.com/locationtech/jts/blob/master/LICENSE_EPLv2.txt, https://github.com/locationtech/jts/blob/master/LICENSE_EDLv1.txt

________________
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):

Expand Down
9 changes: 9 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,11 @@
<artifactId>resilience4j-core</artifactId>
<version>${resilience4j.version}</version>
</dependency>
<dependency>
<groupId>org.locationtech.jts</groupId>
Comment thread
sreekanth-db marked this conversation as resolved.
<artifactId>jts-core</artifactId>
<version>1.20.0</version>
</dependency>
Comment thread
sreekanth-db marked this conversation as resolved.
</dependencies>

<build>
Expand Down Expand Up @@ -539,6 +544,10 @@
<pattern>org.json</pattern>
<shadedPattern>com.databricks.internal.json</shadedPattern>
</relocation>
<relocation>
<pattern>org.locationtech.jts</pattern>
<shadedPattern>com.databricks.internal.jts</shadedPattern>
</relocation>
<relocation>
<pattern>org.osgi</pattern>
<shadedPattern>com.databricks.internal.osgi</shadedPattern>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package com.databricks.jdbc.api.impl;

import com.databricks.jdbc.api.impl.converters.WKTConverter;
import com.databricks.jdbc.exception.DatabricksValidationException;
import com.databricks.jdbc.log.JdbcLogger;
import com.databricks.jdbc.log.JdbcLoggerFactory;
import java.util.Objects;

/**
* Abstract base class for geospatial data types in Databricks JDBC driver.
*
* <p>This class provides common functionality for both GEOMETRY and GEOGRAPHY types, including
* storage of WKT (Well-Known Text) format data and access to both WKT and WKB representations.
*/
public abstract class AbstractDatabricksGeospatial implements IDatabricksGeospatial {
Comment thread
sreekanth-db marked this conversation as resolved.

private static final JdbcLogger LOGGER =
JdbcLoggerFactory.getLogger(AbstractDatabricksGeospatial.class);

private final String wkt;
private final int srid; // Spatial Reference System Identifier

/**
* Constructs an AbstractDatabricksGeospatial with the specified WKT and SRID.
*
* @param wkt the Well-Known Text representation of the geospatial object
* @param srid the Spatial Reference System Identifier
* @throws DatabricksValidationException if the WKT is invalid
*/
protected AbstractDatabricksGeospatial(String wkt, int srid)
throws DatabricksValidationException {
if (wkt == null || wkt.trim().isEmpty()) {
LOGGER.error("WKT string cannot be null or empty");
throw new DatabricksValidationException("WKT string cannot be null or empty");
Comment thread
sreekanth-db marked this conversation as resolved.
}

this.wkt = wkt.trim();
this.srid = srid;
}

/**
* Returns the Well-Known Binary (WKB) representation of the geospatial object.
*
* @return the WKB representation as a byte array
* @throws DatabricksValidationException if WKT to WKB conversion fails
*/
@Override
public byte[] getWKB() throws DatabricksValidationException {
return WKTConverter.toWKB(wkt);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should consider storing this value as computation can be very expensive

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Storing wkb it in a class variable for reuse. Implemented in #1062

}

/**
* Returns the Spatial Reference System Identifier (SRID) of the geospatial object.
*
* @return the SRID value
*/
@Override
public int getSRID() {
return srid;
}

/**
* Returns the Well-Known Text (WKT) representation of the geospatial object.
*
* @return the WKT string
*/
@Override
public String getWKT() {
return wkt;
Comment thread
sreekanth-db marked this conversation as resolved.
}

/**
* Returns a string representation of the geospatial object in EWKT format.
*
* @return the EWKT string representation
*/
@Override
public String toString() {
return String.format("SRID=%d;%s", srid, wkt);
Comment thread
sreekanth-db marked this conversation as resolved.
}

/**
* Checks if this geospatial object is equal to another object.
*
* @param obj the object to compare
* @return true if the objects are equal, false otherwise
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}

AbstractDatabricksGeospatial that = (AbstractDatabricksGeospatial) obj;
return srid == that.srid && wkt.equals(that.wkt);
}

/**
* Returns the hash code for this geospatial object.
*
* @return the hash code
*/
@Override
public int hashCode() {
return Objects.hash(wkt, srid);
}

/**
* Returns the data type of the geospatial object.
*
* @return the type as a string, either "GEOMETRY" or "GEOGRAPHY"
*/
@Override
public abstract String getType();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.databricks.jdbc.api.impl;

import static com.databricks.jdbc.common.util.DatabricksTypeUtil.GEOGRAPHY;

import com.databricks.jdbc.exception.DatabricksValidationException;

public class DatabricksGeography extends AbstractDatabricksGeospatial {

/**
* Constructs a DatabricksGeography with the specified WKT and SRID.
*
* @param wkt the Well-Known Text representation of the geography
* @param srid the Spatial Reference System Identifier
* @throws DatabricksValidationException if the WKT is invalid
*/
public DatabricksGeography(String wkt, int srid) throws DatabricksValidationException {
Comment thread
sreekanth-db marked this conversation as resolved.
super(wkt, srid);
}

@Override
public String getType() {
return GEOGRAPHY;
}
}
24 changes: 24 additions & 0 deletions src/main/java/com/databricks/jdbc/api/impl/DatabricksGeometry.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.databricks.jdbc.api.impl;

import static com.databricks.jdbc.common.util.DatabricksTypeUtil.GEOMETRY;

import com.databricks.jdbc.exception.DatabricksValidationException;

public class DatabricksGeometry extends AbstractDatabricksGeospatial {
Comment thread
sreekanth-db marked this conversation as resolved.
Outdated

/**
* Constructs a DatabricksGeometry with the specified WKT and SRID.
*
* @param wkt the Well-Known Text representation of the geometry
* @param srid the Spatial Reference System Identifier
* @throws DatabricksValidationException if the WKT is invalid
*/
public DatabricksGeometry(String wkt, int srid) throws DatabricksValidationException {
super(wkt, srid);
}

@Override
public String getType() {
return GEOMETRY;
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
package com.databricks.jdbc.api.impl;

import static com.databricks.jdbc.common.DatabricksJdbcConstants.EMPTY_STRING;
import static com.databricks.jdbc.common.util.DatabricksTypeUtil.ARRAY;
import static com.databricks.jdbc.common.util.DatabricksTypeUtil.MAP;
import static com.databricks.jdbc.common.util.DatabricksTypeUtil.STRUCT;
import static com.databricks.jdbc.common.util.DatabricksTypeUtil.VARIANT;
import static com.databricks.jdbc.common.util.DatabricksTypeUtil.*;

import com.databricks.jdbc.api.IDatabricksResultSet;
import com.databricks.jdbc.api.IExecutionStatus;
Expand Down Expand Up @@ -480,13 +477,19 @@ public ResultSetMetaData getMetaData() throws SQLException {
}

/**
* Checks if the given type name represents a complex type (ARRAY, MAP, or STRUCT).
* Checks if the given type name represents a complex type (ARRAY, MAP, STRUCT, GEOMETRY, or
* GEOGRAPHY).
*
* @param typeName The type name to check
* @return true if the type name starts with ARRAY, MAP, or STRUCT, false otherwise
* @return true if the type name starts with ARRAY, MAP, STRUCT, GEOMETRY, or GEOGRAPHY, false
* otherwise
*/
private static boolean isComplexType(String typeName) {
return typeName.startsWith(ARRAY) || typeName.startsWith(MAP) || typeName.startsWith(STRUCT);
return typeName.startsWith(ARRAY)
|| typeName.startsWith(MAP)
|| typeName.startsWith(STRUCT)
|| typeName.startsWith(GEOMETRY)
|| typeName.startsWith(GEOGRAPHY);
}

@Override
Expand Down Expand Up @@ -531,6 +534,10 @@ private Object handleComplexDataTypesForSEAInline(Object obj, String columnName)
return parser.parseJsonStringToDbMap(obj.toString(), columnName).toString();
} else if (columnName.startsWith(STRUCT)) {
return parser.parseJsonStringToDbStruct(obj.toString(), columnName).toString();
} else if (columnName.startsWith(GEOMETRY)) {
return obj;
} else if (columnName.startsWith(GEOGRAPHY)) {
return obj;
}
throw new DatabricksParsingException(
"Unexpected metadata format. Type is not a COMPLEX: " + columnName,
Expand Down Expand Up @@ -1969,7 +1976,11 @@ private <T> T getConvertedObject(
return defaultValue.get();
}
int columnType = resultSetMetaData.getColumnType(columnIndex);
ObjectConverter converter = ConverterHelper.getConverterForSqlType(columnType);
String columnTypeName = resultSetMetaData.getColumnTypeName(columnIndex);

// Use metadata-aware converter selection for proper handling of databricks-specific types
ObjectConverter converter =
ConverterHelper.getConverterForColumnType(columnType, columnTypeName);
return convertMethod.apply(converter, obj);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,7 @@
import static com.databricks.jdbc.common.MetadataResultConstants.LARGE_DISPLAY_COLUMNS;
import static com.databricks.jdbc.common.MetadataResultConstants.REMARKS_COLUMN;
import static com.databricks.jdbc.common.util.DatabricksThriftUtil.*;
import static com.databricks.jdbc.common.util.DatabricksTypeUtil.TIMESTAMP;
import static com.databricks.jdbc.common.util.DatabricksTypeUtil.TIMESTAMP_NTZ;
import static com.databricks.jdbc.common.util.DatabricksTypeUtil.VARIANT;
import static com.databricks.jdbc.common.util.DatabricksTypeUtil.getBasePrecisionAndScale;
import static com.databricks.jdbc.common.util.DatabricksTypeUtil.*;

import com.databricks.jdbc.api.internal.IDatabricksConnectionContext;
import com.databricks.jdbc.common.AccessType;
Expand Down Expand Up @@ -178,6 +175,7 @@ public DatabricksResultSetMetaData(
columnIndex < resultManifest.getSchema().getColumnsSize();
columnIndex++) {
TColumnDesc columnDesc = resultManifest.getSchema().getColumns().get(columnIndex);

ColumnInfo columnInfo = getColumnInfoFromTColumnDesc(columnDesc);
int[] precisionAndScale = getPrecisionAndScale(columnInfo);
int precision = precisionAndScale[0];
Expand Down Expand Up @@ -205,6 +203,16 @@ public DatabricksResultSetMetaData(
.columnTypeClassName("java.lang.String")
.columnType(Types.OTHER)
.columnTypeText(VARIANT);
} else if (isGeometryColumn(arrowMetadata, columnIndex)) {
columnBuilder
.columnTypeClassName(GEOMETRY_CLASS_NAME)
.columnType(Types.OTHER)
.columnTypeText(GEOMETRY);
} else if (isGeographyColumn(arrowMetadata, columnIndex)) {
columnBuilder
.columnTypeClassName(GEOGRAPHY_CLASS_NAME)
.columnType(Types.OTHER)
.columnTypeText(GEOGRAPHY);
}
columnsBuilder.add(columnBuilder.build());
columnNameToIndexMap.putIfAbsent(columnInfo.getName(), ++currIndex);
Expand Down Expand Up @@ -642,6 +650,20 @@ private boolean isVariantColumn(List<String> arrowMetadata, int i) {
&& arrowMetadata.get(i).equalsIgnoreCase(VARIANT);
}

private boolean isGeometryColumn(List<String> arrowMetadata, int index) {
return arrowMetadata != null
Comment thread
sreekanth-db marked this conversation as resolved.
&& arrowMetadata.size() > index
&& arrowMetadata.get(index) != null
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where is this coming from?
what kind of metadata is this ?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed the metadata usage completely (#1062)

&& arrowMetadata.get(index).contains(GEOMETRY);
}

private boolean isGeographyColumn(List<String> arrowMetadata, int index) {
return arrowMetadata != null
&& arrowMetadata.size() > index
&& arrowMetadata.get(index) != null
&& arrowMetadata.get(index).contains(GEOGRAPHY);
}

private ImmutableDatabricksColumn.Builder getColumnBuilder() {
return ImmutableDatabricksColumn.builder()
.isAutoIncrement(false)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package com.databricks.jdbc.api.impl;

import com.databricks.jdbc.exception.DatabricksValidationException;

/**
* Interface for geospatial data types in Databricks JDBC driver.
*
* <p>This interface provides common functionality for both GEOMETRY and GEOGRAPHY types, allowing
* access to Well-Known Text (WKT), Well-Known Binary (WKB) representation and Spatial Reference
* System Identifier (SRID).
*
* <p>Following the established patterns of DatabricksStruct, DatabricksArray, and DatabricksMap,
* this interface enables consistent handling of geospatial data across the JDBC driver.
*/
public interface IDatabricksGeospatial {

/**
* Returns the Well-Known Binary (WKB) representation of the geospatial object.
*
* <p>WKB is a binary format for representing geometry data that is compact and suitable for
* storage and transmission. This method converts the internal representation to WKB format on
* demand.
*
* @return the WKB representation as a byte array
* @throws DatabricksValidationException if WKT to WKB conversion fails
*/
byte[] getWKB() throws DatabricksValidationException;

/**
* Returns the Spatial Reference System Identifier (SRID) of the geospatial object.
*
* <p>SRID identifies the coordinate system used by the geometry. Common values include:
*
* <ul>
* <li>4326 - WGS 84 (World Geodetic System 1984)
* <li>3857 - Web Mercator
* <li>0 - No SRID specified
* </ul>
*
* @return the SRID value
*/
int getSRID();

/**
* Returns the Well-Known Text (WKT) representation of the geospatial object.
*
* <p>WKT is a human-readable text format for representing geometry data. This provides a
* complement to the binary WKB format, allowing easy inspection and debugging of geospatial data.
*
* @return the WKT string representation
*/
String getWKT();

/**
* Returns the data type of the geospatial object.
*
* @return the type as a string, either "GEOMETRY" or "GEOGRAPHY"
*/
String getType();
}
Loading
Loading