org.osgi
com.databricks.internal.osgi
diff --git a/src/main/java/com/databricks/jdbc/api/IDatabricksGeospatial.java b/src/main/java/com/databricks/jdbc/api/IDatabricksGeospatial.java
new file mode 100644
index 0000000000..d58f8d9f2b
--- /dev/null
+++ b/src/main/java/com/databricks/jdbc/api/IDatabricksGeospatial.java
@@ -0,0 +1,60 @@
+package com.databricks.jdbc.api;
+
+import com.databricks.jdbc.exception.DatabricksValidationException;
+
+/**
+ * Interface for geospatial data types in Databricks JDBC driver.
+ *
+ * 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).
+ *
+ *
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.
+ *
+ *
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.
+ *
+ *
SRID identifies the coordinate system used by the geometry. Common values include:
+ *
+ *
+ * - 4326 - WGS 84 (World Geodetic System 1984)
+ *
- 3857 - Web Mercator
+ *
- 0 - No SRID specified
+ *
+ *
+ * @return the SRID value
+ */
+ int getSRID();
+
+ /**
+ * Returns the Well-Known Text (WKT) representation of the geospatial object.
+ *
+ * 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();
+}
diff --git a/src/main/java/com/databricks/jdbc/api/IGeography.java b/src/main/java/com/databricks/jdbc/api/IGeography.java
new file mode 100644
index 0000000000..bae749bc52
--- /dev/null
+++ b/src/main/java/com/databricks/jdbc/api/IGeography.java
@@ -0,0 +1,4 @@
+package com.databricks.jdbc.api;
+
+/** Interface for GEOGRAPHY data types in Databricks JDBC driver. */
+public interface IGeography extends IDatabricksGeospatial {}
diff --git a/src/main/java/com/databricks/jdbc/api/IGeometry.java b/src/main/java/com/databricks/jdbc/api/IGeometry.java
new file mode 100644
index 0000000000..850eb3af55
--- /dev/null
+++ b/src/main/java/com/databricks/jdbc/api/IGeometry.java
@@ -0,0 +1,4 @@
+package com.databricks.jdbc.api;
+
+/** Interface for GEOMETRY data types in Databricks JDBC driver. */
+public interface IGeometry extends IDatabricksGeospatial {}
diff --git a/src/main/java/com/databricks/jdbc/api/impl/AbstractDatabricksGeospatial.java b/src/main/java/com/databricks/jdbc/api/impl/AbstractDatabricksGeospatial.java
new file mode 100644
index 0000000000..eba7bc4fd4
--- /dev/null
+++ b/src/main/java/com/databricks/jdbc/api/impl/AbstractDatabricksGeospatial.java
@@ -0,0 +1,119 @@
+package com.databricks.jdbc.api.impl;
+
+import com.databricks.jdbc.api.IDatabricksGeospatial;
+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.
+ *
+ *
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 {
+
+ 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");
+ }
+
+ 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);
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * 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();
+}
diff --git a/src/main/java/com/databricks/jdbc/api/impl/DatabricksGeography.java b/src/main/java/com/databricks/jdbc/api/impl/DatabricksGeography.java
new file mode 100644
index 0000000000..20f148c563
--- /dev/null
+++ b/src/main/java/com/databricks/jdbc/api/impl/DatabricksGeography.java
@@ -0,0 +1,25 @@
+package com.databricks.jdbc.api.impl;
+
+import static com.databricks.jdbc.common.util.DatabricksTypeUtil.GEOGRAPHY;
+
+import com.databricks.jdbc.api.IGeography;
+import com.databricks.jdbc.exception.DatabricksValidationException;
+
+public class DatabricksGeography extends AbstractDatabricksGeospatial implements IGeography {
+
+ /**
+ * 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 {
+ super(wkt, srid);
+ }
+
+ @Override
+ public String getType() {
+ return GEOGRAPHY;
+ }
+}
diff --git a/src/main/java/com/databricks/jdbc/api/impl/DatabricksGeometry.java b/src/main/java/com/databricks/jdbc/api/impl/DatabricksGeometry.java
new file mode 100644
index 0000000000..b267e7dd84
--- /dev/null
+++ b/src/main/java/com/databricks/jdbc/api/impl/DatabricksGeometry.java
@@ -0,0 +1,25 @@
+package com.databricks.jdbc.api.impl;
+
+import static com.databricks.jdbc.common.util.DatabricksTypeUtil.GEOMETRY;
+
+import com.databricks.jdbc.api.IGeometry;
+import com.databricks.jdbc.exception.DatabricksValidationException;
+
+public class DatabricksGeometry extends AbstractDatabricksGeospatial implements IGeometry {
+
+ /**
+ * 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;
+ }
+}
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 e45329af48..9a9da2566e 100644
--- a/src/main/java/com/databricks/jdbc/api/impl/DatabricksResultSet.java
+++ b/src/main/java/com/databricks/jdbc/api/impl/DatabricksResultSet.java
@@ -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;
@@ -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
@@ -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,
@@ -1969,7 +1976,11 @@ private 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);
}
diff --git a/src/main/java/com/databricks/jdbc/api/impl/DatabricksResultSetMetaData.java b/src/main/java/com/databricks/jdbc/api/impl/DatabricksResultSetMetaData.java
index 3afc6e383f..6836257872 100644
--- a/src/main/java/com/databricks/jdbc/api/impl/DatabricksResultSetMetaData.java
+++ b/src/main/java/com/databricks/jdbc/api/impl/DatabricksResultSetMetaData.java
@@ -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;
@@ -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];
@@ -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);
@@ -642,6 +650,20 @@ private boolean isVariantColumn(List arrowMetadata, int i) {
&& arrowMetadata.get(i).equalsIgnoreCase(VARIANT);
}
+ private boolean isGeometryColumn(List arrowMetadata, int index) {
+ return arrowMetadata != null
+ && arrowMetadata.size() > index
+ && arrowMetadata.get(index) != null
+ && arrowMetadata.get(index).contains(GEOMETRY);
+ }
+
+ private boolean isGeographyColumn(List 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)
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 29a88fd6ba..c8252b800f 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
@@ -160,7 +160,7 @@ public Object getObject(int columnIndex) throws DatabricksSQLException {
}
/**
- * Checks if the given type is a complex type (ARRAY, MAP, or STRUCT).
+ * Checks if the given type is a complex type (ARRAY, MAP, STRUCT, GEOMETRY, or GEOGRAPHY).
*
* @param type The type to check
* @return true if the type is a complex type, false otherwise
@@ -169,7 +169,9 @@ 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.STRUCT
+ || type == ColumnInfoTypeName.GEOMETRY
+ || type == ColumnInfoTypeName.GEOGRAPHY;
}
/** {@inheritDoc} */
diff --git a/src/main/java/com/databricks/jdbc/api/impl/converters/ArrowToJavaObjectConverter.java b/src/main/java/com/databricks/jdbc/api/impl/converters/ArrowToJavaObjectConverter.java
index 42be51d2c3..d39a4511af 100644
--- a/src/main/java/com/databricks/jdbc/api/impl/converters/ArrowToJavaObjectConverter.java
+++ b/src/main/java/com/databricks/jdbc/api/impl/converters/ArrowToJavaObjectConverter.java
@@ -1,10 +1,6 @@
package com.databricks.jdbc.api.impl.converters;
-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.TIMESTAMP;
-import static com.databricks.jdbc.common.util.DatabricksTypeUtil.VARIANT;
+import static com.databricks.jdbc.common.util.DatabricksTypeUtil.*;
import com.databricks.jdbc.api.impl.*;
import com.databricks.jdbc.exception.DatabricksParsingException;
@@ -14,6 +10,7 @@
import com.databricks.jdbc.log.JdbcLoggerFactory;
import com.databricks.jdbc.model.core.ColumnInfo;
import com.databricks.jdbc.model.core.ColumnInfoTypeName;
+import com.databricks.jdbc.model.telemetry.enums.DatabricksDriverErrorCode;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.sql.Date;
@@ -25,6 +22,8 @@
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import org.apache.arrow.vector.TimeStampMicroTZVector;
import org.apache.arrow.vector.ValueVector;
import org.apache.arrow.vector.util.Text;
@@ -32,6 +31,11 @@
public class ArrowToJavaObjectConverter {
private static final JdbcLogger LOGGER =
JdbcLoggerFactory.getLogger(ArrowToJavaObjectConverter.class);
+
+ // Pre-compiled patterns for SRID extraction from metadata
+ private static final Pattern GEOMETRY_SRID_PATTERN = Pattern.compile("GEOMETRY\\((\\d+)\\)");
+ private static final Pattern GEOGRAPHY_SRID_PATTERN = Pattern.compile("GEOGRAPHY\\((\\d+)\\)");
+
private static final List DATE_FORMATTERS =
Arrays.asList(
DateTimeFormatter.ofPattern("yyyy-MM-dd"),
@@ -86,6 +90,12 @@ public static Object convert(
if (arrowMetadata.startsWith(TIMESTAMP)) { // for timestamp_ntz column
requiredType = ColumnInfoTypeName.TIMESTAMP;
}
+ if (arrowMetadata.startsWith(GEOMETRY)) {
+ requiredType = ColumnInfoTypeName.GEOMETRY;
+ }
+ if (arrowMetadata.startsWith(GEOGRAPHY)) {
+ requiredType = ColumnInfoTypeName.GEOGRAPHY;
+ }
}
if (object == null) {
return null;
@@ -136,6 +146,9 @@ public static Object convert(
}
IntervalConverter ic = new IntervalConverter(arrowMetadata);
return ic.toLiteral(object);
+ case GEOMETRY:
+ case GEOGRAPHY:
+ return convertToGeospatial(object, arrowMetadata, requiredType);
case NULL:
return null;
default:
@@ -163,6 +176,26 @@ private static Object convertToStruct(Object object, String arrowMetadata)
return parser.parseJsonStringToDbStruct(object.toString(), arrowMetadata);
}
+ private static AbstractDatabricksGeospatial convertToGeospatial(
+ Object object, String arrowMetadata, ColumnInfoTypeName type) throws DatabricksSQLException {
+ String ewkt = convertToString(object);
+
+ // Parse EWKT to extract SRID from data if present
+ int dataSrid = WKTConverter.extractSRIDFromEWKT(ewkt);
+ String cleanWkt = WKTConverter.removeSRIDFromEWKT(ewkt);
+
+ // Extract SRID from metadata if not present in data
+ int finalSrid = dataSrid;
+ if (dataSrid == 0) {
+ String typeName = type == ColumnInfoTypeName.GEOMETRY ? GEOMETRY : GEOGRAPHY;
+ finalSrid = extractSRIDFromMetadata(arrowMetadata, typeName);
+ }
+
+ return type == ColumnInfoTypeName.GEOMETRY
+ ? new DatabricksGeometry(cleanWkt, finalSrid)
+ : new DatabricksGeography(cleanWkt, finalSrid);
+ }
+
private static Object convertToTimestamp(Object object, Optional timeZoneOpt)
throws DatabricksSQLException {
if (object instanceof Text) {
@@ -294,4 +327,43 @@ private static T convertToNumber(
LOGGER.error(errorMessage);
throw new DatabricksValidationException(errorMessage);
}
+
+ /**
+ * Extracts SRID from Arrow metadata string.
+ *
+ * @param metadata Arrow metadata like "GEOMETRY(32633)" or "GEOGRAPHY(4326)"
+ * @param typePrefix The prefix to look for ("GEOMETRY" or "GEOGRAPHY")
+ * @return SRID value, or 0 if not found
+ * @throws DatabricksParsingException if metadata format is invalid
+ */
+ private static int extractSRIDFromMetadata(String metadata, String typePrefix)
+ throws DatabricksParsingException {
+ if (metadata == null) {
+ LOGGER.debug("Metadata is null, returning default SRID 0 for {}", typePrefix);
+ return 0;
+ }
+
+ try {
+ // Look for pattern like "GEOMETRY(32633)" or "GEOGRAPHY(4326)"
+ Pattern pattern =
+ typePrefix.equals(GEOMETRY) ? GEOMETRY_SRID_PATTERN : GEOGRAPHY_SRID_PATTERN;
+ Matcher m = pattern.matcher(metadata);
+
+ if (m.find()) {
+ return Integer.parseInt(m.group(1));
+ }
+ } catch (Exception e) {
+ String errorMessage =
+ String.format("Failed to parse SRID from %s metadata: %s", typePrefix, metadata);
+ LOGGER.error(errorMessage, e);
+ throw new DatabricksParsingException(
+ errorMessage, e, DatabricksDriverErrorCode.RESULT_SET_ERROR);
+ }
+
+ LOGGER.debug(
+ "No SRID found in metadata for {}, returning default SRID 0. Metadata: {}",
+ typePrefix,
+ metadata);
+ return 0;
+ }
}
diff --git a/src/main/java/com/databricks/jdbc/api/impl/converters/ConverterHelper.java b/src/main/java/com/databricks/jdbc/api/impl/converters/ConverterHelper.java
index 6815e609f3..dcefd5330a 100644
--- a/src/main/java/com/databricks/jdbc/api/impl/converters/ConverterHelper.java
+++ b/src/main/java/com/databricks/jdbc/api/impl/converters/ConverterHelper.java
@@ -1,5 +1,10 @@
package com.databricks.jdbc.api.impl.converters;
+import static com.databricks.jdbc.common.util.DatabricksTypeUtil.GEOGRAPHY;
+import static com.databricks.jdbc.common.util.DatabricksTypeUtil.GEOMETRY;
+
+import com.databricks.jdbc.api.impl.DatabricksGeography;
+import com.databricks.jdbc.api.impl.DatabricksGeometry;
import com.databricks.jdbc.exception.DatabricksSQLException;
import java.math.BigDecimal;
import java.math.BigInteger;
@@ -12,6 +17,7 @@ public class ConverterHelper {
private static final Map CONVERTER_CACHE = new HashMap<>();
private static final Map> SUPPORTED_CONVERSIONS = new HashMap<>();
+ private static final GeospatialConverter GEOSPATIAL_CONVERTER = new GeospatialConverter();
static {
// Numeric Types
@@ -506,6 +512,10 @@ public static Object convertSqlTypeToSpecificJavaType(
return converter.toDatabricksArray(obj);
} else if (javaType == Struct.class) {
return converter.toDatabricksStruct(obj);
+ } else if (javaType == DatabricksGeometry.class) {
+ return converter.toDatabricksGeometry(obj);
+ } else if (javaType == DatabricksGeography.class) {
+ return converter.toDatabricksGeography(obj);
}
return converter.toString(obj); // By default, convert to string
}
@@ -516,10 +526,30 @@ public static Object convertSqlTypeToSpecificJavaType(
* @param columnSqlType The SQL type of the column, as defined in java.sql.Types
* @return An ObjectConverter suitable for the specified SQL type
*/
+ // TODO: replace all usages of this method with getConverterForColumnType
public static ObjectConverter getConverterForSqlType(int columnSqlType) {
return CONVERTER_CACHE.getOrDefault(columnSqlType, CONVERTER_CACHE.get(Types.VARCHAR));
}
+ /**
+ * Retrieves the appropriate ObjectConverter for a given SQL type and column type name. This
+ * method provides metadata-aware converter selection, checking the actual column type name first
+ * for database-specific types before falling back to SQL type-based selection.
+ *
+ * @param columnSqlType The SQL type of the column, as defined in java.sql.Types
+ * @param columnTypeName The actual column type name from metadata (e.g., "GEOMETRY", "GEOGRAPHY")
+ * @return An ObjectConverter suitable for the specified column type
+ */
+ public static ObjectConverter getConverterForColumnType(
+ int columnSqlType, String columnTypeName) {
+ if (columnTypeName != null) {
+ if (columnTypeName.equals(GEOMETRY) || columnTypeName.equals(GEOGRAPHY)) {
+ return GEOSPATIAL_CONVERTER;
+ }
+ }
+ return getConverterForSqlType(columnSqlType);
+ }
+
public static boolean isConversionSupported(int fromType, int toType) {
return SUPPORTED_CONVERSIONS.containsKey(fromType)
&& SUPPORTED_CONVERSIONS.get(fromType).contains(toType);
diff --git a/src/main/java/com/databricks/jdbc/api/impl/converters/GeospatialConverter.java b/src/main/java/com/databricks/jdbc/api/impl/converters/GeospatialConverter.java
new file mode 100644
index 0000000000..69d99e63bf
--- /dev/null
+++ b/src/main/java/com/databricks/jdbc/api/impl/converters/GeospatialConverter.java
@@ -0,0 +1,81 @@
+package com.databricks.jdbc.api.impl.converters;
+
+import static com.databricks.jdbc.common.util.DatabricksTypeUtil.GEOGRAPHY;
+import static com.databricks.jdbc.common.util.DatabricksTypeUtil.GEOMETRY;
+
+import com.databricks.jdbc.api.IDatabricksGeospatial;
+import com.databricks.jdbc.api.impl.DatabricksGeography;
+import com.databricks.jdbc.api.impl.DatabricksGeometry;
+import com.databricks.jdbc.exception.DatabricksSQLException;
+import com.databricks.jdbc.log.JdbcLogger;
+import com.databricks.jdbc.log.JdbcLoggerFactory;
+import com.databricks.jdbc.model.telemetry.enums.DatabricksDriverErrorCode;
+import org.apache.arrow.vector.util.Text;
+
+public class GeospatialConverter implements ObjectConverter {
+
+ private static final JdbcLogger LOGGER = JdbcLoggerFactory.getLogger(GeospatialConverter.class);
+
+ @Override
+ public DatabricksGeometry toDatabricksGeometry(Object object) throws DatabricksSQLException {
+ if (object instanceof DatabricksGeometry) {
+ return (DatabricksGeometry) object;
+ }
+ return convertToGeospatial(object, GEOMETRY, DatabricksGeometry::new);
+ }
+
+ @Override
+ public DatabricksGeography toDatabricksGeography(Object object) throws DatabricksSQLException {
+ if (object instanceof DatabricksGeography) {
+ return (DatabricksGeography) object;
+ }
+ return convertToGeospatial(object, GEOGRAPHY, DatabricksGeography::new);
+ }
+
+ private T convertToGeospatial(
+ Object object, String typeName, GeospatialFactory factory) throws DatabricksSQLException {
+ if (object instanceof String || object instanceof Text) {
+ String ewktString = object.toString();
+ try {
+ int srid = WKTConverter.extractSRIDFromEWKT(ewktString);
+ String cleanWKT = WKTConverter.removeSRIDFromEWKT(ewktString);
+ return factory.create(cleanWKT, srid);
+ } catch (Exception e) {
+ String errorMessage =
+ String.format("Failed to convert EWKT to %s: %s", typeName, ewktString);
+ LOGGER.warn(errorMessage, e);
+ throw new DatabricksSQLException(errorMessage, e, DatabricksDriverErrorCode.INVALID_STATE);
+ }
+ }
+
+ throw new DatabricksSQLException(
+ String.format(
+ "Unsupported %s conversion from type: %s",
+ typeName.substring(0, 1).toUpperCase() + typeName.substring(1), object.getClass()),
+ DatabricksDriverErrorCode.UNSUPPORTED_OPERATION);
+ }
+
+ @FunctionalInterface
+ private interface GeospatialFactory {
+ T create(String wkt, int srid) throws Exception;
+ }
+
+ @Override
+ public String toString(Object object) throws DatabricksSQLException {
+ if (object != null) {
+ return object.toString();
+ }
+ throw new DatabricksSQLException(
+ "Cannot convert null to String", DatabricksDriverErrorCode.UNSUPPORTED_OPERATION);
+ }
+
+ @Override
+ public byte[] toByteArray(Object object) throws DatabricksSQLException {
+ if (object instanceof IDatabricksGeospatial) {
+ return ((IDatabricksGeospatial) object).getWKB();
+ }
+ throw new DatabricksSQLException(
+ "Unsupported byte array conversion operation for geospatial types",
+ DatabricksDriverErrorCode.UNSUPPORTED_OPERATION);
+ }
+}
diff --git a/src/main/java/com/databricks/jdbc/api/impl/converters/ObjectConverter.java b/src/main/java/com/databricks/jdbc/api/impl/converters/ObjectConverter.java
index c1df874bed..ecf8fdc9b6 100644
--- a/src/main/java/com/databricks/jdbc/api/impl/converters/ObjectConverter.java
+++ b/src/main/java/com/databricks/jdbc/api/impl/converters/ObjectConverter.java
@@ -1,6 +1,8 @@
package com.databricks.jdbc.api.impl.converters;
import com.databricks.jdbc.api.impl.DatabricksArray;
+import com.databricks.jdbc.api.impl.DatabricksGeography;
+import com.databricks.jdbc.api.impl.DatabricksGeometry;
import com.databricks.jdbc.api.impl.DatabricksMap;
import com.databricks.jdbc.api.impl.DatabricksStruct;
import com.databricks.jdbc.exception.DatabricksSQLException;
@@ -141,6 +143,24 @@ default DatabricksStruct toDatabricksStruct(Object object) throws DatabricksSQLE
"Unsupported Struct conversion operation", DatabricksDriverErrorCode.UNSUPPORTED_OPERATION);
}
+ default DatabricksGeometry toDatabricksGeometry(Object object) throws DatabricksSQLException {
+ if (object instanceof DatabricksGeometry) {
+ return (DatabricksGeometry) object;
+ }
+ throw new DatabricksSQLException(
+ "Unsupported Geometry conversion operation",
+ DatabricksDriverErrorCode.UNSUPPORTED_OPERATION);
+ }
+
+ default DatabricksGeography toDatabricksGeography(Object object) throws DatabricksSQLException {
+ if (object instanceof DatabricksGeography) {
+ return (DatabricksGeography) object;
+ }
+ throw new DatabricksSQLException(
+ "Unsupported Geography conversion operation",
+ DatabricksDriverErrorCode.UNSUPPORTED_OPERATION);
+ }
+
default InputStream toBinaryStream(Object object) throws DatabricksSQLException {
try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream)) {
diff --git a/src/main/java/com/databricks/jdbc/api/impl/converters/WKTConverter.java b/src/main/java/com/databricks/jdbc/api/impl/converters/WKTConverter.java
new file mode 100644
index 0000000000..52ccae52d5
--- /dev/null
+++ b/src/main/java/com/databricks/jdbc/api/impl/converters/WKTConverter.java
@@ -0,0 +1,132 @@
+package com.databricks.jdbc.api.impl.converters;
+
+import com.databricks.jdbc.exception.DatabricksValidationException;
+import com.databricks.jdbc.log.JdbcLogger;
+import com.databricks.jdbc.log.JdbcLoggerFactory;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.io.ParseException;
+import org.locationtech.jts.io.WKBReader;
+import org.locationtech.jts.io.WKBWriter;
+import org.locationtech.jts.io.WKTReader;
+import org.locationtech.jts.io.WKTWriter;
+
+/**
+ * Utility class for converting between WKT (Well-Known Text) and WKB (Well-Known Binary) formats.
+ *
+ * This class uses the JTS (Java Topology Suite) library to provide robust WKT/WKB conversion
+ * functionality for geospatial data. JTS is a widely-used, well-tested library that implements the
+ * OpenGIS Consortium's Simple Features Specification for SQL.
+ */
+public class WKTConverter {
+
+ private static final JdbcLogger LOGGER = JdbcLoggerFactory.getLogger(WKTConverter.class);
+
+ private static final ThreadLocal WKT_READER = ThreadLocal.withInitial(WKTReader::new);
+ private static final ThreadLocal WKT_WRITER = ThreadLocal.withInitial(WKTWriter::new);
+ private static final ThreadLocal WKB_READER = ThreadLocal.withInitial(WKBReader::new);
+ private static final ThreadLocal WKB_WRITER = ThreadLocal.withInitial(WKBWriter::new);
+
+ /**
+ * Converts WKT (Well-Known Text) to WKB (Well-Known Binary) format.
+ *
+ * This implementation uses the JTS library to parse the WKT string into a Geometry object and
+ * then converts it to WKB format. This provides robust, standards-compliant conversion.
+ *
+ * @param wkt the WKT string to convert
+ * @return the WKB representation as a byte array
+ * @throws DatabricksValidationException if the WKT is invalid
+ */
+ public static byte[] toWKB(String wkt) throws DatabricksValidationException {
+ if (wkt == null || wkt.trim().isEmpty()) {
+ throw new DatabricksValidationException("WKT string cannot be null or empty");
+ }
+
+ try {
+ Geometry geometry = WKT_READER.get().read(wkt);
+ return WKB_WRITER.get().write(geometry);
+ } catch (ParseException e) {
+ String errorMessage = String.format("Invalid WKT format: %s", wkt);
+ LOGGER.error(errorMessage, e);
+ throw new DatabricksValidationException(errorMessage, e);
+ }
+ }
+
+ /**
+ * Converts WKB (Well-Known Binary) to WKT (Well-Known Text) format.
+ *
+ *
This implementation uses the JTS library to parse the WKB bytes into a Geometry object and
+ * then converts it to WKT format.
+ *
+ * @param wkb the WKB bytes to convert
+ * @return the WKT representation as a string
+ * @throws DatabricksValidationException if the WKB is invalid
+ */
+ public static String toWKT(byte[] wkb) throws DatabricksValidationException {
+ if (wkb == null || wkb.length == 0) {
+ throw new DatabricksValidationException("WKB bytes cannot be null or empty");
+ }
+
+ try {
+ Geometry geometry = WKB_READER.get().read(wkb);
+ return WKT_WRITER.get().write(geometry);
+ } catch (Exception e) {
+ String errorMessage = String.format("Invalid WKB format: %d bytes", wkb.length);
+ LOGGER.error(errorMessage, e);
+ throw new DatabricksValidationException(errorMessage, e);
+ }
+ }
+
+ /**
+ * Extracts the SRID from an EWKT (Extended Well-Known Text) string.
+ *
+ *
EWKT format includes SRID prefix: "SRID=4326;POINT(1 2)"
+ *
+ * @param ewkt the EWKT string
+ * @return the SRID value, or 0 if no SRID is specified
+ */
+ public static int extractSRIDFromEWKT(String ewkt) {
+ if (ewkt == null || ewkt.trim().isEmpty()) {
+ return 0;
+ }
+
+ String trimmed = ewkt.trim();
+ if (trimmed.startsWith("SRID=")) {
+ int semicolonIndex = trimmed.indexOf(';');
+ if (semicolonIndex > 0) {
+ try {
+ String sridStr = trimmed.substring(5, semicolonIndex);
+ return Integer.parseInt(sridStr);
+ } catch (NumberFormatException e) {
+ LOGGER.warn("Invalid SRID format in EWKT: {}", ewkt);
+ return 0;
+ }
+ }
+ }
+
+ return 0; // Default SRID if not specified
+ }
+
+ /**
+ * Removes the SRID prefix from an EWKT string to get clean WKT.
+ *
+ *
Converts "SRID=4326;POINT(1 2)" to "POINT(1 2)"
+ *
+ * @param ewkt the EWKT string
+ * @return the clean WKT string without SRID prefix
+ */
+ public static String removeSRIDFromEWKT(String ewkt) {
+ if (ewkt == null || ewkt.trim().isEmpty()) {
+ return ewkt;
+ }
+
+ String trimmed = ewkt.trim();
+ if (trimmed.startsWith("SRID=")) {
+ int semicolonIndex = trimmed.indexOf(';');
+ if (semicolonIndex > 0) {
+ return trimmed.substring(semicolonIndex + 1);
+ }
+ }
+
+ return trimmed; // Return as-is if no SRID prefix
+ }
+}
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 d402a4ee0f..0610776a31 100644
--- a/src/main/java/com/databricks/jdbc/common/util/DatabricksTypeUtil.java
+++ b/src/main/java/com/databricks/jdbc/common/util/DatabricksTypeUtil.java
@@ -55,6 +55,10 @@ public class DatabricksTypeUtil {
public static final String VARIANT = "VARIANT";
public static final String CHAR = "CHAR";
public static final String INTERVAL = "INTERVAL";
+ public static final String GEOMETRY = "GEOMETRY";
+ public static final String GEOGRAPHY = "GEOGRAPHY";
+ public static final String GEOMETRY_CLASS_NAME = "com.databricks.jdbc.api.IGeometry";
+ public static final String GEOGRAPHY_CLASS_NAME = "com.databricks.jdbc.api.IGeography";
public static final String MEASURE = "measure";
private static final ArrayList SIGNED_TYPES =
new ArrayList<>(
@@ -147,6 +151,8 @@ public static int getColumnType(ColumnInfoTypeName typeName) {
return Types.STRUCT;
case ARRAY:
return Types.ARRAY;
+ case GEOMETRY:
+ case GEOGRAPHY:
case USER_DEFINED_TYPE:
return Types.OTHER;
default:
@@ -190,6 +196,10 @@ public static String getColumnTypeClassName(ColumnInfoTypeName typeName) {
return "java.sql.Struct";
case ARRAY:
return "java.sql.Array";
+ case GEOMETRY:
+ return GEOMETRY_CLASS_NAME;
+ case GEOGRAPHY:
+ return GEOGRAPHY_CLASS_NAME;
case NULL:
return "null";
case MAP:
@@ -252,6 +262,8 @@ public static int getDisplaySize(ColumnInfoTypeName typeName, int precision, int
return 4; // Length of `NULL`
case ARRAY:
case STRUCT:
+ case GEOMETRY:
+ case GEOGRAPHY:
default:
return 255;
}
diff --git a/src/test/java/com/databricks/jdbc/api/impl/DatabricksGeographyTest.java b/src/test/java/com/databricks/jdbc/api/impl/DatabricksGeographyTest.java
new file mode 100644
index 0000000000..fc62f2b052
--- /dev/null
+++ b/src/test/java/com/databricks/jdbc/api/impl/DatabricksGeographyTest.java
@@ -0,0 +1,165 @@
+package com.databricks.jdbc.api.impl;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import com.databricks.jdbc.exception.DatabricksValidationException;
+import org.junit.jupiter.api.Test;
+
+/** Test class for DatabricksGeography. Reference: DatabricksGeometryTest.java */
+public class DatabricksGeographyTest {
+
+ // ===================================================================================
+ // Constructor tests
+ // ===================================================================================
+
+ @Test
+ public void testConstructor_WithValidPoint() throws DatabricksValidationException {
+ DatabricksGeography geography = new DatabricksGeography("POINT(-122.4194 37.7749)", 0);
+ assertNotNull(geography);
+ assertEquals("POINT(-122.4194 37.7749)", geography.getWKT());
+ assertEquals(0, geography.getSRID());
+ }
+
+ @Test
+ public void testConstructor_WithValidPointAndSRID() throws DatabricksValidationException {
+ DatabricksGeography geography = new DatabricksGeography("POINT(-122.4194 37.7749)", 4326);
+ assertNotNull(geography);
+ assertEquals("POINT(-122.4194 37.7749)", geography.getWKT());
+ assertEquals(4326, geography.getSRID());
+ }
+
+ @Test
+ public void testConstructor_WithLineString() throws DatabricksValidationException {
+ DatabricksGeography geography =
+ new DatabricksGeography("LINESTRING(-122.4 37.7, -122.5 37.8, -122.6 37.9)", 4326);
+ assertNotNull(geography);
+ assertTrue(geography.getWKT().startsWith("LINESTRING"));
+ }
+
+ @Test
+ public void testConstructor_WithPolygon() throws DatabricksValidationException {
+ DatabricksGeography geography =
+ new DatabricksGeography(
+ "POLYGON((-122.4 37.7, -122.5 37.7, -122.5 37.8, -122.4 37.8, -122.4 37.7))", 4326);
+ assertNotNull(geography);
+ assertTrue(geography.getWKT().startsWith("POLYGON"));
+ }
+
+ @Test
+ public void testConstructor_WithNullWKT_ThrowsException() {
+ assertThrows(DatabricksValidationException.class, () -> new DatabricksGeography(null, 0));
+ }
+
+ @Test
+ public void testConstructor_WithEmptyWKT_ThrowsException() {
+ assertThrows(DatabricksValidationException.class, () -> new DatabricksGeography("", 0));
+ }
+
+ @Test
+ public void testConstructor_WithWhitespaceWKT_ThrowsException() {
+ assertThrows(DatabricksValidationException.class, () -> new DatabricksGeography(" ", 0));
+ }
+
+ // ===================================================================================
+ // Accessor tests
+ // ===================================================================================
+
+ @Test
+ public void testGetWkt() throws DatabricksValidationException {
+ DatabricksGeography geography = new DatabricksGeography("POINT(-122.4194 37.7749)", 4326);
+ assertEquals("POINT(-122.4194 37.7749)", geography.getWKT());
+ }
+
+ @Test
+ public void testGetSrid_WithZero() throws DatabricksValidationException {
+ DatabricksGeography geography = new DatabricksGeography("POINT(-122.4194 37.7749)", 0);
+ assertEquals(0, geography.getSRID());
+ }
+
+ @Test
+ public void testGetSrid_With3857() throws DatabricksValidationException {
+ DatabricksGeography geography = new DatabricksGeography("POINT(-122.4194 37.7749)", 3857);
+ assertEquals(3857, geography.getSRID());
+ }
+
+ @Test
+ public void testGetWkb_ReturnsValidBytes() throws DatabricksValidationException {
+ DatabricksGeography geography = new DatabricksGeography("POINT(-122.4194 37.7749)", 4326);
+ byte[] wkb = geography.getWKB();
+ assertNotNull(wkb);
+ assertTrue(wkb.length > 0);
+ }
+
+ // ===================================================================================
+ // toString tests
+ // ===================================================================================
+
+ @Test
+ public void testToString_WithZeroSRID() throws DatabricksValidationException {
+ DatabricksGeography geography = new DatabricksGeography("POINT(-122.4194 37.7749)", 0);
+ assertEquals("SRID=0;POINT(-122.4194 37.7749)", geography.toString());
+ }
+
+ @Test
+ public void testToString_With4326() throws DatabricksValidationException {
+ DatabricksGeography geography = new DatabricksGeography("POINT(-122.4194 37.7749)", 4326);
+ assertEquals("SRID=4326;POINT(-122.4194 37.7749)", geography.toString());
+ }
+
+ // ===================================================================================
+ // equals and hashCode tests
+ // ===================================================================================
+
+ @Test
+ public void testEquals_SameWKTAndSRID() throws DatabricksValidationException {
+ DatabricksGeography geo1 = new DatabricksGeography("POINT(-122.4194 37.7749)", 4326);
+ DatabricksGeography geo2 = new DatabricksGeography("POINT(-122.4194 37.7749)", 4326);
+ assertEquals(geo1, geo2);
+ }
+
+ @Test
+ public void testEquals_DifferentWKT() throws DatabricksValidationException {
+ DatabricksGeography geo1 = new DatabricksGeography("POINT(-122.4194 37.7749)", 4326);
+ DatabricksGeography geo2 = new DatabricksGeography("POINT(-118.2437 34.0522)", 4326);
+ assertNotEquals(geo1, geo2);
+ }
+
+ @Test
+ public void testEquals_DifferentSRID() throws DatabricksValidationException {
+ DatabricksGeography geo1 = new DatabricksGeography("POINT(-122.4194 37.7749)", 4326);
+ DatabricksGeography geo2 = new DatabricksGeography("POINT(-122.4194 37.7749)", 3857);
+ assertNotEquals(geo1, geo2);
+ }
+
+ @Test
+ public void testEquals_SameInstance() throws DatabricksValidationException {
+ DatabricksGeography geography = new DatabricksGeography("POINT(-122.4194 37.7749)", 4326);
+ assertEquals(geography, geography);
+ }
+
+ @Test
+ public void testEquals_WithNull() throws DatabricksValidationException {
+ DatabricksGeography geography = new DatabricksGeography("POINT(-122.4194 37.7749)", 4326);
+ assertNotEquals(geography, null);
+ }
+
+ @Test
+ public void testEquals_WithDifferentClass() throws DatabricksValidationException {
+ DatabricksGeography geography = new DatabricksGeography("POINT(-122.4194 37.7749)", 4326);
+ assertNotEquals(geography, "POINT(-122.4194 37.7749)");
+ }
+
+ @Test
+ public void testHashCode_EqualObjects() throws DatabricksValidationException {
+ DatabricksGeography geo1 = new DatabricksGeography("POINT(-122.4194 37.7749)", 4326);
+ DatabricksGeography geo2 = new DatabricksGeography("POINT(-122.4194 37.7749)", 4326);
+ assertEquals(geo1.hashCode(), geo2.hashCode());
+ }
+
+ @Test
+ public void testHashCode_DifferentObjects() throws DatabricksValidationException {
+ DatabricksGeography geo1 = new DatabricksGeography("POINT(-122.4194 37.7749)", 4326);
+ DatabricksGeography geo2 = new DatabricksGeography("POINT(-118.2437 34.0522)", 4326);
+ assertNotEquals(geo1.hashCode(), geo2.hashCode());
+ }
+}
diff --git a/src/test/java/com/databricks/jdbc/api/impl/DatabricksGeometryTest.java b/src/test/java/com/databricks/jdbc/api/impl/DatabricksGeometryTest.java
new file mode 100644
index 0000000000..fedfbbd877
--- /dev/null
+++ b/src/test/java/com/databricks/jdbc/api/impl/DatabricksGeometryTest.java
@@ -0,0 +1,169 @@
+package com.databricks.jdbc.api.impl;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import com.databricks.jdbc.exception.DatabricksValidationException;
+import org.junit.jupiter.api.Test;
+
+/** Test class for DatabricksGeometry. Reference: DatabricksArrayTest.java */
+public class DatabricksGeometryTest {
+
+ // ===================================================================================
+ // Constructor tests
+ // ===================================================================================
+
+ @Test
+ public void testConstructor_WithValidPoint() throws DatabricksValidationException {
+ DatabricksGeometry geometry = new DatabricksGeometry("POINT(1 2)", 0);
+ assertNotNull(geometry);
+ assertEquals("POINT(1 2)", geometry.getWKT());
+ assertEquals(0, geometry.getSRID());
+ }
+
+ @Test
+ public void testConstructor_WithValidPointAndSRID() throws DatabricksValidationException {
+ DatabricksGeometry geometry = new DatabricksGeometry("POINT(1 2)", 4326);
+ assertNotNull(geometry);
+ assertEquals("POINT(1 2)", geometry.getWKT());
+ assertEquals(4326, geometry.getSRID());
+ }
+
+ @Test
+ public void testConstructor_WithLineString() throws DatabricksValidationException {
+ DatabricksGeometry geometry = new DatabricksGeometry("LINESTRING(0 0, 10 10, 20 20)", 0);
+ assertNotNull(geometry);
+ assertTrue(geometry.getWKT().startsWith("LINESTRING"));
+ }
+
+ @Test
+ public void testConstructor_WithPolygon() throws DatabricksValidationException {
+ DatabricksGeometry geometry =
+ new DatabricksGeometry("POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))", 0);
+ assertNotNull(geometry);
+ assertTrue(geometry.getWKT().startsWith("POLYGON"));
+ }
+
+ @Test
+ public void testConstructor_WithNullWKT_ThrowsException() {
+ assertThrows(DatabricksValidationException.class, () -> new DatabricksGeometry(null, 0));
+ }
+
+ @Test
+ public void testConstructor_WithEmptyWKT_ThrowsException() {
+ assertThrows(DatabricksValidationException.class, () -> new DatabricksGeometry("", 0));
+ }
+
+ @Test
+ public void testConstructor_WithWhitespaceWKT_ThrowsException() {
+ assertThrows(DatabricksValidationException.class, () -> new DatabricksGeometry(" ", 0));
+ }
+
+ // ===================================================================================
+ // Accessor tests
+ // ===================================================================================
+
+ @Test
+ public void testGetWkt() throws DatabricksValidationException {
+ DatabricksGeometry geometry = new DatabricksGeometry("POINT(5 10)", 4326);
+ assertEquals("POINT(5 10)", geometry.getWKT());
+ }
+
+ @Test
+ public void testGetSrid_WithZero() throws DatabricksValidationException {
+ DatabricksGeometry geometry = new DatabricksGeometry("POINT(1 2)", 0);
+ assertEquals(0, geometry.getSRID());
+ }
+
+ @Test
+ public void testGetSrid_With4326() throws DatabricksValidationException {
+ DatabricksGeometry geometry = new DatabricksGeometry("POINT(1 2)", 4326);
+ assertEquals(4326, geometry.getSRID());
+ }
+
+ @Test
+ public void testGetSrid_With3857() throws DatabricksValidationException {
+ DatabricksGeometry geometry = new DatabricksGeometry("POINT(1 2)", 3857);
+ assertEquals(3857, geometry.getSRID());
+ }
+
+ @Test
+ public void testGetWkb_ReturnsValidBytes() throws DatabricksValidationException {
+ DatabricksGeometry geometry = new DatabricksGeometry("POINT(1 2)", 4326);
+ byte[] wkb = geometry.getWKB();
+ assertNotNull(wkb);
+ assertTrue(wkb.length > 0);
+ }
+
+ // ===================================================================================
+ // toString tests
+ // ===================================================================================
+
+ @Test
+ public void testToString_WithZeroSRID() throws DatabricksValidationException {
+ DatabricksGeometry geometry = new DatabricksGeometry("POINT(1 2)", 0);
+ assertEquals("SRID=0;POINT(1 2)", geometry.toString());
+ }
+
+ @Test
+ public void testToString_With4326() throws DatabricksValidationException {
+ DatabricksGeometry geometry = new DatabricksGeometry("POINT(1 2)", 4326);
+ assertEquals("SRID=4326;POINT(1 2)", geometry.toString());
+ }
+
+ // ===================================================================================
+ // equals and hashCode tests
+ // ===================================================================================
+
+ @Test
+ public void testEquals_SameWKTAndSRID() throws DatabricksValidationException {
+ DatabricksGeometry geo1 = new DatabricksGeometry("POINT(1 2)", 4326);
+ DatabricksGeometry geo2 = new DatabricksGeometry("POINT(1 2)", 4326);
+ assertEquals(geo1, geo2);
+ }
+
+ @Test
+ public void testEquals_DifferentWKT() throws DatabricksValidationException {
+ DatabricksGeometry geo1 = new DatabricksGeometry("POINT(1 2)", 4326);
+ DatabricksGeometry geo2 = new DatabricksGeometry("POINT(3 4)", 4326);
+ assertNotEquals(geo1, geo2);
+ }
+
+ @Test
+ public void testEquals_DifferentSRID() throws DatabricksValidationException {
+ DatabricksGeometry geo1 = new DatabricksGeometry("POINT(1 2)", 4326);
+ DatabricksGeometry geo2 = new DatabricksGeometry("POINT(1 2)", 3857);
+ assertNotEquals(geo1, geo2);
+ }
+
+ @Test
+ public void testEquals_SameInstance() throws DatabricksValidationException {
+ DatabricksGeometry geometry = new DatabricksGeometry("POINT(1 2)", 4326);
+ assertEquals(geometry, geometry);
+ }
+
+ @Test
+ public void testEquals_WithNull() throws DatabricksValidationException {
+ DatabricksGeometry geometry = new DatabricksGeometry("POINT(1 2)", 4326);
+ assertNotEquals(geometry, null);
+ }
+
+ @Test
+ public void testEquals_WithDifferentClass() throws DatabricksValidationException {
+ DatabricksGeometry geometry = new DatabricksGeometry("POINT(1 2)", 4326);
+ assertNotEquals(geometry, "POINT(1 2)");
+ }
+
+ @Test
+ public void testHashCode_EqualObjects() throws DatabricksValidationException {
+ DatabricksGeometry geo1 = new DatabricksGeometry("POINT(1 2)", 4326);
+ DatabricksGeometry geo2 = new DatabricksGeometry("POINT(1 2)", 4326);
+ assertEquals(geo1.hashCode(), geo2.hashCode());
+ }
+
+ @Test
+ public void testHashCode_DifferentObjects() throws DatabricksValidationException {
+ DatabricksGeometry geo1 = new DatabricksGeometry("POINT(1 2)", 4326);
+ DatabricksGeometry geo2 = new DatabricksGeometry("POINT(3 4)", 4326);
+ assertNotEquals(geo1.hashCode(), geo2.hashCode());
+ }
+}
diff --git a/src/test/java/com/databricks/jdbc/api/impl/converters/ArrowToJavaObjectConverterTest.java b/src/test/java/com/databricks/jdbc/api/impl/converters/ArrowToJavaObjectConverterTest.java
index 4afe72d98d..258c38f487 100644
--- a/src/test/java/com/databricks/jdbc/api/impl/converters/ArrowToJavaObjectConverterTest.java
+++ b/src/test/java/com/databricks/jdbc/api/impl/converters/ArrowToJavaObjectConverterTest.java
@@ -2,10 +2,12 @@
import static com.databricks.jdbc.api.impl.converters.ArrowToJavaObjectConverter.convert;
import static com.databricks.jdbc.api.impl.converters.ArrowToJavaObjectConverter.getZoneIdFromTimeZoneOpt;
-import static com.databricks.jdbc.common.util.DatabricksTypeUtil.VARIANT;
+import static com.databricks.jdbc.common.util.DatabricksTypeUtil.*;
import static org.junit.jupiter.api.Assertions.*;
import com.databricks.jdbc.api.impl.DatabricksArray;
+import com.databricks.jdbc.api.impl.DatabricksGeography;
+import com.databricks.jdbc.api.impl.DatabricksGeometry;
import com.databricks.jdbc.api.impl.DatabricksStruct;
import com.databricks.jdbc.api.internal.IDatabricksConnectionContext;
import com.databricks.jdbc.exception.DatabricksValidationException;
@@ -541,4 +543,80 @@ public void testGetZoneIdFromTimeZoneOpt_InvalidTimeZones() {
DateTimeException.class,
() -> getZoneIdFromTimeZoneOpt(Optional.of("5:30"))); // Missing sign
}
+
+ @Test
+ public void testGeometryConversion() throws SQLException {
+ VarCharVector varCharVector = new VarCharVector("varCharVector", this.bufferAllocator);
+ varCharVector.allocateNew();
+ varCharVector.set(0, new Text("POINT(1 2)"));
+ varCharVector.setValueCount(1);
+
+ Object result =
+ convert(varCharVector, 0, ColumnInfoTypeName.GEOMETRY, GEOMETRY, new ColumnInfo());
+
+ assertNotNull(result);
+ assertInstanceOf(DatabricksGeometry.class, result);
+ DatabricksGeometry geometry = (DatabricksGeometry) result;
+ assertEquals("SRID=0;POINT(1 2)", geometry.toString());
+ assertEquals(0, geometry.getSRID()); // Default SRID
+
+ varCharVector.close();
+ }
+
+ @Test
+ public void testGeographyConversion() throws SQLException {
+ VarCharVector varCharVector = new VarCharVector("varCharVector", this.bufferAllocator);
+ varCharVector.allocateNew();
+ varCharVector.set(0, new Text("POINT(1 2)"));
+ varCharVector.setValueCount(1);
+
+ Object result =
+ convert(varCharVector, 0, ColumnInfoTypeName.GEOGRAPHY, GEOGRAPHY, new ColumnInfo());
+
+ assertNotNull(result);
+ assertInstanceOf(DatabricksGeography.class, result);
+ DatabricksGeography geography = (DatabricksGeography) result;
+ assertEquals("SRID=0;POINT(1 2)", geography.toString());
+ assertEquals(0, geography.getSRID()); // Default SRID
+
+ varCharVector.close();
+ }
+
+ @Test
+ public void testGeometryWithSRIDConversion() throws SQLException {
+ VarCharVector varCharVector = new VarCharVector("varCharVector", this.bufferAllocator);
+ varCharVector.allocateNew();
+ varCharVector.set(0, new Text("SRID=4326;POINT(1 2)"));
+ varCharVector.setValueCount(1);
+
+ Object result =
+ convert(varCharVector, 0, ColumnInfoTypeName.GEOMETRY, "GEOMETRY", new ColumnInfo());
+
+ assertNotNull(result);
+ assertInstanceOf(DatabricksGeometry.class, result);
+ DatabricksGeometry geometry = (DatabricksGeometry) result;
+ assertEquals("SRID=4326;POINT(1 2)", geometry.toString());
+ assertEquals(4326, geometry.getSRID());
+
+ varCharVector.close();
+ }
+
+ @Test
+ public void testGeographyWithSRIDConversion() throws SQLException {
+ VarCharVector varCharVector = new VarCharVector("varCharVector", this.bufferAllocator);
+ varCharVector.allocateNew();
+ varCharVector.set(0, new Text("SRID=4326;POINT(1 2)"));
+ varCharVector.setValueCount(1);
+
+ Object result =
+ convert(varCharVector, 0, ColumnInfoTypeName.GEOGRAPHY, "GEOGRAPHY", new ColumnInfo());
+
+ assertNotNull(result);
+ assertInstanceOf(DatabricksGeography.class, result);
+ DatabricksGeography geography = (DatabricksGeography) result;
+ assertEquals("SRID=4326;POINT(1 2)", geography.toString());
+ assertEquals(4326, geography.getSRID());
+
+ varCharVector.close();
+ }
}
diff --git a/src/test/java/com/databricks/jdbc/api/impl/converters/GeospatialConverterTest.java b/src/test/java/com/databricks/jdbc/api/impl/converters/GeospatialConverterTest.java
new file mode 100644
index 0000000000..22c8844cad
--- /dev/null
+++ b/src/test/java/com/databricks/jdbc/api/impl/converters/GeospatialConverterTest.java
@@ -0,0 +1,118 @@
+package com.databricks.jdbc.api.impl.converters;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import com.databricks.jdbc.api.impl.DatabricksGeography;
+import com.databricks.jdbc.api.impl.DatabricksGeometry;
+import com.databricks.jdbc.exception.DatabricksSQLException;
+import org.apache.arrow.vector.util.Text;
+import org.junit.jupiter.api.Test;
+
+/** Test class for GeospatialConverter. */
+public class GeospatialConverterTest {
+
+ private final GeospatialConverter converter = new GeospatialConverter();
+
+ // ===================================================================================
+ // toDatabricksGeometry() tests
+ // ===================================================================================
+
+ @Test
+ public void testToDatabricksGeometry_WithValidWKT() throws DatabricksSQLException {
+ DatabricksGeometry result = converter.toDatabricksGeometry("POINT(1 2)");
+ assertNotNull(result);
+ assertEquals("POINT(1 2)", result.getWKT());
+ assertEquals(0, result.getSRID());
+ }
+
+ @Test
+ public void testToDatabricksGeometry_WithEWKT() throws DatabricksSQLException {
+ DatabricksGeometry result = converter.toDatabricksGeometry("SRID=4326;POINT(1 2)");
+ assertNotNull(result);
+ assertEquals("POINT(1 2)", result.getWKT());
+ assertEquals(4326, result.getSRID());
+ }
+
+ @Test
+ public void testToDatabricksGeometry_WithTextInput() throws DatabricksSQLException {
+ Text textInput = new Text("POINT(1 2)");
+ DatabricksGeometry result = converter.toDatabricksGeometry(textInput);
+ assertNotNull(result);
+ assertEquals("POINT(1 2)", result.getWKT());
+ }
+
+ @Test
+ public void testToDatabricksGeometry_WithGeometryInstance_ReturnsItself()
+ throws DatabricksSQLException {
+ DatabricksGeometry input = new DatabricksGeometry("POINT(1 2)", 4326);
+ DatabricksGeometry result = converter.toDatabricksGeometry(input);
+ assertSame(input, result);
+ }
+
+ @Test
+ public void testToDatabricksGeometry_WithUnsupportedType_ThrowsException() {
+ assertThrows(DatabricksSQLException.class, () -> converter.toDatabricksGeometry(123));
+ }
+
+ // ===================================================================================
+ // toDatabricksGeography() tests
+ // ===================================================================================
+
+ @Test
+ public void testToDatabricksGeography_WithValidWKT() throws DatabricksSQLException {
+ DatabricksGeography result = converter.toDatabricksGeography("POINT(-122.4194 37.7749)");
+ assertNotNull(result);
+ assertEquals("POINT(-122.4194 37.7749)", result.getWKT());
+ assertEquals(0, result.getSRID());
+ }
+
+ @Test
+ public void testToDatabricksGeography_WithEWKT() throws DatabricksSQLException {
+ DatabricksGeography result =
+ converter.toDatabricksGeography("SRID=4326;POINT(-122.4194 37.7749)");
+ assertNotNull(result);
+ assertEquals("POINT(-122.4194 37.7749)", result.getWKT());
+ assertEquals(4326, result.getSRID());
+ }
+
+ @Test
+ public void testToDatabricksGeography_WithGeographyInstance_ReturnsItself()
+ throws DatabricksSQLException {
+ DatabricksGeography input = new DatabricksGeography("POINT(1 2)", 4326);
+ DatabricksGeography result = converter.toDatabricksGeography(input);
+ assertSame(input, result);
+ }
+
+ // ===================================================================================
+ // toString() tests
+ // ===================================================================================
+
+ @Test
+ public void testToString_WithGeometryObject() throws DatabricksSQLException {
+ DatabricksGeometry geometry = new DatabricksGeometry("POINT(1 2)", 4326);
+ String result = converter.toString(geometry);
+ assertEquals("SRID=4326;POINT(1 2)", result);
+ }
+
+ @Test
+ public void testToString_WithNull_ThrowsException() {
+ assertThrows(DatabricksSQLException.class, () -> converter.toString(null));
+ }
+
+ // ===================================================================================
+ // toByteArray() tests
+ // ===================================================================================
+
+ @Test
+ public void testToByteArray_WithGeometryObject() throws DatabricksSQLException {
+ DatabricksGeometry geometry = new DatabricksGeometry("POINT(1 2)", 4326);
+ byte[] result = converter.toByteArray(geometry);
+ assertNotNull(result);
+ assertTrue(result.length > 0);
+ }
+
+ @Test
+ public void testToByteArray_WithNonGeospatialType_ThrowsException() {
+ assertThrows(DatabricksSQLException.class, () -> converter.toByteArray("not geospatial"));
+ }
+}
diff --git a/src/test/java/com/databricks/jdbc/api/impl/converters/WKTConverterTest.java b/src/test/java/com/databricks/jdbc/api/impl/converters/WKTConverterTest.java
new file mode 100644
index 0000000000..af2e45e6a9
--- /dev/null
+++ b/src/test/java/com/databricks/jdbc/api/impl/converters/WKTConverterTest.java
@@ -0,0 +1,206 @@
+package com.databricks.jdbc.api.impl.converters;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import com.databricks.jdbc.exception.DatabricksValidationException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import org.junit.jupiter.api.Test;
+
+/** Test class for WKTConverter utility. */
+public class WKTConverterTest {
+
+ @Test
+ public void testToWKB_ValidWKT() throws DatabricksValidationException {
+ String wkt = "POINT(1 2)";
+ byte[] wkb = WKTConverter.toWKB(wkt);
+
+ assertNotNull(wkb);
+ assertTrue(wkb.length > 0);
+ // WKB is binary data, not UTF-8 bytes of WKT
+ // We can verify it's valid WKB by converting it back to WKT
+ String convertedBack = WKTConverter.toWKT(wkb);
+ // JTS outputs "POINT (1 2)" with a space after POINT, which is valid WKT
+ assertEquals("POINT (1 2)", convertedBack);
+ }
+
+ @Test
+ public void testToWKB_NullWKT() {
+ assertThrows(DatabricksValidationException.class, () -> WKTConverter.toWKB(null));
+ }
+
+ @Test
+ public void testToWKB_EmptyWKT() {
+ assertThrows(DatabricksValidationException.class, () -> WKTConverter.toWKB(""));
+ }
+
+ @Test
+ public void testToWKB_WhitespaceWKT() {
+ assertThrows(DatabricksValidationException.class, () -> WKTConverter.toWKB(" "));
+ }
+
+ @Test
+ public void testExtractSRIDFromEWKT_WithSRID() {
+ String ewkt = "SRID=4326;POINT(1 2)";
+ int srid = WKTConverter.extractSRIDFromEWKT(ewkt);
+ assertEquals(4326, srid);
+ }
+
+ @Test
+ public void testExtractSRIDFromEWKT_WithoutSRID() {
+ String wkt = "POINT(1 2)";
+ int srid = WKTConverter.extractSRIDFromEWKT(wkt);
+ assertEquals(0, srid);
+ }
+
+ @Test
+ public void testExtractSRIDFromEWKT_Null() {
+ int srid = WKTConverter.extractSRIDFromEWKT(null);
+ assertEquals(0, srid);
+ }
+
+ @Test
+ public void testExtractSRIDFromEWKT_Empty() {
+ int srid = WKTConverter.extractSRIDFromEWKT("");
+ assertEquals(0, srid);
+ }
+
+ @Test
+ public void testExtractSRIDFromEWKT_InvalidSRID() {
+ String ewkt = "SRID=invalid;POINT(1 2)";
+ int srid = WKTConverter.extractSRIDFromEWKT(ewkt);
+ assertEquals(0, srid); // Should return 0 for invalid SRID
+ }
+
+ @Test
+ public void testExtractSRIDFromEWKT_NoSemicolon() {
+ String ewkt = "SRID=4326POINT(1 2)";
+ int srid = WKTConverter.extractSRIDFromEWKT(ewkt);
+ assertEquals(0, srid); // Should return 0 if no semicolon
+ }
+
+ @Test
+ public void testRemoveSRIDFromEWKT_WithSRID() {
+ String ewkt = "SRID=4326;POINT(1 2)";
+ String wkt = WKTConverter.removeSRIDFromEWKT(ewkt);
+ assertEquals("POINT(1 2)", wkt);
+ }
+
+ @Test
+ public void testRemoveSRIDFromEWKT_WithoutSRID() {
+ String wkt = "POINT(1 2)";
+ String result = WKTConverter.removeSRIDFromEWKT(wkt);
+ assertEquals("POINT(1 2)", result);
+ }
+
+ @Test
+ public void testRemoveSRIDFromEWKT_Null() {
+ String result = WKTConverter.removeSRIDFromEWKT(null);
+ assertNull(result);
+ }
+
+ @Test
+ public void testRemoveSRIDFromEWKT_Empty() {
+ String result = WKTConverter.removeSRIDFromEWKT("");
+ assertEquals("", result);
+ }
+
+ @Test
+ public void testRemoveSRIDFromEWKT_NoSemicolon() {
+ String ewkt = "SRID=4326POINT(1 2)";
+ String result = WKTConverter.removeSRIDFromEWKT(ewkt);
+ assertEquals("SRID=4326POINT(1 2)", result); // Should return as-is if no semicolon
+ }
+
+ @Test
+ public void testRemoveSRIDFromEWKT_OnlySRID() {
+ String ewkt = "SRID=4326;";
+ String result = WKTConverter.removeSRIDFromEWKT(ewkt);
+ assertEquals("", result);
+ }
+
+ @Test
+ public void testToWKB_InvalidWKTFormat_LogsError() {
+ String invalidWkt = "POINT(1 2 3 4 5)"; // Too many coordinates for a POINT
+
+ DatabricksValidationException exception =
+ assertThrows(DatabricksValidationException.class, () -> WKTConverter.toWKB(invalidWkt));
+
+ assertTrue(exception.getMessage().contains("Invalid WKT format"));
+ }
+
+ @Test
+ public void testToWKT_ValidWKB() throws DatabricksValidationException {
+ // First create valid WKB from WKT
+ byte[] wkb = WKTConverter.toWKB("POINT (1 2)");
+ String wkt = WKTConverter.toWKT(wkb);
+
+ assertEquals("POINT (1 2)", wkt);
+ }
+
+ @Test
+ public void testToWKT_NullWKB() {
+ assertThrows(DatabricksValidationException.class, () -> WKTConverter.toWKT(null));
+ }
+
+ @Test
+ public void testToWKT_EmptyWKB() {
+ assertThrows(DatabricksValidationException.class, () -> WKTConverter.toWKT(new byte[0]));
+ }
+
+ @Test
+ public void testToWKT_InvalidWKB_LogsError() {
+ byte[] invalidWkb = new byte[] {1, 2, 3, 4, 5}; // Random invalid bytes
+
+ DatabricksValidationException exception =
+ assertThrows(DatabricksValidationException.class, () -> WKTConverter.toWKT(invalidWkb));
+
+ assertTrue(exception.getMessage().contains("Invalid WKB format"));
+ }
+
+ @Test
+ public void testConcurrency() throws Exception {
+ int numThreads = 50;
+ ExecutorService executor = Executors.newFixedThreadPool(numThreads);
+ CountDownLatch latch = new CountDownLatch(numThreads);
+ List> futures = new ArrayList<>();
+
+ for (int threadNum = 0; threadNum < numThreads; threadNum++) {
+ final int threadId = threadNum;
+ Future> future =
+ executor.submit(
+ () -> {
+ try {
+ int x = threadId;
+ int y = threadId * 2;
+ String wkt = String.format("POINT (%d %d)", x, y);
+
+ byte[] wkb = WKTConverter.toWKB(wkt);
+ assertNotNull(wkb, "Thread " + threadId + ": WKB should not be null");
+
+ String convertedWkt = WKTConverter.toWKT(wkb);
+ assertEquals(wkt, convertedWkt, "Thread " + threadId + ": WKT mismatch");
+
+ } catch (Exception e) {
+ fail("Thread " + threadId + " failed: " + e.getMessage());
+ } finally {
+ latch.countDown();
+ }
+ });
+ futures.add(future);
+ }
+
+ assertTrue(latch.await(10, TimeUnit.SECONDS), "Not all threads completed in time");
+ executor.shutdown();
+
+ // Verify all threads completed successfully
+ for (Future> future : futures) {
+ future.get();
+ }
+ }
+}
diff --git a/src/test/java/com/databricks/jdbc/integration/fakeservice/tests/DataTypesIntegrationTests.java b/src/test/java/com/databricks/jdbc/integration/fakeservice/tests/DataTypesIntegrationTests.java
index 684a478a5a..7809278eac 100644
--- a/src/test/java/com/databricks/jdbc/integration/fakeservice/tests/DataTypesIntegrationTests.java
+++ b/src/test/java/com/databricks/jdbc/integration/fakeservice/tests/DataTypesIntegrationTests.java
@@ -11,6 +11,7 @@
import java.util.Properties;
import java.util.stream.Stream;
import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
@@ -338,6 +339,65 @@ private void validateIntervalResults(ResultSet resultSet) throws SQLException {
assertFalse(resultSet.next());
}
+ @Test
+ void testGeospatialTypes() throws SQLException {
+
+ // Skip for THRIFT_SERVER as the test environment version doesn't support geospatial types
+ // TODO: Update stubs and remove this skip once THRIFT_SERVER environment is upgraded to support
+ // geospatial types
+ Assumptions.assumeTrue(
+ isSqlExecSdkClient(), "Geospatial types are not supported on THRIFT_SERVER yet");
+
+ String query =
+ "SELECT * FROM (VALUES "
+ + "(1, ST_GeomFromText('POINT (1 2)'), ST_GeogFromText('POINT (3 4)')), "
+ + "(2, ST_GeomFromText('LINESTRING (0 0, 1 1, 2 2)'), ST_GeogFromText('LINESTRING (5 5, 6 6)')), "
+ + "(3, NULL, NULL)"
+ + ") AS geospatial_data(id, geom, geog) "
+ + "ORDER BY id";
+
+ ResultSet rs = executeQuery(connection, query);
+ assertNotNull(
+ rs, "ResultSet should not be null - GEOMETRY/GEOGRAPHY types may not be supported");
+ ResultSetMetaData rsmd = rs.getMetaData();
+
+ // Validate metadata
+ assertEquals("GEOMETRY", rsmd.getColumnTypeName(2));
+ assertEquals("GEOGRAPHY", rsmd.getColumnTypeName(3));
+
+ // Validate data
+ int rowCount = 0;
+ while (rs.next()) {
+ rowCount++;
+ int id = rs.getInt("id");
+ Object geom = rs.getObject("geom");
+ Object geog = rs.getObject("geog");
+
+ switch (id) {
+ case 1:
+ assertNotNull(geom);
+ assertNotNull(geog);
+ assertTrue(geom.toString().contains("POINT"));
+ assertTrue(geog.toString().contains("POINT"));
+ break;
+ case 2:
+ assertNotNull(geom);
+ assertNotNull(geog);
+ assertTrue(geom.toString().contains("LINESTRING"));
+ assertTrue(geog.toString().contains("LINESTRING"));
+ break;
+ case 3:
+ assertNull(geom);
+ assertNull(geog);
+ break;
+ default:
+ fail("Unexpected row id: " + id);
+ }
+ }
+ assertEquals(3, rowCount);
+ rs.close();
+ }
+
private void closeConnection(Connection connection) throws SQLException {
if (connection != null) {
if (((DatabricksConnection) connection).getConnectionContext().getClientType()
diff --git a/src/test/resources/cloudfetchapi/datatypesintegrationtests/testgeospatialtypes/mappings/oregon-staging_6051921418418893.jobs_sql_2025-10-10_09_results_2025-10-10t093531z_dfb38243-a074-4425-a7fe-772f480f384a-05a66b02-c06d-4941-9b01-e820827c2f85.json b/src/test/resources/cloudfetchapi/datatypesintegrationtests/testgeospatialtypes/mappings/oregon-staging_6051921418418893.jobs_sql_2025-10-10_09_results_2025-10-10t093531z_dfb38243-a074-4425-a7fe-772f480f384a-05a66b02-c06d-4941-9b01-e820827c2f85.json
new file mode 100644
index 0000000000..d53c45f6da
--- /dev/null
+++ b/src/test/resources/cloudfetchapi/datatypesintegrationtests/testgeospatialtypes/mappings/oregon-staging_6051921418418893.jobs_sql_2025-10-10_09_results_2025-10-10t093531z_dfb38243-a074-4425-a7fe-772f480f384a-05a66b02-c06d-4941-9b01-e820827c2f85.json
@@ -0,0 +1,25 @@
+{
+ "id" : "05a66b02-c06d-4941-9b01-e820827c2f85",
+ "name" : "oregon-staging_6051921418418893.jobs_sql_2025-10-10_09_results_2025-10-10t093531z_dfb38243-a074-4425-a7fe-772f480f384a",
+ "request" : {
+ "url" : "/oregon-staging/6051921418418893.jobs/sql/2025-10-10/09/results_2025-10-10T09%3A35%3A31Z_dfb38243-a074-4425-a7fe-772f480f384a?[REDACTED]X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20251010T093531Z&X-Amz-SignedHeaders=host&X-Amz-Expires=899&[REDACTED]X-Amz-Signature=6a356f310ac3c56bbfccb7b5f5f31b34eb1010a04c8250659118a9db52f83e5f",
+ "method" : "GET"
+ },
+ "response" : {
+ "status" : 200,
+ "base64Body" : "BCJNGHRwjroAAACg/////wgBAAAQAAEAsAoADgAGAA0ACAAKDwAiAAQYAKEBCgAMAAAACAAEGAARCAwAASEAoQADAAAAkAAAAEguAKQAANL///8UAAAABABCAAAFAVwAAgIAQMD///8oAPUAZ2VvZwAAEgAYABQAEwASYAAXEjwAERgzACAFAUwABAIAEQQCAAAIAAFEABRtRAA/AAATRAACEhw5ACICIAgAAQIAdAgADAAIAAfEABABHACAAgAAAGlkAABLcvQ0AAAAAGc6z14EIk0YdHCOHAEAAKL/////CAEAABQAAQDyAgwAFgAOABUAEAAEAAwAAACoFwBgAAAABAAQCADSAwoAGAAMAAgABAAKADgAUJgAAAADHAADAgATCAgAAAIAEAEFAAcUAABUAAACABAYBQAHIAARIA4AEgBkAEEAAAAwBgAwAAAhBQBBAAAAWAYABjAAEGANAAcwABFwDgAwAAAxBQADAgAAlAAABAAIAgAEEAAEmAAMEAATBy0AACAAFwLYAAQoAAACAAD4AACYAAAEAPASUE9JTlQoMSAyKUxJTkVTVFJJTkcoMCAwLDEgMSwyIDIpMQAHSAAAAgAAbAEAqAAABACiU1JJRD00MzI2O0oARjMgNCkUAAdUAPAANSA1LDYgNikAAAAAAAAA84O3awAAAACLf1BBBCJNGHRwjggAAID/////AAAAAIaGkggAAAAAhoaSCA==",
+ "headers" : {
+ "Accept-Ranges" : "bytes",
+ "Server" : "AmazonS3",
+ "ETag" : "\"c0447953da35669e9a3629504add95a8\"",
+ "Last-Modified" : "Fri, 10 Oct 2025 09:35:32 GMT",
+ "x-amz-request-id" : "66976VQD1VAX75SS",
+ "x-amz-server-side-encryption" : "AES256",
+ "x-amz-id-2" : "JCHXeBYwC7iGlinUE87DghMNEWmGe+GT3g2+gqGelAu6GSSpZRuBjBgl5zvhXnQ1qGnQAKanlRQ=",
+ "Date" : "Fri, 10 Oct 2025 09:35:33 GMT",
+ "Content-Type" : "binary/octet-stream"
+ }
+ },
+ "uuid" : "05a66b02-c06d-4941-9b01-e820827c2f85",
+ "insertionIndex" : 1
+}
\ No newline at end of file
diff --git a/src/test/resources/sqlexecapi/datatypesintegrationtests/testgeospatialtypes/mappings/api_2.0_sql_sessions-26c29504-a4b8-440b-bbd1-d7b1d3576eb7.json b/src/test/resources/sqlexecapi/datatypesintegrationtests/testgeospatialtypes/mappings/api_2.0_sql_sessions-26c29504-a4b8-440b-bbd1-d7b1d3576eb7.json
new file mode 100644
index 0000000000..d2fbd84c5e
--- /dev/null
+++ b/src/test/resources/sqlexecapi/datatypesintegrationtests/testgeospatialtypes/mappings/api_2.0_sql_sessions-26c29504-a4b8-440b-bbd1-d7b1d3576eb7.json
@@ -0,0 +1,38 @@
+{
+ "id" : "26c29504-a4b8-440b-bbd1-d7b1d3576eb7",
+ "name" : "api_2.0_sql_sessions",
+ "request" : {
+ "url" : "/api/2.0/sql/sessions/",
+ "method" : "POST",
+ "bodyPatterns" : [ {
+ "equalToJson" : "{\"warehouse_id\":\"dd43ee29fedd958d\",\"schema\":\"default\",\"catalog\":\"SPARK\"}",
+ "ignoreArrayOrder" : true,
+ "ignoreExtraElements" : true
+ } ]
+ },
+ "response" : {
+ "status" : 200,
+ "body" : "{\"session_id\":\"01f0a5bc-7472-14b8-bc16-71f68ce36d0c\"}",
+ "headers" : {
+ "x-request-id" : "b03130c4-9753-4a1d-93f1-b8d4fe7d021f",
+ "date" : "Fri, 10 Oct 2025 09:35:27 GMT",
+ "server" : "databricks",
+ "x-databricks-popp-response-code-details" : "via_upstream",
+ "x-databricks-shard-debug" : "oregon-staging",
+ "vary" : "Accept-Encoding",
+ "x-databricks-org-id" : "6051921418418893",
+ "strict-transport-security" : "max-age=31536000; includeSubDomains; preload",
+ "x-content-type-options" : "nosniff",
+ "x-databricks-popp-routing-reason" : "deployment-name",
+ "content-type" : "application/json",
+ "server-timing" : "request_id;dur=0;desc=\"b03130c4-9753-4a1d-93f1-b8d4fe7d021f\", client_protocol;dur=0;desc=\"HTTP/1.1\"",
+ "alt-svc" : "h3=\":5443\"; ma=86400, h3-29=\":5443\"; ma=86400",
+ "x-databricks-apiproxy-response-code-details" : "via_upstream"
+ }
+ },
+ "uuid" : "26c29504-a4b8-440b-bbd1-d7b1d3576eb7",
+ "scenarioName" : "scenario-1-api-2.0-sql-sessions",
+ "requiredScenarioState" : "Started",
+ "newScenarioState" : "scenario-1-api-2.0-sql-sessions-2",
+ "insertionIndex" : 7
+}
\ No newline at end of file
diff --git a/src/test/resources/sqlexecapi/datatypesintegrationtests/testgeospatialtypes/mappings/api_2.0_sql_sessions-53ba8b00-9e82-4ffe-a90a-aa18dfea3f89.json b/src/test/resources/sqlexecapi/datatypesintegrationtests/testgeospatialtypes/mappings/api_2.0_sql_sessions-53ba8b00-9e82-4ffe-a90a-aa18dfea3f89.json
new file mode 100644
index 0000000000..85e8fb750b
--- /dev/null
+++ b/src/test/resources/sqlexecapi/datatypesintegrationtests/testgeospatialtypes/mappings/api_2.0_sql_sessions-53ba8b00-9e82-4ffe-a90a-aa18dfea3f89.json
@@ -0,0 +1,37 @@
+{
+ "id" : "53ba8b00-9e82-4ffe-a90a-aa18dfea3f89",
+ "name" : "api_2.0_sql_sessions",
+ "request" : {
+ "url" : "/api/2.0/sql/sessions/",
+ "method" : "POST",
+ "bodyPatterns" : [ {
+ "equalToJson" : "{\"warehouse_id\":\"dd43ee29fedd958d\",\"schema\":\"default\",\"catalog\":\"SPARK\"}",
+ "ignoreArrayOrder" : true,
+ "ignoreExtraElements" : true
+ } ]
+ },
+ "response" : {
+ "status" : 200,
+ "body" : "{\"session_id\":\"01f0a5bc-75de-14b1-aba0-dd791b1a50e3\"}",
+ "headers" : {
+ "x-request-id" : "b7cfeca3-2f54-4a9b-84b4-d99dedd08fe3",
+ "date" : "Fri, 10 Oct 2025 09:35:29 GMT",
+ "server" : "databricks",
+ "x-databricks-popp-response-code-details" : "via_upstream",
+ "x-databricks-shard-debug" : "oregon-staging",
+ "vary" : "Accept-Encoding",
+ "x-databricks-org-id" : "6051921418418893",
+ "strict-transport-security" : "max-age=31536000; includeSubDomains; preload",
+ "x-content-type-options" : "nosniff",
+ "x-databricks-popp-routing-reason" : "deployment-name",
+ "content-type" : "application/json",
+ "server-timing" : "request_id;dur=0;desc=\"b7cfeca3-2f54-4a9b-84b4-d99dedd08fe3\", client_protocol;dur=0;desc=\"HTTP/1.1\"",
+ "alt-svc" : "h3=\":5443\"; ma=86400, h3-29=\":5443\"; ma=86400",
+ "x-databricks-apiproxy-response-code-details" : "via_upstream"
+ }
+ },
+ "uuid" : "53ba8b00-9e82-4ffe-a90a-aa18dfea3f89",
+ "scenarioName" : "scenario-1-api-2.0-sql-sessions",
+ "requiredScenarioState" : "scenario-1-api-2.0-sql-sessions-2",
+ "insertionIndex" : 5
+}
\ No newline at end of file
diff --git a/src/test/resources/sqlexecapi/datatypesintegrationtests/testgeospatialtypes/mappings/api_2.0_sql_sessions_01f0a5bc-7472-14b8-bc16-71f68ce36d0c-423765cb-ebcc-4198-8b3e-2c1997822b1c.json b/src/test/resources/sqlexecapi/datatypesintegrationtests/testgeospatialtypes/mappings/api_2.0_sql_sessions_01f0a5bc-7472-14b8-bc16-71f68ce36d0c-423765cb-ebcc-4198-8b3e-2c1997822b1c.json
new file mode 100644
index 0000000000..b25b01ec9e
--- /dev/null
+++ b/src/test/resources/sqlexecapi/datatypesintegrationtests/testgeospatialtypes/mappings/api_2.0_sql_sessions_01f0a5bc-7472-14b8-bc16-71f68ce36d0c-423765cb-ebcc-4198-8b3e-2c1997822b1c.json
@@ -0,0 +1,29 @@
+{
+ "id" : "423765cb-ebcc-4198-8b3e-2c1997822b1c",
+ "name" : "api_2.0_sql_sessions_01f0a5bc-7472-14b8-bc16-71f68ce36d0c",
+ "request" : {
+ "url" : "/api/2.0/sql/sessions/01f0a5bc-7472-14b8-bc16-71f68ce36d0c?warehouse_id=dd43ee29fedd958d",
+ "method" : "DELETE"
+ },
+ "response" : {
+ "status" : 200,
+ "body" : "{}",
+ "headers" : {
+ "x-request-id" : "ed1c1402-4910-4360-b08a-9040377de512",
+ "date" : "Fri, 10 Oct 2025 09:35:35 GMT",
+ "server" : "databricks",
+ "x-databricks-popp-response-code-details" : "via_upstream",
+ "x-databricks-shard-debug" : "oregon-staging",
+ "x-databricks-org-id" : "6051921418418893",
+ "strict-transport-security" : "max-age=31536000; includeSubDomains; preload",
+ "x-content-type-options" : "nosniff",
+ "x-databricks-popp-routing-reason" : "deployment-name",
+ "content-type" : "application/json",
+ "server-timing" : "request_id;dur=0;desc=\"ed1c1402-4910-4360-b08a-9040377de512\", client_protocol;dur=0;desc=\"HTTP/1.1\"",
+ "alt-svc" : "h3=\":5443\"; ma=86400, h3-29=\":5443\"; ma=86400",
+ "x-databricks-apiproxy-response-code-details" : "via_upstream"
+ }
+ },
+ "uuid" : "423765cb-ebcc-4198-8b3e-2c1997822b1c",
+ "insertionIndex" : 2
+}
\ No newline at end of file
diff --git a/src/test/resources/sqlexecapi/datatypesintegrationtests/testgeospatialtypes/mappings/api_2.0_sql_sessions_01f0a5bc-75de-14b1-aba0-dd791b1a50e3-838649f2-ff8b-416a-b28f-e9bef5a36f7e.json b/src/test/resources/sqlexecapi/datatypesintegrationtests/testgeospatialtypes/mappings/api_2.0_sql_sessions_01f0a5bc-75de-14b1-aba0-dd791b1a50e3-838649f2-ff8b-416a-b28f-e9bef5a36f7e.json
new file mode 100644
index 0000000000..b7006d771d
--- /dev/null
+++ b/src/test/resources/sqlexecapi/datatypesintegrationtests/testgeospatialtypes/mappings/api_2.0_sql_sessions_01f0a5bc-75de-14b1-aba0-dd791b1a50e3-838649f2-ff8b-416a-b28f-e9bef5a36f7e.json
@@ -0,0 +1,29 @@
+{
+ "id" : "838649f2-ff8b-416a-b28f-e9bef5a36f7e",
+ "name" : "api_2.0_sql_sessions_01f0a5bc-75de-14b1-aba0-dd791b1a50e3",
+ "request" : {
+ "url" : "/api/2.0/sql/sessions/01f0a5bc-75de-14b1-aba0-dd791b1a50e3?warehouse_id=dd43ee29fedd958d",
+ "method" : "DELETE"
+ },
+ "response" : {
+ "status" : 200,
+ "body" : "{}",
+ "headers" : {
+ "x-request-id" : "020075a5-a0e8-4c1f-8885-e395d89fc45e",
+ "date" : "Fri, 10 Oct 2025 09:35:36 GMT",
+ "server" : "databricks",
+ "x-databricks-popp-response-code-details" : "via_upstream",
+ "x-databricks-shard-debug" : "oregon-staging",
+ "x-databricks-org-id" : "6051921418418893",
+ "strict-transport-security" : "max-age=31536000; includeSubDomains; preload",
+ "x-content-type-options" : "nosniff",
+ "x-databricks-popp-routing-reason" : "deployment-name",
+ "content-type" : "application/json",
+ "server-timing" : "request_id;dur=0;desc=\"020075a5-a0e8-4c1f-8885-e395d89fc45e\", client_protocol;dur=0;desc=\"HTTP/1.1\"",
+ "alt-svc" : "h3=\":5443\"; ma=86400, h3-29=\":5443\"; ma=86400",
+ "x-databricks-apiproxy-response-code-details" : "via_upstream"
+ }
+ },
+ "uuid" : "838649f2-ff8b-416a-b28f-e9bef5a36f7e",
+ "insertionIndex" : 1
+}
\ No newline at end of file
diff --git a/src/test/resources/sqlexecapi/datatypesintegrationtests/testgeospatialtypes/mappings/api_2.0_sql_statements-1bc97f2a-5ba2-48a6-a701-819fd4d1fd53.json b/src/test/resources/sqlexecapi/datatypesintegrationtests/testgeospatialtypes/mappings/api_2.0_sql_statements-1bc97f2a-5ba2-48a6-a701-819fd4d1fd53.json
new file mode 100644
index 0000000000..ca8e90737f
--- /dev/null
+++ b/src/test/resources/sqlexecapi/datatypesintegrationtests/testgeospatialtypes/mappings/api_2.0_sql_statements-1bc97f2a-5ba2-48a6-a701-819fd4d1fd53.json
@@ -0,0 +1,35 @@
+{
+ "id" : "1bc97f2a-5ba2-48a6-a701-819fd4d1fd53",
+ "name" : "api_2.0_sql_statements",
+ "request" : {
+ "url" : "/api/2.0/sql/statements/",
+ "method" : "POST",
+ "bodyPatterns" : [ {
+ "equalToJson" : "{\"statement\":\"SELECT * FROM (VALUES (1, ST_GeomFromText('POINT (1 2)'), ST_GeogFromText('POINT (3 4)')), (2, ST_GeomFromText('LINESTRING (0 0, 1 1, 2 2)'), ST_GeogFromText('LINESTRING (5 5, 6 6)')), (3, NULL, NULL)) AS geospatial_data(id, geom, geog) ORDER BY id\",\"warehouse_id\":\"dd43ee29fedd958d\",\"session_id\":\"01f0a5bc-7472-14b8-bc16-71f68ce36d0c\",\"disposition\":\"EXTERNAL_LINKS\",\"format\":\"ARROW_STREAM\",\"on_wait_timeout\":\"CONTINUE\",\"parameters\":[],\"result_compression\":\"LZ4_FRAME\"}",
+ "ignoreArrayOrder" : true,
+ "ignoreExtraElements" : true
+ } ]
+ },
+ "response" : {
+ "status" : 200,
+ "body" : "{\"statement_id\":\"01f0a5bc-7691-1333-a867-e256984f11cf\",\"status\":{\"state\":\"SUCCEEDED\"},\"manifest\":{\"format\":\"ARROW_STREAM\",\"schema\":{\"column_count\":3,\"columns\":[{\"name\":\"id\",\"type_text\":\"INT\",\"type_name\":\"INT\",\"position\":0},{\"name\":\"geom\",\"type_text\":\"GEOMETRY(0)\",\"type_name\":\"GEOMETRY\",\"position\":1},{\"name\":\"geog\",\"type_text\":\"GEOGRAPHY(4326)\",\"type_name\":\"GEOGRAPHY\",\"position\":2}]},\"total_chunk_count\":1,\"chunks\":[{\"chunk_index\":0,\"row_offset\":0,\"row_count\":3,\"byte_count\":720}],\"total_row_count\":3,\"total_byte_count\":720,\"truncated\":false,\"result_compression\":\"LZ4_FRAME\"},\"result\":{\"external_links\":[{\"chunk_index\":0,\"row_offset\":0,\"row_count\":3,\"byte_count\":547,\"external_link\":\"https://e2-dogfood-core.s3.us-west-2.amazonaws.com/oregon-staging/6051921418418893.jobs/sql/2025-10-10/09/results_2025-10-10T09%3A35%3A31Z_dfb38243-a074-4425-a7fe-772f480f384a?[REDACTED]X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20251010T093531Z&X-Amz-SignedHeaders=host&X-Amz-Expires=899&[REDACTED]X-Amz-Signature=6a356f310ac3c56bbfccb7b5f5f31b34eb1010a04c8250659118a9db52f83e5f\",\"expiration\":\"2025-10-10T09:50:31.512Z\"}]}}",
+ "headers" : {
+ "x-request-id" : "c41ca866-9974-4e14-83c2-a6c072d9a33c",
+ "date" : "Fri, 10 Oct 2025 09:35:31 GMT",
+ "server" : "databricks",
+ "x-databricks-popp-response-code-details" : "via_upstream",
+ "x-databricks-shard-debug" : "oregon-staging",
+ "vary" : "Accept-Encoding",
+ "x-databricks-org-id" : "6051921418418893",
+ "strict-transport-security" : "max-age=31536000; includeSubDomains; preload",
+ "x-content-type-options" : "nosniff",
+ "x-databricks-popp-routing-reason" : "deployment-name",
+ "content-type" : "application/json",
+ "server-timing" : "request_id;dur=0;desc=\"c41ca866-9974-4e14-83c2-a6c072d9a33c\", client_protocol;dur=0;desc=\"HTTP/1.1\"",
+ "alt-svc" : "h3=\":5443\"; ma=86400, h3-29=\":5443\"; ma=86400",
+ "x-databricks-apiproxy-response-code-details" : "via_upstream"
+ }
+ },
+ "uuid" : "1bc97f2a-5ba2-48a6-a701-819fd4d1fd53",
+ "insertionIndex" : 4
+}
\ No newline at end of file
diff --git a/src/test/resources/sqlexecapi/datatypesintegrationtests/testgeospatialtypes/mappings/api_2.0_sql_statements_01f0a5bc-7691-1333-a867-e256984f11cf-03a6959b-2f46-41af-9cc0-dfd458a4d0ac.json b/src/test/resources/sqlexecapi/datatypesintegrationtests/testgeospatialtypes/mappings/api_2.0_sql_statements_01f0a5bc-7691-1333-a867-e256984f11cf-03a6959b-2f46-41af-9cc0-dfd458a4d0ac.json
new file mode 100644
index 0000000000..a51a78f2c7
--- /dev/null
+++ b/src/test/resources/sqlexecapi/datatypesintegrationtests/testgeospatialtypes/mappings/api_2.0_sql_statements_01f0a5bc-7691-1333-a867-e256984f11cf-03a6959b-2f46-41af-9cc0-dfd458a4d0ac.json
@@ -0,0 +1,29 @@
+{
+ "id" : "03a6959b-2f46-41af-9cc0-dfd458a4d0ac",
+ "name" : "api_2.0_sql_statements_01f0a5bc-7691-1333-a867-e256984f11cf",
+ "request" : {
+ "url" : "/api/2.0/sql/statements/01f0a5bc-7691-1333-a867-e256984f11cf",
+ "method" : "DELETE"
+ },
+ "response" : {
+ "status" : 200,
+ "body" : "{}",
+ "headers" : {
+ "x-request-id" : "b59b889c-e6b6-4c88-af81-ace62198e27f",
+ "date" : "Fri, 10 Oct 2025 09:35:34 GMT",
+ "server" : "databricks",
+ "x-databricks-popp-response-code-details" : "via_upstream",
+ "x-databricks-shard-debug" : "oregon-staging",
+ "x-databricks-org-id" : "6051921418418893",
+ "strict-transport-security" : "max-age=31536000; includeSubDomains; preload",
+ "x-content-type-options" : "nosniff",
+ "x-databricks-popp-routing-reason" : "deployment-name",
+ "content-type" : "application/json",
+ "server-timing" : "request_id;dur=0;desc=\"b59b889c-e6b6-4c88-af81-ace62198e27f\", client_protocol;dur=0;desc=\"HTTP/1.1\"",
+ "alt-svc" : "h3=\":5443\"; ma=86400, h3-29=\":5443\"; ma=86400",
+ "x-databricks-apiproxy-response-code-details" : "via_upstream"
+ }
+ },
+ "uuid" : "03a6959b-2f46-41af-9cc0-dfd458a4d0ac",
+ "insertionIndex" : 3
+}
\ No newline at end of file
diff --git a/src/test/resources/sqlexecapi/datatypesintegrationtests/testgeospatialtypes/mappings/oidc_.well-known_oauth-authorization-server-75d1e2b1-4917-4fc3-bc23-63e9201f71c2.json b/src/test/resources/sqlexecapi/datatypesintegrationtests/testgeospatialtypes/mappings/oidc_.well-known_oauth-authorization-server-75d1e2b1-4917-4fc3-bc23-63e9201f71c2.json
new file mode 100644
index 0000000000..1d8f93b433
--- /dev/null
+++ b/src/test/resources/sqlexecapi/datatypesintegrationtests/testgeospatialtypes/mappings/oidc_.well-known_oauth-authorization-server-75d1e2b1-4917-4fc3-bc23-63e9201f71c2.json
@@ -0,0 +1,36 @@
+{
+ "id" : "75d1e2b1-4917-4fc3-bc23-63e9201f71c2",
+ "name" : "oidc_.well-known_oauth-authorization-server",
+ "request" : {
+ "url" : "/oidc/.well-known/oauth-authorization-server",
+ "method" : "GET"
+ },
+ "response" : {
+ "status" : 200,
+ "body" : "{\"authorization_endpoint\":\"https:\\/\\/e2-dogfood.staging.cloud.databricks.com\\/oidc\\/v1\\/authorize\",\"token_endpoint\":\"https:\\/\\/e2-dogfood.staging.cloud.databricks.com\\/oidc\\/v1\\/token\",\"issuer\":\"https:\\/\\/e2-dogfood.staging.cloud.databricks.com\\/oidc\",\"jwks_uri\":\"https:\\/\\/oregon.staging.cloud.databricks.com\\/oidc\\/jwks.json\",\"scopes_supported\":[\"all-apis\",\"apps.apps\",\"catalog.artifact-allowlists\",\"catalog.catalogs\",\"catalog.connections\",\"catalog.credentials\",\"catalog.entity-tag-assignments\",\"catalog.external-lineage\",\"catalog.external-locations\",\"catalog.external-metadata\",\"catalog.functions\",\"catalog.grants\",\"catalog.metastores\",\"catalog.model-versions\",\"catalog.online-tables\",\"catalog.policies\",\"catalog.quality-monitors\",\"catalog.registered-models\",\"catalog.resource-quotas\",\"catalog.rfa\",\"catalog.schemas\",\"catalog.storage-credentials\",\"catalog.system-schemas\",\"catalog.table-constraints\",\"catalog.tables\",\"catalog.temporary-path-credentials\",\"catalog.temporary-table-credentials\",\"catalog.volumes\",\"catalog.workspace-bindings\",\"cleanrooms.clean-room-asset-revisions\",\"cleanrooms.clean-room-assets\",\"cleanrooms.clean-room-auto-approval-rules\",\"cleanrooms.clean-room-task-runs\",\"cleanrooms.clean-rooms\",\"compute.cluster-policies\",\"compute.clusters\",\"compute.command-execution\",\"compute.global-init-scripts\",\"compute.instance-pools\",\"compute.instance-profiles\",\"compute.libraries\",\"compute.policy-compliance-for-clusters\",\"compute.policy-families\",\"dashboards.genie\",\"dashboards.lakeview\",\"dashboards.lakeview-embedded\",\"database.database\",\"email\",\"files.dbfs\",\"files.files\",\"iam.account-access-control-proxy\",\"iam.current-user\",\"iam.groups\",\"iam.permissions\",\"iam.service-principals\",\"iam.users\",\"iamv2.iam\",\"jobs.jobs\",\"jobs.policy-compliance-for-jobs\",\"marketplace.consumer-fulfillments\",\"marketplace.consumer-installations\",\"marketplace.consumer-listings\",\"marketplace.consumer-personalization-requests\",\"marketplace.consumer-providers\",\"marketplace.provider-exchange-filters\",\"marketplace.provider-exchanges\",\"marketplace.provider-files\",\"marketplace.provider-listings\",\"marketplace.provider-personalization-requests\",\"marketplace.provider-provider-analytics-dashboards\",\"marketplace.provider-providers\",\"mcp.external\",\"mcp.functions\",\"mcp.genie\",\"mcp.sql\",\"mcp.vectorsearch\",\"ml.experiments\",\"ml.model-registry\",\"oauth2.service-principal-secrets-proxy\",\"offline_access\",\"openid\",\"pipelines.pipelines\",\"profile\",\"qualitymonitorv2.quality-monitor\",\"serving.serving-endpoints\",\"settings.aibi-dashboard-embedding-access-policy\",\"settings.aibi-dashboard-embedding-approved-domains\",\"settings.automatic-cluster-update\",\"settings.compliance-security-profile\",\"settings.dashboard-email-subscriptions\",\"settings.default-namespace\",\"settings.disable-legacy-access\",\"settings.disable-legacy-dbfs\",\"settings.enable-export-notebook\",\"settings.enable-notebook-table-clipboard\",\"settings.enable-results-downloading\",\"settings.enhanced-security-monitoring\",\"settings.ip-access-lists\",\"settings.notification-destinations\",\"settings.restrict-workspace-admins\",\"settings.sql-results-download\",\"settings.token-management\",\"settings.tokens\",\"settings.workspace-conf\",\"settingsv2.settings\",\"sharing.providers\",\"sharing.recipient-activation\",\"sharing.recipients\",\"sharing.shares\",\"sql\",\"sql.alerts\",\"sql.alerts-legacy\",\"sql.alerts-v2\",\"sql.dashboards\",\"sql.data-sources\",\"sql.dbsql-permissions\",\"sql.driver\",\"sql.queries\",\"sql.queries-legacy\",\"sql.query-history\",\"sql.statement-execution\",\"sql.warehouses\",\"tags.tag-policies\",\"vectorsearch.vector-search-endpoints\",\"vectorsearch.vector-search-indexes\",\"workspace.git-credentials\",\"workspace.repos\",\"workspace.secrets\",\"workspace.workspace\"],\"response_types_supported\":[\"code\",\"id_token\"],\"response_modes_supported\":[\"query\",\"fragment\",\"form_post\"],\"grant_types_supported\":[\"client_credentials\",\"authorization_code\",\"refresh_token\"],\"code_challenge_methods_supported\":[\"S256\"],\"token_endpoint_auth_methods_supported\":[\"client_secret_basic\",\"client_secret_post\",\"none\"],\"subject_types_supported\":[\"public\"],\"id_token_signing_alg_values_supported\":[\"RS256\"],\"claims_supported\":[\"iss\",\"sub\",\"aud\",\"iat\",\"exp\",\"jti\",\"name\",\"family_name\",\"given_name\",\"preferred_username\"],\"request_uri_parameter_supported\":false}",
+ "headers" : {
+ "x-request-id" : "8b5b037b-ab96-4532-9ee6-a983e18118fd",
+ "date" : "Fri, 10 Oct 2025 09:35:26 GMT",
+ "server" : "databricks",
+ "x-databricks-popp-response-code-details" : "via_upstream",
+ "x-databricks-shard-debug" : "oregon-staging",
+ "vary" : "Accept-Encoding",
+ "access-control-allow-headers" : "Content-Type",
+ "x-databricks-org-id" : "6051921418418893",
+ "strict-transport-security" : "max-age=31536000; includeSubDomains; preload",
+ "access-control-allow-methods" : "GET",
+ "access-control-allow-origin" : "*",
+ "x-content-type-options" : "nosniff",
+ "x-databricks-popp-routing-reason" : "deployment-name",
+ "content-type" : "application/json; charset=UTF-8",
+ "server-timing" : "request_id;dur=0;desc=\"8b5b037b-ab96-4532-9ee6-a983e18118fd\", client_protocol;dur=0;desc=\"HTTP/1.1\"",
+ "alt-svc" : "h3=\":5443\"; ma=86400, h3-29=\":5443\"; ma=86400",
+ "x-databricks-apiproxy-response-code-details" : "via_upstream"
+ }
+ },
+ "uuid" : "75d1e2b1-4917-4fc3-bc23-63e9201f71c2",
+ "scenarioName" : "scenario-2-oidc-.well-known-oauth-authorization-server",
+ "requiredScenarioState" : "Started",
+ "newScenarioState" : "scenario-2-oidc-.well-known-oauth-authorization-server-2",
+ "insertionIndex" : 8
+}
\ No newline at end of file
diff --git a/src/test/resources/sqlexecapi/datatypesintegrationtests/testgeospatialtypes/mappings/oidc_.well-known_oauth-authorization-server-872f82e0-1642-4fc5-a033-23dc902aed10.json b/src/test/resources/sqlexecapi/datatypesintegrationtests/testgeospatialtypes/mappings/oidc_.well-known_oauth-authorization-server-872f82e0-1642-4fc5-a033-23dc902aed10.json
new file mode 100644
index 0000000000..16a3edb92c
--- /dev/null
+++ b/src/test/resources/sqlexecapi/datatypesintegrationtests/testgeospatialtypes/mappings/oidc_.well-known_oauth-authorization-server-872f82e0-1642-4fc5-a033-23dc902aed10.json
@@ -0,0 +1,35 @@
+{
+ "id" : "872f82e0-1642-4fc5-a033-23dc902aed10",
+ "name" : "oidc_.well-known_oauth-authorization-server",
+ "request" : {
+ "url" : "/oidc/.well-known/oauth-authorization-server",
+ "method" : "GET"
+ },
+ "response" : {
+ "status" : 200,
+ "body" : "{\"authorization_endpoint\":\"https:\\/\\/e2-dogfood.staging.cloud.databricks.com\\/oidc\\/v1\\/authorize\",\"token_endpoint\":\"https:\\/\\/e2-dogfood.staging.cloud.databricks.com\\/oidc\\/v1\\/token\",\"issuer\":\"https:\\/\\/e2-dogfood.staging.cloud.databricks.com\\/oidc\",\"jwks_uri\":\"https:\\/\\/oregon.staging.cloud.databricks.com\\/oidc\\/jwks.json\",\"scopes_supported\":[\"all-apis\",\"apps.apps\",\"catalog.artifact-allowlists\",\"catalog.catalogs\",\"catalog.connections\",\"catalog.credentials\",\"catalog.entity-tag-assignments\",\"catalog.external-lineage\",\"catalog.external-locations\",\"catalog.external-metadata\",\"catalog.functions\",\"catalog.grants\",\"catalog.metastores\",\"catalog.model-versions\",\"catalog.online-tables\",\"catalog.policies\",\"catalog.quality-monitors\",\"catalog.registered-models\",\"catalog.resource-quotas\",\"catalog.rfa\",\"catalog.schemas\",\"catalog.storage-credentials\",\"catalog.system-schemas\",\"catalog.table-constraints\",\"catalog.tables\",\"catalog.temporary-path-credentials\",\"catalog.temporary-table-credentials\",\"catalog.volumes\",\"catalog.workspace-bindings\",\"cleanrooms.clean-room-asset-revisions\",\"cleanrooms.clean-room-assets\",\"cleanrooms.clean-room-auto-approval-rules\",\"cleanrooms.clean-room-task-runs\",\"cleanrooms.clean-rooms\",\"compute.cluster-policies\",\"compute.clusters\",\"compute.command-execution\",\"compute.global-init-scripts\",\"compute.instance-pools\",\"compute.instance-profiles\",\"compute.libraries\",\"compute.policy-compliance-for-clusters\",\"compute.policy-families\",\"dashboards.genie\",\"dashboards.lakeview\",\"dashboards.lakeview-embedded\",\"database.database\",\"email\",\"files.dbfs\",\"files.files\",\"iam.account-access-control-proxy\",\"iam.current-user\",\"iam.groups\",\"iam.permissions\",\"iam.service-principals\",\"iam.users\",\"iamv2.iam\",\"jobs.jobs\",\"jobs.policy-compliance-for-jobs\",\"marketplace.consumer-fulfillments\",\"marketplace.consumer-installations\",\"marketplace.consumer-listings\",\"marketplace.consumer-personalization-requests\",\"marketplace.consumer-providers\",\"marketplace.provider-exchange-filters\",\"marketplace.provider-exchanges\",\"marketplace.provider-files\",\"marketplace.provider-listings\",\"marketplace.provider-personalization-requests\",\"marketplace.provider-provider-analytics-dashboards\",\"marketplace.provider-providers\",\"mcp.external\",\"mcp.functions\",\"mcp.genie\",\"mcp.sql\",\"mcp.vectorsearch\",\"ml.experiments\",\"ml.model-registry\",\"oauth2.service-principal-secrets-proxy\",\"offline_access\",\"openid\",\"pipelines.pipelines\",\"profile\",\"qualitymonitorv2.quality-monitor\",\"serving.serving-endpoints\",\"settings.aibi-dashboard-embedding-access-policy\",\"settings.aibi-dashboard-embedding-approved-domains\",\"settings.automatic-cluster-update\",\"settings.compliance-security-profile\",\"settings.dashboard-email-subscriptions\",\"settings.default-namespace\",\"settings.disable-legacy-access\",\"settings.disable-legacy-dbfs\",\"settings.enable-export-notebook\",\"settings.enable-notebook-table-clipboard\",\"settings.enable-results-downloading\",\"settings.enhanced-security-monitoring\",\"settings.ip-access-lists\",\"settings.notification-destinations\",\"settings.restrict-workspace-admins\",\"settings.sql-results-download\",\"settings.token-management\",\"settings.tokens\",\"settings.workspace-conf\",\"settingsv2.settings\",\"sharing.providers\",\"sharing.recipient-activation\",\"sharing.recipients\",\"sharing.shares\",\"sql\",\"sql.alerts\",\"sql.alerts-legacy\",\"sql.alerts-v2\",\"sql.dashboards\",\"sql.data-sources\",\"sql.dbsql-permissions\",\"sql.driver\",\"sql.queries\",\"sql.queries-legacy\",\"sql.query-history\",\"sql.statement-execution\",\"sql.warehouses\",\"tags.tag-policies\",\"vectorsearch.vector-search-endpoints\",\"vectorsearch.vector-search-indexes\",\"workspace.git-credentials\",\"workspace.repos\",\"workspace.secrets\",\"workspace.workspace\"],\"response_types_supported\":[\"code\",\"id_token\"],\"response_modes_supported\":[\"query\",\"fragment\",\"form_post\"],\"grant_types_supported\":[\"client_credentials\",\"authorization_code\",\"refresh_token\"],\"code_challenge_methods_supported\":[\"S256\"],\"token_endpoint_auth_methods_supported\":[\"client_secret_basic\",\"client_secret_post\",\"none\"],\"subject_types_supported\":[\"public\"],\"id_token_signing_alg_values_supported\":[\"RS256\"],\"claims_supported\":[\"iss\",\"sub\",\"aud\",\"iat\",\"exp\",\"jti\",\"name\",\"family_name\",\"given_name\",\"preferred_username\"],\"request_uri_parameter_supported\":false}",
+ "headers" : {
+ "x-request-id" : "dfe1201f-10ce-430a-81ba-0404d9248468",
+ "date" : "Fri, 10 Oct 2025 09:35:28 GMT",
+ "server" : "databricks",
+ "x-databricks-popp-response-code-details" : "via_upstream",
+ "x-databricks-shard-debug" : "oregon-staging",
+ "vary" : "Accept-Encoding",
+ "access-control-allow-headers" : "Content-Type",
+ "x-databricks-org-id" : "6051921418418893",
+ "strict-transport-security" : "max-age=31536000; includeSubDomains; preload",
+ "access-control-allow-methods" : "GET",
+ "access-control-allow-origin" : "*",
+ "x-content-type-options" : "nosniff",
+ "x-databricks-popp-routing-reason" : "deployment-name",
+ "content-type" : "application/json; charset=UTF-8",
+ "server-timing" : "request_id;dur=0;desc=\"dfe1201f-10ce-430a-81ba-0404d9248468\", client_protocol;dur=0;desc=\"HTTP/1.1\"",
+ "alt-svc" : "h3=\":5443\"; ma=86400, h3-29=\":5443\"; ma=86400",
+ "x-databricks-apiproxy-response-code-details" : "via_upstream"
+ }
+ },
+ "uuid" : "872f82e0-1642-4fc5-a033-23dc902aed10",
+ "scenarioName" : "scenario-2-oidc-.well-known-oauth-authorization-server",
+ "requiredScenarioState" : "scenario-2-oidc-.well-known-oauth-authorization-server-2",
+ "insertionIndex" : 6
+}
\ No newline at end of file
diff --git a/src/test/resources/thriftserverapi/datatypesintegrationtests/testgeospatialtypes/mappings/oidc_.well-known_oauth-authorization-server-27a67ec3-b459-49cd-9d6f-f2cdf5ecd35a.json b/src/test/resources/thriftserverapi/datatypesintegrationtests/testgeospatialtypes/mappings/oidc_.well-known_oauth-authorization-server-27a67ec3-b459-49cd-9d6f-f2cdf5ecd35a.json
new file mode 100644
index 0000000000..da6984e000
--- /dev/null
+++ b/src/test/resources/thriftserverapi/datatypesintegrationtests/testgeospatialtypes/mappings/oidc_.well-known_oauth-authorization-server-27a67ec3-b459-49cd-9d6f-f2cdf5ecd35a.json
@@ -0,0 +1,33 @@
+{
+ "id" : "27a67ec3-b459-49cd-9d6f-f2cdf5ecd35a",
+ "name" : "oidc_.well-known_oauth-authorization-server",
+ "request" : {
+ "url" : "/oidc/.well-known/oauth-authorization-server",
+ "method" : "GET"
+ },
+ "response" : {
+ "status" : 200,
+ "body" : "{\"authorization_endpoint\":\"https:\\/\\/e2-dogfood.staging.cloud.databricks.com\\/oidc\\/v1\\/authorize\",\"token_endpoint\":\"https:\\/\\/e2-dogfood.staging.cloud.databricks.com\\/oidc\\/v1\\/token\",\"issuer\":\"https:\\/\\/e2-dogfood.staging.cloud.databricks.com\\/oidc\",\"jwks_uri\":\"https:\\/\\/oregon.staging.cloud.databricks.com\\/oidc\\/jwks.json\",\"scopes_supported\":[\"all-apis\",\"apps.apps\",\"catalog.artifact-allowlists\",\"catalog.catalogs\",\"catalog.connections\",\"catalog.credentials\",\"catalog.entity-tag-assignments\",\"catalog.external-lineage\",\"catalog.external-locations\",\"catalog.external-metadata\",\"catalog.functions\",\"catalog.grants\",\"catalog.metastores\",\"catalog.model-versions\",\"catalog.online-tables\",\"catalog.policies\",\"catalog.quality-monitors\",\"catalog.registered-models\",\"catalog.resource-quotas\",\"catalog.rfa\",\"catalog.schemas\",\"catalog.storage-credentials\",\"catalog.system-schemas\",\"catalog.table-constraints\",\"catalog.tables\",\"catalog.temporary-path-credentials\",\"catalog.temporary-table-credentials\",\"catalog.volumes\",\"catalog.workspace-bindings\",\"cleanrooms.clean-room-asset-revisions\",\"cleanrooms.clean-room-assets\",\"cleanrooms.clean-room-auto-approval-rules\",\"cleanrooms.clean-room-task-runs\",\"cleanrooms.clean-rooms\",\"compute.cluster-policies\",\"compute.clusters\",\"compute.command-execution\",\"compute.global-init-scripts\",\"compute.instance-pools\",\"compute.instance-profiles\",\"compute.libraries\",\"compute.policy-compliance-for-clusters\",\"compute.policy-families\",\"dashboards.genie\",\"dashboards.lakeview\",\"dashboards.lakeview-embedded\",\"database.database\",\"dataquality.data-quality\",\"email\",\"files.dbfs\",\"files.files\",\"iam.account-access-control-proxy\",\"iam.current-user\",\"iam.groups\",\"iam.permissions\",\"iam.service-principals\",\"iam.users\",\"iamv2.iam\",\"jobs.jobs\",\"jobs.policy-compliance-for-jobs\",\"marketplace.consumer-fulfillments\",\"marketplace.consumer-installations\",\"marketplace.consumer-listings\",\"marketplace.consumer-personalization-requests\",\"marketplace.consumer-providers\",\"marketplace.provider-exchange-filters\",\"marketplace.provider-exchanges\",\"marketplace.provider-files\",\"marketplace.provider-listings\",\"marketplace.provider-personalization-requests\",\"marketplace.provider-provider-analytics-dashboards\",\"marketplace.provider-providers\",\"mcp.external\",\"mcp.functions\",\"mcp.genie\",\"mcp.sql\",\"mcp.vectorsearch\",\"ml.experiments\",\"ml.model-registry\",\"oauth2.service-principal-secrets-proxy\",\"offline_access\",\"openid\",\"pipelines.pipelines\",\"profile\",\"qualitymonitorv2.quality-monitor\",\"serving.serving-endpoints\",\"settings.aibi-dashboard-embedding-access-policy\",\"settings.aibi-dashboard-embedding-approved-domains\",\"settings.automatic-cluster-update\",\"settings.compliance-security-profile\",\"settings.dashboard-email-subscriptions\",\"settings.default-namespace\",\"settings.disable-legacy-access\",\"settings.disable-legacy-dbfs\",\"settings.enable-export-notebook\",\"settings.enable-notebook-table-clipboard\",\"settings.enable-results-downloading\",\"settings.enhanced-security-monitoring\",\"settings.ip-access-lists\",\"settings.notification-destinations\",\"settings.restrict-workspace-admins\",\"settings.sql-results-download\",\"settings.token-management\",\"settings.tokens\",\"settings.workspace-conf\",\"settingsv2.settings\",\"sharing.providers\",\"sharing.recipient-activation\",\"sharing.recipient-federation-policies\",\"sharing.recipients\",\"sharing.shares\",\"sql\",\"sql.alerts\",\"sql.alerts-legacy\",\"sql.alerts-v2\",\"sql.dashboards\",\"sql.data-sources\",\"sql.dbsql-permissions\",\"sql.driver\",\"sql.queries\",\"sql.queries-legacy\",\"sql.query-history\",\"sql.statement-execution\",\"sql.warehouses\",\"tags.tag-policies\",\"vectorsearch.vector-search-endpoints\",\"vectorsearch.vector-search-indexes\",\"workspace.git-credentials\",\"workspace.repos\",\"workspace.secrets\",\"workspace.workspace\"],\"response_types_supported\":[\"code\",\"id_token\"],\"response_modes_supported\":[\"query\",\"fragment\",\"form_post\"],\"grant_types_supported\":[\"client_credentials\",\"authorization_code\",\"refresh_token\"],\"code_challenge_methods_supported\":[\"S256\"],\"token_endpoint_auth_methods_supported\":[\"client_secret_basic\",\"client_secret_post\",\"none\"],\"subject_types_supported\":[\"public\"],\"id_token_signing_alg_values_supported\":[\"RS256\"],\"claims_supported\":[\"iss\",\"sub\",\"aud\",\"iat\",\"exp\",\"jti\",\"name\",\"family_name\",\"given_name\",\"preferred_username\"],\"request_uri_parameter_supported\":false}",
+ "headers" : {
+ "x-request-id" : "6398af77-8a27-4154-b641-264b2a96e878",
+ "date" : "Fri, 24 Oct 2025 09:13:20 GMT",
+ "server" : "databricks",
+ "x-databricks-popp-response-code-details" : "via_upstream",
+ "x-databricks-shard-debug" : "oregon-staging",
+ "vary" : "Accept-Encoding",
+ "x-databricks-org-id" : "6051921418418893",
+ "strict-transport-security" : "max-age=31536000; includeSubDomains; preload",
+ "x-content-type-options" : "nosniff",
+ "x-databricks-popp-routing-reason" : "deployment-name",
+ "content-type" : "application/json; charset=UTF-8",
+ "server-timing" : "request_id;dur=0;desc=\"6398af77-8a27-4154-b641-264b2a96e878\", client_protocol;dur=0;desc=\"HTTP/1.1\"",
+ "alt-svc" : "h3=\":5443\"; ma=86400, h3-29=\":5443\"; ma=86400",
+ "x-databricks-apiproxy-response-code-details" : "via_upstream"
+ }
+ },
+ "uuid" : "27a67ec3-b459-49cd-9d6f-f2cdf5ecd35a",
+ "scenarioName" : "scenario-2-oidc-.well-known-oauth-authorization-server",
+ "requiredScenarioState" : "Started",
+ "newScenarioState" : "scenario-2-oidc-.well-known-oauth-authorization-server-2",
+ "insertionIndex" : 6
+}
\ No newline at end of file
diff --git a/src/test/resources/thriftserverapi/datatypesintegrationtests/testgeospatialtypes/mappings/oidc_.well-known_oauth-authorization-server-a917e9cc-d745-4586-97f4-d6d4701804fd.json b/src/test/resources/thriftserverapi/datatypesintegrationtests/testgeospatialtypes/mappings/oidc_.well-known_oauth-authorization-server-a917e9cc-d745-4586-97f4-d6d4701804fd.json
new file mode 100644
index 0000000000..3c97117b70
--- /dev/null
+++ b/src/test/resources/thriftserverapi/datatypesintegrationtests/testgeospatialtypes/mappings/oidc_.well-known_oauth-authorization-server-a917e9cc-d745-4586-97f4-d6d4701804fd.json
@@ -0,0 +1,32 @@
+{
+ "id" : "a917e9cc-d745-4586-97f4-d6d4701804fd",
+ "name" : "oidc_.well-known_oauth-authorization-server",
+ "request" : {
+ "url" : "/oidc/.well-known/oauth-authorization-server",
+ "method" : "GET"
+ },
+ "response" : {
+ "status" : 200,
+ "body" : "{\"authorization_endpoint\":\"https:\\/\\/e2-dogfood.staging.cloud.databricks.com\\/oidc\\/v1\\/authorize\",\"token_endpoint\":\"https:\\/\\/e2-dogfood.staging.cloud.databricks.com\\/oidc\\/v1\\/token\",\"issuer\":\"https:\\/\\/e2-dogfood.staging.cloud.databricks.com\\/oidc\",\"jwks_uri\":\"https:\\/\\/oregon.staging.cloud.databricks.com\\/oidc\\/jwks.json\",\"scopes_supported\":[\"all-apis\",\"apps.apps\",\"catalog.artifact-allowlists\",\"catalog.catalogs\",\"catalog.connections\",\"catalog.credentials\",\"catalog.entity-tag-assignments\",\"catalog.external-lineage\",\"catalog.external-locations\",\"catalog.external-metadata\",\"catalog.functions\",\"catalog.grants\",\"catalog.metastores\",\"catalog.model-versions\",\"catalog.online-tables\",\"catalog.policies\",\"catalog.quality-monitors\",\"catalog.registered-models\",\"catalog.resource-quotas\",\"catalog.rfa\",\"catalog.schemas\",\"catalog.storage-credentials\",\"catalog.system-schemas\",\"catalog.table-constraints\",\"catalog.tables\",\"catalog.temporary-path-credentials\",\"catalog.temporary-table-credentials\",\"catalog.volumes\",\"catalog.workspace-bindings\",\"cleanrooms.clean-room-asset-revisions\",\"cleanrooms.clean-room-assets\",\"cleanrooms.clean-room-auto-approval-rules\",\"cleanrooms.clean-room-task-runs\",\"cleanrooms.clean-rooms\",\"compute.cluster-policies\",\"compute.clusters\",\"compute.command-execution\",\"compute.global-init-scripts\",\"compute.instance-pools\",\"compute.instance-profiles\",\"compute.libraries\",\"compute.policy-compliance-for-clusters\",\"compute.policy-families\",\"dashboards.genie\",\"dashboards.lakeview\",\"dashboards.lakeview-embedded\",\"database.database\",\"dataquality.data-quality\",\"email\",\"files.dbfs\",\"files.files\",\"iam.account-access-control-proxy\",\"iam.current-user\",\"iam.groups\",\"iam.permissions\",\"iam.service-principals\",\"iam.users\",\"iamv2.iam\",\"jobs.jobs\",\"jobs.policy-compliance-for-jobs\",\"marketplace.consumer-fulfillments\",\"marketplace.consumer-installations\",\"marketplace.consumer-listings\",\"marketplace.consumer-personalization-requests\",\"marketplace.consumer-providers\",\"marketplace.provider-exchange-filters\",\"marketplace.provider-exchanges\",\"marketplace.provider-files\",\"marketplace.provider-listings\",\"marketplace.provider-personalization-requests\",\"marketplace.provider-provider-analytics-dashboards\",\"marketplace.provider-providers\",\"mcp.external\",\"mcp.functions\",\"mcp.genie\",\"mcp.sql\",\"mcp.vectorsearch\",\"ml.experiments\",\"ml.model-registry\",\"oauth2.service-principal-secrets-proxy\",\"offline_access\",\"openid\",\"pipelines.pipelines\",\"profile\",\"qualitymonitorv2.quality-monitor\",\"serving.serving-endpoints\",\"settings.aibi-dashboard-embedding-access-policy\",\"settings.aibi-dashboard-embedding-approved-domains\",\"settings.automatic-cluster-update\",\"settings.compliance-security-profile\",\"settings.dashboard-email-subscriptions\",\"settings.default-namespace\",\"settings.disable-legacy-access\",\"settings.disable-legacy-dbfs\",\"settings.enable-export-notebook\",\"settings.enable-notebook-table-clipboard\",\"settings.enable-results-downloading\",\"settings.enhanced-security-monitoring\",\"settings.ip-access-lists\",\"settings.notification-destinations\",\"settings.restrict-workspace-admins\",\"settings.sql-results-download\",\"settings.token-management\",\"settings.tokens\",\"settings.workspace-conf\",\"settingsv2.settings\",\"sharing.providers\",\"sharing.recipient-activation\",\"sharing.recipient-federation-policies\",\"sharing.recipients\",\"sharing.shares\",\"sql\",\"sql.alerts\",\"sql.alerts-legacy\",\"sql.alerts-v2\",\"sql.dashboards\",\"sql.data-sources\",\"sql.dbsql-permissions\",\"sql.driver\",\"sql.queries\",\"sql.queries-legacy\",\"sql.query-history\",\"sql.statement-execution\",\"sql.warehouses\",\"tags.tag-policies\",\"vectorsearch.vector-search-endpoints\",\"vectorsearch.vector-search-indexes\",\"workspace.git-credentials\",\"workspace.repos\",\"workspace.secrets\",\"workspace.workspace\"],\"response_types_supported\":[\"code\",\"id_token\"],\"response_modes_supported\":[\"query\",\"fragment\",\"form_post\"],\"grant_types_supported\":[\"client_credentials\",\"authorization_code\",\"refresh_token\"],\"code_challenge_methods_supported\":[\"S256\"],\"token_endpoint_auth_methods_supported\":[\"client_secret_basic\",\"client_secret_post\",\"none\"],\"subject_types_supported\":[\"public\"],\"id_token_signing_alg_values_supported\":[\"RS256\"],\"claims_supported\":[\"iss\",\"sub\",\"aud\",\"iat\",\"exp\",\"jti\",\"name\",\"family_name\",\"given_name\",\"preferred_username\"],\"request_uri_parameter_supported\":false}",
+ "headers" : {
+ "x-request-id" : "2ea66a34-ef7a-4d2e-80ca-0e188dd6cfe0",
+ "date" : "Fri, 24 Oct 2025 09:13:22 GMT",
+ "server" : "databricks",
+ "x-databricks-popp-response-code-details" : "via_upstream",
+ "x-databricks-shard-debug" : "oregon-staging",
+ "vary" : "Accept-Encoding",
+ "x-databricks-org-id" : "6051921418418893",
+ "strict-transport-security" : "max-age=31536000; includeSubDomains; preload",
+ "x-content-type-options" : "nosniff",
+ "x-databricks-popp-routing-reason" : "deployment-name",
+ "content-type" : "application/json; charset=UTF-8",
+ "server-timing" : "request_id;dur=0;desc=\"2ea66a34-ef7a-4d2e-80ca-0e188dd6cfe0\", client_protocol;dur=0;desc=\"HTTP/1.1\"",
+ "alt-svc" : "h3=\":5443\"; ma=86400, h3-29=\":5443\"; ma=86400",
+ "x-databricks-apiproxy-response-code-details" : "via_upstream"
+ }
+ },
+ "uuid" : "a917e9cc-d745-4586-97f4-d6d4701804fd",
+ "scenarioName" : "scenario-2-oidc-.well-known-oauth-authorization-server",
+ "requiredScenarioState" : "scenario-2-oidc-.well-known-oauth-authorization-server-2",
+ "insertionIndex" : 4
+}
\ No newline at end of file
diff --git a/src/test/resources/thriftserverapi/datatypesintegrationtests/testgeospatialtypes/mappings/sql_protocolv1_o_6051921418418893_0819-204509-hill72-2633ce3d-b2d7-44ce-b0c1-fc3ba4fc18ec.json b/src/test/resources/thriftserverapi/datatypesintegrationtests/testgeospatialtypes/mappings/sql_protocolv1_o_6051921418418893_0819-204509-hill72-2633ce3d-b2d7-44ce-b0c1-fc3ba4fc18ec.json
new file mode 100644
index 0000000000..0e610868a9
--- /dev/null
+++ b/src/test/resources/thriftserverapi/datatypesintegrationtests/testgeospatialtypes/mappings/sql_protocolv1_o_6051921418418893_0819-204509-hill72-2633ce3d-b2d7-44ce-b0c1-fc3ba4fc18ec.json
@@ -0,0 +1,34 @@
+{
+ "id" : "2633ce3d-b2d7-44ce-b0c1-fc3ba4fc18ec",
+ "name" : "sql_protocolv1_o_6051921418418893_0819-204509-hill72",
+ "request" : {
+ "url" : "/sql/protocolv1/o/6051921418418893/0819-204509-hill72",
+ "method" : "POST",
+ "bodyPatterns" : [ {
+ "binaryEqualTo" : "gAEAAQAAAAxDbG9zZVNlc3Npb24AAAACDAABDAABDAABCwABAAAAEAnNQTevcEGqkAQucLP7Kb0LAAIAAAAQ4AypzuZjTMObzvkAoICHTgAAAAA="
+ } ]
+ },
+ "response" : {
+ "status" : 200,
+ "base64Body" : "gAEAAgAAAAxDbG9zZVNlc3Npb24AAAACDAAADAABCAABAAAAAAAAAA==",
+ "headers" : {
+ "x-request-id" : "e00003a5-f35d-4333-aa14-22a30eede082",
+ "date" : "Fri, 24 Oct 2025 09:13:25 GMT,Fri, 24 Oct 2025 09:13:25 GMT",
+ "server" : "databricks",
+ "x-databricks-popp-response-code-details" : "via_upstream",
+ "x-databricks-shard-debug" : "oregon-staging",
+ "x-frame-options" : "SAMEORIGIN",
+ "x-databricks-org-id" : "6051921418418893",
+ "strict-transport-security" : "max-age=31536000; includeSubDomains; preload",
+ "x-content-type-options" : "nosniff",
+ "x-xss-protection" : "1; mode=block",
+ "x-databricks-popp-routing-reason" : "deployment-name",
+ "content-type" : "application/x-thrift",
+ "server-timing" : "request_id;dur=0;desc=\"e00003a5-f35d-4333-aa14-22a30eede082\", client_protocol;dur=0;desc=\"HTTP/1.1\"",
+ "alt-svc" : "h3=\":5443\"; ma=86400, h3-29=\":5443\"; ma=86400",
+ "x-databricks-apiproxy-response-code-details" : "via_upstream"
+ }
+ },
+ "uuid" : "2633ce3d-b2d7-44ce-b0c1-fc3ba4fc18ec",
+ "insertionIndex" : 1
+}
\ No newline at end of file
diff --git a/src/test/resources/thriftserverapi/datatypesintegrationtests/testgeospatialtypes/mappings/sql_protocolv1_o_6051921418418893_0819-204509-hill72-293a9649-9cf5-48ed-820e-e9177e852c1a.json b/src/test/resources/thriftserverapi/datatypesintegrationtests/testgeospatialtypes/mappings/sql_protocolv1_o_6051921418418893_0819-204509-hill72-293a9649-9cf5-48ed-820e-e9177e852c1a.json
new file mode 100644
index 0000000000..ff712dbe7b
--- /dev/null
+++ b/src/test/resources/thriftserverapi/datatypesintegrationtests/testgeospatialtypes/mappings/sql_protocolv1_o_6051921418418893_0819-204509-hill72-293a9649-9cf5-48ed-820e-e9177e852c1a.json
@@ -0,0 +1,36 @@
+{
+ "id" : "293a9649-9cf5-48ed-820e-e9177e852c1a",
+ "name" : "sql_protocolv1_o_6051921418418893_0819-204509-hill72",
+ "request" : {
+ "url" : "/sql/protocolv1/o/6051921418418893/0819-204509-hill72",
+ "method" : "POST",
+ "bodyPatterns" : [ {
+ "binaryEqualTo" : "gAEAAQAAAAtPcGVuU2Vzc2lvbgAAAAEMAAEIAAH////5DQAECwsAAAAACgUCAAAAAAAApQkMBQQLAAEAAAAFU1BBUksLAAIAAAAHZGVmYXVsdAACBQUBAAA="
+ } ]
+ },
+ "response" : {
+ "status" : 200,
+ "base64Body" : "gAEAAgAAAAtPcGVuU2Vzc2lvbgAAAAEMAAAMAAEIAAEAAAAAAAgAAgAApQgMAAMMAAELAAEAAAAQCc1BN69wQaqQBC5ws/spvQsAAgAAABDgDKnO5mNMw5vO+QCggIdOBg0BAAAACA0BAAClCAANAAQLCwAAAAAMBQQLAAEAAAAFc3BhcmsLAAIAAAAHZGVmYXVsdAACBQUBAAA=",
+ "headers" : {
+ "x-request-id" : "c30cf2c5-0788-4f11-959d-0cbf52033345",
+ "date" : "Fri, 24 Oct 2025 09:13:23 GMT,Fri, 24 Oct 2025 09:13:23 GMT",
+ "server" : "databricks",
+ "x-databricks-popp-response-code-details" : "via_upstream",
+ "x-databricks-shard-debug" : "oregon-staging",
+ "x-frame-options" : "SAMEORIGIN",
+ "x-databricks-org-id" : "6051921418418893",
+ "strict-transport-security" : "max-age=31536000; includeSubDomains; preload",
+ "x-content-type-options" : "nosniff",
+ "x-xss-protection" : "1; mode=block",
+ "x-databricks-popp-routing-reason" : "deployment-name",
+ "content-type" : "application/x-thrift",
+ "server-timing" : "request_id;dur=0;desc=\"c30cf2c5-0788-4f11-959d-0cbf52033345\", client_protocol;dur=0;desc=\"HTTP/1.1\"",
+ "alt-svc" : "h3=\":5443\"; ma=86400, h3-29=\":5443\"; ma=86400",
+ "x-databricks-apiproxy-response-code-details" : "via_upstream"
+ }
+ },
+ "uuid" : "293a9649-9cf5-48ed-820e-e9177e852c1a",
+ "scenarioName" : "scenario-1-sql-protocolv1-o-6051921418418893-0819-204509-hill72",
+ "requiredScenarioState" : "scenario-1-sql-protocolv1-o-6051921418418893-0819-204509-hill72-2",
+ "insertionIndex" : 3
+}
\ No newline at end of file
diff --git a/src/test/resources/thriftserverapi/datatypesintegrationtests/testgeospatialtypes/mappings/sql_protocolv1_o_6051921418418893_0819-204509-hill72-33b87d3b-3ae5-4f26-aefd-f74b47230318.json b/src/test/resources/thriftserverapi/datatypesintegrationtests/testgeospatialtypes/mappings/sql_protocolv1_o_6051921418418893_0819-204509-hill72-33b87d3b-3ae5-4f26-aefd-f74b47230318.json
new file mode 100644
index 0000000000..0f5bcbc188
--- /dev/null
+++ b/src/test/resources/thriftserverapi/datatypesintegrationtests/testgeospatialtypes/mappings/sql_protocolv1_o_6051921418418893_0819-204509-hill72-33b87d3b-3ae5-4f26-aefd-f74b47230318.json
@@ -0,0 +1,34 @@
+{
+ "id" : "33b87d3b-3ae5-4f26-aefd-f74b47230318",
+ "name" : "sql_protocolv1_o_6051921418418893_0819-204509-hill72",
+ "request" : {
+ "url" : "/sql/protocolv1/o/6051921418418893/0819-204509-hill72",
+ "method" : "POST",
+ "bodyPatterns" : [ {
+ "binaryEqualTo" : "gAEAAQAAAAxDbG9zZVNlc3Npb24AAAACDAABDAABDAABCwABAAAAEHT3AdS4M0fbvr9VNZLKF4QLAAIAAAAQuhmVKxnqTW+o2AcGqJWHnQAAAAA="
+ } ]
+ },
+ "response" : {
+ "status" : 200,
+ "base64Body" : "gAEAAgAAAAxDbG9zZVNlc3Npb24AAAACDAAADAABCAABAAAAAAAAAA==",
+ "headers" : {
+ "x-request-id" : "7c4d98c5-9310-444a-8d60-71b75871ebbc",
+ "date" : "Fri, 24 Oct 2025 09:13:24 GMT,Fri, 24 Oct 2025 09:13:24 GMT",
+ "server" : "databricks",
+ "x-databricks-popp-response-code-details" : "via_upstream",
+ "x-databricks-shard-debug" : "oregon-staging",
+ "x-frame-options" : "SAMEORIGIN",
+ "x-databricks-org-id" : "6051921418418893",
+ "strict-transport-security" : "max-age=31536000; includeSubDomains; preload",
+ "x-content-type-options" : "nosniff",
+ "x-xss-protection" : "1; mode=block",
+ "x-databricks-popp-routing-reason" : "deployment-name",
+ "content-type" : "application/x-thrift",
+ "server-timing" : "request_id;dur=0;desc=\"7c4d98c5-9310-444a-8d60-71b75871ebbc\", client_protocol;dur=0;desc=\"HTTP/1.1\"",
+ "alt-svc" : "h3=\":5443\"; ma=86400, h3-29=\":5443\"; ma=86400",
+ "x-databricks-apiproxy-response-code-details" : "via_upstream"
+ }
+ },
+ "uuid" : "33b87d3b-3ae5-4f26-aefd-f74b47230318",
+ "insertionIndex" : 2
+}
\ No newline at end of file
diff --git a/src/test/resources/thriftserverapi/datatypesintegrationtests/testgeospatialtypes/mappings/sql_protocolv1_o_6051921418418893_0819-204509-hill72-535e7132-d129-4dae-9556-fb04bf7e09ac.json b/src/test/resources/thriftserverapi/datatypesintegrationtests/testgeospatialtypes/mappings/sql_protocolv1_o_6051921418418893_0819-204509-hill72-535e7132-d129-4dae-9556-fb04bf7e09ac.json
new file mode 100644
index 0000000000..4b452253d5
--- /dev/null
+++ b/src/test/resources/thriftserverapi/datatypesintegrationtests/testgeospatialtypes/mappings/sql_protocolv1_o_6051921418418893_0819-204509-hill72-535e7132-d129-4dae-9556-fb04bf7e09ac.json
@@ -0,0 +1,37 @@
+{
+ "id" : "535e7132-d129-4dae-9556-fb04bf7e09ac",
+ "name" : "sql_protocolv1_o_6051921418418893_0819-204509-hill72",
+ "request" : {
+ "url" : "/sql/protocolv1/o/6051921418418893/0819-204509-hill72",
+ "method" : "POST",
+ "bodyPatterns" : [ {
+ "binaryEqualTo" : "gAEAAQAAAAtPcGVuU2Vzc2lvbgAAAAEMAAEIAAH////5DQAECwsAAAAACgUCAAAAAAAApQkMBQQLAAEAAAAFU1BBUksLAAIAAAAHZGVmYXVsdAACBQUBAAA="
+ } ]
+ },
+ "response" : {
+ "status" : 200,
+ "base64Body" : "gAEAAgAAAAtPcGVuU2Vzc2lvbgAAAAEMAAAMAAEIAAEAAAAAAAgAAgAApQgMAAMMAAELAAEAAAAQdPcB1LgzR9u+v1U1ksoXhAsAAgAAABC6GZUrGepNb6jYBwaolYedBg0BAAAACA0BAAClCAANAAQLCwAAAAAMBQQLAAEAAAAFc3BhcmsLAAIAAAAHZGVmYXVsdAACBQUBAAA=",
+ "headers" : {
+ "x-request-id" : "434b02f0-1e52-4dbd-afc4-8abc17cdd547",
+ "date" : "Fri, 24 Oct 2025 09:13:21 GMT,Fri, 24 Oct 2025 09:13:21 GMT",
+ "server" : "databricks",
+ "x-databricks-popp-response-code-details" : "via_upstream",
+ "x-databricks-shard-debug" : "oregon-staging",
+ "x-frame-options" : "SAMEORIGIN",
+ "x-databricks-org-id" : "6051921418418893",
+ "strict-transport-security" : "max-age=31536000; includeSubDomains; preload",
+ "x-content-type-options" : "nosniff",
+ "x-xss-protection" : "1; mode=block",
+ "x-databricks-popp-routing-reason" : "deployment-name",
+ "content-type" : "application/x-thrift",
+ "server-timing" : "request_id;dur=0;desc=\"434b02f0-1e52-4dbd-afc4-8abc17cdd547\", client_protocol;dur=0;desc=\"HTTP/1.1\"",
+ "alt-svc" : "h3=\":5443\"; ma=86400, h3-29=\":5443\"; ma=86400",
+ "x-databricks-apiproxy-response-code-details" : "via_upstream"
+ }
+ },
+ "uuid" : "535e7132-d129-4dae-9556-fb04bf7e09ac",
+ "scenarioName" : "scenario-1-sql-protocolv1-o-6051921418418893-0819-204509-hill72",
+ "requiredScenarioState" : "Started",
+ "newScenarioState" : "scenario-1-sql-protocolv1-o-6051921418418893-0819-204509-hill72-2",
+ "insertionIndex" : 5
+}
\ No newline at end of file