diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/Geography.java b/src/main/java/com/microsoft/sqlserver/jdbc/Geography.java index a1bdca638..d8dd00929 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/Geography.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/Geography.java @@ -9,30 +9,33 @@ import java.nio.ByteOrder; +/** + * Geography datatype represents data in a round-earth coordinate system. + */ + public class Geography extends SQLServerSpatialDatatype { /** - * Private constructor used for creating a Geography object from WKT and srid. + * Private constructor used for creating a Geography object from WKT and Spatial Reference Identifier. * - * @param WellKnownText + * @param wkt * Well-Known Text (WKT) provided by the user. * @param srid * Spatial Reference Identifier (SRID) provided by the user. * @throws SQLServerException * if an exception occurs */ - private Geography(String WellKnownText, int srid) throws SQLServerException { - this.wkt = WellKnownText; + private Geography(String wkt, int srid) throws SQLServerException { + if (null == wkt || wkt.length() <= 0) { + throwIllegalWKT(); + } + + this.wkt = wkt; this.srid = srid; - try { - parseWKTForSerialization(this, currentWktPos, -1, false); - } catch (StringIndexOutOfBoundsException e) { - String strError = SQLServerException.getErrString("R_illegalWKT"); - throw new SQLServerException(strError, null, 0, null); - } + parseWKTForSerialization(this, currentWktPos, -1, false); - serializeToWkb(false); + serializeToWkb(false, this); isNull = false; } @@ -45,11 +48,15 @@ private Geography(String WellKnownText, int srid) throws SQLServerException { * if an exception occurs */ private Geography(byte[] wkb) throws SQLServerException { + if (null == wkb || wkb.length <= 0) { + throwIllegalWKB(); + } + this.wkb = wkb; buffer = ByteBuffer.wrap(wkb); buffer.order(ByteOrder.LITTLE_ENDIAN); - parseWkb(); + parseWkb(this); WKTsb = new StringBuffer(); WKTsbNoZM = new StringBuffer(); @@ -62,8 +69,8 @@ private Geography(byte[] wkb) throws SQLServerException { } /** - * Returns a Geography instance from an Open Geospatial Consortium (OGC) Well-Known Text (WKT) representation - * augmented with any Z (elevation) and M (measure) values carried by the instance. + * Constructor for a Geography instance from an Open Geospatial Consortium (OGC) Well-Known Text (WKT) + * representation augmented with any Z (elevation) and M (measure) values carried by the instance. * * @param wkt * Well-Known Text (WKT) provided by the user. @@ -78,7 +85,8 @@ public static Geography STGeomFromText(String wkt, int srid) throws SQLServerExc } /** - * Returns a Geography instance from an Open Geospatial Consortium (OGC) Well-Known Binary (WKB) representation. + * Constructor for a Geography instance from an Open Geospatial Consortium (OGC) Well-Known Binary (WKB) + * representation. * * @param wkb * Well-Known Binary (WKB) provided by the user. @@ -91,7 +99,7 @@ public static Geography STGeomFromWKB(byte[] wkb) throws SQLServerException { } /** - * Returns a constructed Geography from an internal SQL Server format for spatial data. + * Constructor for a Geography instance from an internal SQL Server format for spatial data. * * @param wkb * Well-Known Binary (WKB) provided by the user. @@ -104,8 +112,8 @@ public static Geography deserialize(byte[] wkb) throws SQLServerException { } /** - * Returns a Geography instance from an Open Geospatial Consortium (OGC) Well-Known Text (WKT) representation. SRID - * is defaulted to 4326. + * Constructor for a Geography instance from an Open Geospatial Consortium (OGC) Well-Known Text (WKT) + * representation. Spatial Reference Identifier is defaulted to 4326. * * @param wkt * Well-Known Text (WKT) provided by the user. @@ -118,20 +126,21 @@ public static Geography parse(String wkt) throws SQLServerException { } /** - * Constructs a Geography instance that represents a Point instance from its X and Y values and an SRID. + * Constructor for a Geography instance that represents a Point instance from its latitude and longitude values and + * a Spatial Reference Identifier. * - * @param x - * x coordinate - * @param y - * y coordinate + * @param lat + * latitude + * @param lon + * longitude * @param srid - * SRID + * Spatial Reference Identifier value * @return Geography Geography instance * @throws SQLServerException * if an exception occurs */ - public static Geography point(double x, double y, int srid) throws SQLServerException { - return new Geography("POINT (" + x + " " + y + ")", srid); + public static Geography point(double lat, double lon, int srid) throws SQLServerException { + return new Geography("POINT (" + lat + " " + lon + ")", srid); } /** @@ -147,7 +156,7 @@ public String STAsText() throws SQLServerException { buffer = ByteBuffer.wrap(wkb); buffer.order(ByteOrder.LITTLE_ENDIAN); - parseWkb(); + parseWkb(this); WKTsb = new StringBuffer(); WKTsbNoZM = new StringBuffer(); @@ -165,7 +174,7 @@ public String STAsText() throws SQLServerException { */ public byte[] STAsBinary() { if (null == wkbNoZM) { - serializeToWkb(true); + serializeToWkb(true, this); } return wkbNoZM; } @@ -198,25 +207,25 @@ public boolean hasZ() { } /** - * Returns the X coordinate value. + * Returns the latitude value. * - * @return double value that represents the X coordinate. + * @return double value that represents the latitude. */ - public Double getX() { - if (null != internalType && internalType == InternalSpatialDatatype.POINT && points.length == 2) { - return points[0]; + public Double getLatitude() { + if (null != internalType && internalType == InternalSpatialDatatype.POINT && xValues.length == 1) { + return xValues[0]; } return null; } /** - * Returns the Y coordinate value. + * Returns the longitude value. * - * @return double value that represents the Y coordinate. + * @return double value that represents the longitude. */ - public Double getY() { - if (null != internalType && internalType == InternalSpatialDatatype.POINT && points.length == 2) { - return points[1]; + public Double getLongitude() { + if (null != internalType && internalType == InternalSpatialDatatype.POINT && yValues.length == 1) { + return yValues[0]; } return null; } @@ -302,111 +311,4 @@ public String asTextZM() { public String toString() { return wkt; } - - protected void serializeToWkb(boolean noZM) { - ByteBuffer buf = ByteBuffer.allocate(determineWkbCapacity()); - createSerializationProperties(); - - buf.order(ByteOrder.LITTLE_ENDIAN); - buf.putInt(srid); - buf.put(version); - buf.put(serializationProperties); - - if (!isSinglePoint && !isSingleLineSegment) { - buf.putInt(numberOfPoints); - } - - for (int i = 0; i < numberOfPoints; i++) { - buf.putDouble(points[2 * i + 1]); - buf.putDouble(points[2 * i]); - } - - if (!noZM) { - if (hasZvalues) { - for (int i = 0; i < numberOfPoints; i++) { - buf.putDouble(zValues[i]); - } - } - if (hasMvalues) { - for (int i = 0; i < numberOfPoints; i++) { - buf.putDouble(mValues[i]); - } - } - } - - if (isSinglePoint || isSingleLineSegment) { - wkb = buf.array(); - return; - } - - buf.putInt(numberOfFigures); - for (int i = 0; i < numberOfFigures; i++) { - buf.put(figures[i].getFiguresAttribute()); - buf.putInt(figures[i].getPointOffset()); - } - - buf.putInt(numberOfShapes); - for (int i = 0; i < numberOfShapes; i++) { - buf.putInt(shapes[i].getParentOffset()); - buf.putInt(shapes[i].getFigureOffset()); - buf.put(shapes[i].getOpenGISType()); - } - - if (version == 2 && null != segments) { - buf.putInt(numberOfSegments); - for (int i = 0; i < numberOfSegments; i++) { - buf.put(segments[i].getSegmentType()); - } - } - - if (noZM) { - wkbNoZM = buf.array(); - } else { - wkb = buf.array(); - - } - return; - } - - protected void parseWkb() { - srid = buffer.getInt(); - version = buffer.get(); - serializationProperties = buffer.get(); - - interpretSerializationPropBytes(); - readNumberOfPoints(); - readPoints(); - - if (hasZvalues) { - readZvalues(); - } - - if (hasMvalues) { - readMvalues(); - } - - if (!(isSinglePoint || isSingleLineSegment)) { - readNumberOfFigures(); - readFigures(); - readNumberOfShapes(); - readShapes(); - } - - determineInternalType(); - - if (buffer.hasRemaining()) { - if (version == 2 && internalType.getTypeCode() != 8 && internalType.getTypeCode() != 11) { - readNumberOfSegments(); - readSegments(); - } - } - } - - private void readPoints() { - points = new double[2 * numberOfPoints]; - for (int i = 0; i < numberOfPoints; i++) { - points[2 * i + 1] = buffer.getDouble(); - points[2 * i] = buffer.getDouble(); - } - } } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java b/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java index 45d8939f4..64d78e0e8 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java @@ -9,30 +9,33 @@ import java.nio.ByteOrder; +/** + * Geometry datatype represents data in a Euclidean (flat) coordinate system. + */ + public class Geometry extends SQLServerSpatialDatatype { /** - * Private constructor used for creating a Geometry object from WKT and srid. + * Private constructor used for creating a Geometry object from WKT and Spatial Reference Identifier. * - * @param WellKnownText + * @param wkt * Well-Known Text (WKT) provided by the user. * @param srid * Spatial Reference Identifier (SRID) provided by the user. * @throws SQLServerException * if an exception occurs */ - private Geometry(String WellKnownText, int srid) throws SQLServerException { - this.wkt = WellKnownText; + private Geometry(String wkt, int srid) throws SQLServerException { + if (null == wkt || wkt.length() <= 0) { + throwIllegalWKT(); + } + + this.wkt = wkt; this.srid = srid; - try { - parseWKTForSerialization(this, currentWktPos, -1, false); - } catch (StringIndexOutOfBoundsException e) { - String strError = SQLServerException.getErrString("R_illegalWKT"); - throw new SQLServerException(strError, null, 0, null); - } + parseWKTForSerialization(this, currentWktPos, -1, false); - serializeToWkb(false); + serializeToWkb(false, this); isNull = false; } @@ -45,11 +48,15 @@ private Geometry(String WellKnownText, int srid) throws SQLServerException { * if an exception occurs */ private Geometry(byte[] wkb) throws SQLServerException { + if (null == wkb || wkb.length <= 0) { + throwIllegalWKB(); + } + this.wkb = wkb; buffer = ByteBuffer.wrap(wkb); buffer.order(ByteOrder.LITTLE_ENDIAN); - parseWkb(); + parseWkb(this); WKTsb = new StringBuffer(); WKTsbNoZM = new StringBuffer(); @@ -62,7 +69,7 @@ private Geometry(byte[] wkb) throws SQLServerException { } /** - * Returns a Geometry instance from an Open Geospatial Consortium (OGC) Well-Known Text (WKT) representation + * Constructor for a Geometry instance from an Open Geospatial Consortium (OGC) Well-Known Text (WKT) representation * augmented with any Z (elevation) and M (measure) values carried by the instance. * * @param wkt @@ -78,7 +85,8 @@ public static Geometry STGeomFromText(String wkt, int srid) throws SQLServerExce } /** - * Returns a Geometry instance from an Open Geospatial Consortium (OGC) Well-Known Binary (WKB) representation. + * Constructor for a Geometry instance from an Open Geospatial Consortium (OGC) Well-Known Binary (WKB) + * representation. * * @param wkb * Well-Known Binary (WKB) provided by the user. @@ -91,7 +99,7 @@ public static Geometry STGeomFromWKB(byte[] wkb) throws SQLServerException { } /** - * Returns a constructed Geometry from an internal SQL Server format for spatial data. + * Constructor for a Geometry instance from an internal SQL Server format for spatial data. * * @param wkb * Well-Known Binary (WKB) provided by the user. @@ -104,8 +112,8 @@ public static Geometry deserialize(byte[] wkb) throws SQLServerException { } /** - * Returns a Geometry instance from an Open Geospatial Consortium (OGC) Well-Known Text (WKT) representation. SRID - * is defaulted to 0. + * Constructor for a Geometry instance from an Open Geospatial Consortium (OGC) Well-Known Text (WKT) + * representation. Spatial Reference Identifier is defaulted to 0. * * @param wkt * Well-Known Text (WKT) provided by the user. @@ -118,14 +126,15 @@ public static Geometry parse(String wkt) throws SQLServerException { } /** - * Constructs a Geometry instance that represents a Point instance from its X and Y values and an SRID. + * Constructor for a Geometry instance that represents a Point instance from its X and Y values and an Spatial + * Reference Identifier. * * @param x * x coordinate * @param y * y coordinate * @param srid - * SRID + * Spatial Reference Identifier value * @return Geometry Geography instance * @throws SQLServerException * if an exception occurs @@ -147,7 +156,7 @@ public String STAsText() throws SQLServerException { buffer = ByteBuffer.wrap(wkb); buffer.order(ByteOrder.LITTLE_ENDIAN); - parseWkb(); + parseWkb(this); WKTsb = new StringBuffer(); WKTsbNoZM = new StringBuffer(); @@ -165,7 +174,7 @@ public String STAsText() throws SQLServerException { */ public byte[] STAsBinary() { if (null == wkbNoZM) { - serializeToWkb(true); + serializeToWkb(true, this); } return wkbNoZM; } @@ -203,8 +212,8 @@ public boolean hasZ() { * @return double value that represents the X coordinate. */ public Double getX() { - if (null != internalType && internalType == InternalSpatialDatatype.POINT && points.length == 2) { - return points[0]; + if (null != internalType && internalType == InternalSpatialDatatype.POINT && xValues.length == 1) { + return xValues[0]; } return null; } @@ -215,8 +224,8 @@ public Double getX() { * @return double value that represents the Y coordinate. */ public Double getY() { - if (null != internalType && internalType == InternalSpatialDatatype.POINT && points.length == 2) { - return points[1]; + if (null != internalType && internalType == InternalSpatialDatatype.POINT && yValues.length == 1) { + return yValues[0]; } return null; } @@ -302,112 +311,4 @@ public String asTextZM() { public String toString() { return wkt; } - - protected void serializeToWkb(boolean noZM) { - ByteBuffer buf = ByteBuffer.allocate(determineWkbCapacity()); - createSerializationProperties(); - - buf.order(ByteOrder.LITTLE_ENDIAN); - buf.putInt(srid); - buf.put(version); - buf.put(serializationProperties); - - if (!isSinglePoint && !isSingleLineSegment) { - buf.putInt(numberOfPoints); - } - - for (int i = 0; i < numberOfPoints; i++) { - buf.putDouble(points[2 * i]); - buf.putDouble(points[2 * i + 1]); - } - - if (!noZM) { - if (hasZvalues) { - for (int i = 0; i < numberOfPoints; i++) { - buf.putDouble(zValues[i]); - } - } - - if (hasMvalues) { - for (int i = 0; i < numberOfPoints; i++) { - buf.putDouble(mValues[i]); - } - } - } - - if (isSinglePoint || isSingleLineSegment) { - wkb = buf.array(); - return; - } - - buf.putInt(numberOfFigures); - for (int i = 0; i < numberOfFigures; i++) { - buf.put(figures[i].getFiguresAttribute()); - buf.putInt(figures[i].getPointOffset()); - } - - buf.putInt(numberOfShapes); - for (int i = 0; i < numberOfShapes; i++) { - buf.putInt(shapes[i].getParentOffset()); - buf.putInt(shapes[i].getFigureOffset()); - buf.put(shapes[i].getOpenGISType()); - } - - if (version == 2 && null != segments) { - buf.putInt(numberOfSegments); - for (int i = 0; i < numberOfSegments; i++) { - buf.put(segments[i].getSegmentType()); - } - } - - if (noZM) { - wkbNoZM = buf.array(); - } else { - wkb = buf.array(); - - } - return; - } - - protected void parseWkb() { - srid = buffer.getInt(); - version = buffer.get(); - serializationProperties = buffer.get(); - - interpretSerializationPropBytes(); - readNumberOfPoints(); - readPoints(); - - if (hasZvalues) { - readZvalues(); - } - - if (hasMvalues) { - readMvalues(); - } - - if (!(isSinglePoint || isSingleLineSegment)) { - readNumberOfFigures(); - readFigures(); - readNumberOfShapes(); - readShapes(); - } - - determineInternalType(); - - if (buffer.hasRemaining()) { - if (version == 2 && internalType.getTypeCode() != 8 && internalType.getTypeCode() != 11) { - readNumberOfSegments(); - readSegments(); - } - } - } - - private void readPoints() { - points = new double[2 * numberOfPoints]; - for (int i = 0; i < numberOfPoints; i++) { - points[2 * i] = buffer.getDouble(); - points[2 * i + 1] = buffer.getDouble(); - } - } } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/InternalSpatialDatatype.java b/src/main/java/com/microsoft/sqlserver/jdbc/InternalSpatialDatatype.java index 216615447..dfbf6af68 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/InternalSpatialDatatype.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/InternalSpatialDatatype.java @@ -30,15 +30,15 @@ private InternalSpatialDatatype(byte typeCode, String typeName) { this.typeName = typeName; } - public byte getTypeCode() { + byte getTypeCode() { return this.typeCode; } - public String getTypeName() { + String getTypeName() { return this.typeName; } - public static InternalSpatialDatatype valueOf(byte typeCode) { + static InternalSpatialDatatype valueOf(byte typeCode) { for (InternalSpatialDatatype internalType : values()) { if (internalType.typeCode == typeCode) { return internalType; diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java index 56f888148..f7cf5ffb1 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java @@ -1965,13 +1965,24 @@ public int[] executeBatch() throws SQLServerException, BatchUpdateException, SQL ArrayList columnList = parseUserSQLForColumnListDW(); ArrayList valueList = parseUserSQLForValueListDW(false); - String destinationTableName = tableName; - SQLServerStatement stmt = (SQLServerStatement) connection.createStatement(ResultSet.TYPE_FORWARD_ONLY, - ResultSet.CONCUR_READ_ONLY, connection.getHoldability(), stmtColumnEncriptionSetting); - - // Get destination metadata - try (SQLServerResultSet rs = stmt.executeQueryInternal( - "sp_executesql N'SET FMTONLY ON SELECT * FROM " + destinationTableName + " '");) { + checkAdditionalQuery(); + + try (SQLServerStatement stmt = (SQLServerStatement) connection.createStatement( + ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, connection.getHoldability(), + stmtColumnEncriptionSetting); + SQLServerResultSet rs = stmt.executeQueryInternal( + "sp_executesql N'SET FMTONLY ON SELECT * FROM " + tableName + " '");) { + if (null != columnList && columnList.size() > 0) { + if (columnList.size() != valueList.size()) { + throw new IllegalArgumentException( + "Number of provided columns does not match the table definition."); + } + } else { + if (rs.getColumnCount() != valueList.size()) { + throw new IllegalArgumentException( + "Number of provided columns does not match the table definition."); + } + } SQLServerBulkBatchInsertRecord batchRecord = new SQLServerBulkBatchInsertRecord(batchParamValues, columnList, valueList, null); @@ -1981,6 +1992,7 @@ public int[] executeBatch() throws SQLServerException, BatchUpdateException, SQL CryptoMetadata cryptoMetadata = c.getCryptoMetadata(); int jdbctype; TypeInfo ti = c.getTypeInfo(); + checkValidColumns(ti); if (null != cryptoMetadata) { jdbctype = cryptoMetadata.getBaseTypeInfo().getSSType().getJDBCType().getIntValue(); } else { @@ -2003,18 +2015,16 @@ public int[] executeBatch() throws SQLServerException, BatchUpdateException, SQL batchParamValues = null; loggerExternal.exiting(getClassNameLogging(), "executeBatch", updateCounts); return updateCounts; - } finally { - if (null != stmt) - stmt.close(); } } } catch (SQLException e) { // throw a BatchUpdateException with the given error message, and return null for the updateCounts. throw new BatchUpdateException(e.getMessage(), null, 0, null); - } catch (IllegalArgumentException e) { + } + catch (IllegalArgumentException e) { // If we fail with IllegalArgumentException, fall back to the original batch insert logic. if (getStatementLogger().isLoggable(java.util.logging.Level.FINE)) { - getStatementLogger().fine("Parsing user's Batch Insert SQL Query failed: " + e.toString()); + getStatementLogger().fine("Parsing user's Batch Insert SQL Query failed: " + e.getMessage()); getStatementLogger().fine("Falling back to the original implementation for Batch Insert."); } } @@ -2109,13 +2119,24 @@ public long[] executeLargeBatch() throws SQLServerException, BatchUpdateExceptio ArrayList columnList = parseUserSQLForColumnListDW(); ArrayList valueList = parseUserSQLForValueListDW(false); - String destinationTableName = tableName; - SQLServerStatement stmt = (SQLServerStatement) connection.createStatement(ResultSet.TYPE_FORWARD_ONLY, - ResultSet.CONCUR_READ_ONLY, connection.getHoldability(), stmtColumnEncriptionSetting); - - // Get destination metadata - try (SQLServerResultSet rs = stmt.executeQueryInternal( - "sp_executesql N'SET FMTONLY ON SELECT * FROM " + destinationTableName + " '");) { + checkAdditionalQuery(); + + try (SQLServerStatement stmt = (SQLServerStatement) connection.createStatement( + ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, connection.getHoldability(), + stmtColumnEncriptionSetting); + SQLServerResultSet rs = stmt.executeQueryInternal( + "sp_executesql N'SET FMTONLY ON SELECT * FROM " + tableName + " '");) { + if (null != columnList && columnList.size() > 0) { + if (columnList.size() != valueList.size()) { + throw new IllegalArgumentException( + "Number of provided columns does not match the table definition."); + } + } else { + if (rs.getColumnCount() != valueList.size()) { + throw new IllegalArgumentException( + "Number of provided columns does not match the table definition."); + } + } SQLServerBulkBatchInsertRecord batchRecord = new SQLServerBulkBatchInsertRecord(batchParamValues, columnList, valueList, null); @@ -2125,6 +2146,7 @@ public long[] executeLargeBatch() throws SQLServerException, BatchUpdateExceptio CryptoMetadata cryptoMetadata = c.getCryptoMetadata(); int jdbctype; TypeInfo ti = c.getTypeInfo(); + checkValidColumns(ti); if (null != cryptoMetadata) { jdbctype = cryptoMetadata.getBaseTypeInfo().getSSType().getJDBCType().getIntValue(); } else { @@ -2147,18 +2169,16 @@ public long[] executeLargeBatch() throws SQLServerException, BatchUpdateExceptio batchParamValues = null; loggerExternal.exiting(getClassNameLogging(), "executeLargeBatch", updateCounts); return updateCounts; - } finally { - if (null != stmt) - stmt.close(); } } } catch (SQLException e) { // throw a BatchUpdateException with the given error message, and return null for the updateCounts. throw new BatchUpdateException(e.getMessage(), null, 0, null); - } catch (IllegalArgumentException e) { + } + catch (IllegalArgumentException e) { // If we fail with IllegalArgumentException, fall back to the original batch insert logic. if (getStatementLogger().isLoggable(java.util.logging.Level.FINE)) { - getStatementLogger().fine("Parsing user's Batch Insert SQL Query failed: " + e.toString()); + getStatementLogger().fine("Parsing user's Batch Insert SQL Query failed: " + e.getMessage()); getStatementLogger().fine("Falling back to the original implementation for Batch Insert."); } } @@ -2206,17 +2226,71 @@ public long[] executeLargeBatch() throws SQLServerException, BatchUpdateExceptio return updateCounts; } + private void checkValidColumns(TypeInfo ti) throws SQLServerException { + int jdbctype = ti.getSSType().getJDBCType().getIntValue(); + // currently, we do not support: geometry, geography, datetime and smalldatetime + switch (jdbctype) { + case java.sql.Types.INTEGER: + case java.sql.Types.SMALLINT: + case java.sql.Types.BIGINT: + case java.sql.Types.BIT: + case java.sql.Types.TINYINT: + case java.sql.Types.DOUBLE: + case java.sql.Types.REAL: + case microsoft.sql.Types.MONEY: + case microsoft.sql.Types.SMALLMONEY: + case java.sql.Types.DECIMAL: + case java.sql.Types.NUMERIC: + case microsoft.sql.Types.GUID: + case java.sql.Types.CHAR: + case java.sql.Types.NCHAR: + case java.sql.Types.LONGVARCHAR: + case java.sql.Types.VARCHAR: + case java.sql.Types.LONGNVARCHAR: + case java.sql.Types.NVARCHAR: + case java.sql.Types.BINARY: + case java.sql.Types.LONGVARBINARY: + case java.sql.Types.VARBINARY: + // Spatial datatypes fall under Varbinary, check if the UDT is geometry/geography. + String typeName = ti.getSSTypeName(); + if (typeName.equalsIgnoreCase("geometry") || typeName.equalsIgnoreCase("geography")) { + MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_BulkTypeNotSupported")); + throw new IllegalArgumentException(form.format(new Object[] {typeName})); + } + case java.sql.Types.TIMESTAMP: + case java.sql.Types.DATE: + case java.sql.Types.TIME: + case 2013: // java.sql.Types.TIME_WITH_TIMEZONE + case 2014: // java.sql.Types.TIMESTAMP_WITH_TIMEZONE + case microsoft.sql.Types.DATETIMEOFFSET: + case microsoft.sql.Types.SQL_VARIANT: + return; + default: { + MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_BulkTypeNotSupported")); + String unsupportedDataType = JDBCType.of(jdbctype).toString(); + throw new IllegalArgumentException(form.format(new Object[] {unsupportedDataType})); + } + } + } + + private void checkAdditionalQuery() { + while (checkAndRemoveCommentsAndSpace(true)) {} + + // At this point, if localUserSQL is not empty (after removing all whitespaces, semicolons and comments), we + // have a + // new query. reject this. + if (localUserSQL.length() > 0) { + throw new IllegalArgumentException("Multiple queries are not allowed."); + } + } + private String parseUserSQLForTableNameDW(boolean hasInsertBeenFound, boolean hasIntoBeenFound, boolean hasTableBeenFound, boolean isExpectingTableName) { // As far as finding the table name goes, There are two cases: // Insert into and Insert // And there could be in-line comments (with /* and */) in between. // This method assumes the localUserSQL string starts with "insert". - localUserSQL = localUserSQL.trim(); - if (checkAndRemoveComments()) { - return parseUserSQLForTableNameDW(hasInsertBeenFound, hasIntoBeenFound, hasTableBeenFound, - isExpectingTableName); - } + while (checkAndRemoveCommentsAndSpace(false)) {} StringBuilder sb = new StringBuilder(); @@ -2225,7 +2299,7 @@ private String parseUserSQLForTableNameDW(boolean hasInsertBeenFound, boolean ha // This if statement is needed to handle the case where the user has something like: // [dbo] . /* random comment */ [tableName] if (hasTableBeenFound && !isExpectingTableName) { - if (localUserSQL.substring(0, 1).equalsIgnoreCase(".")) { + if (checkSQLLength(1) && localUserSQL.substring(0, 1).equalsIgnoreCase(".")) { sb.append("."); localUserSQL = localUserSQL.substring(1); return sb.toString() + parseUserSQLForTableNameDW(true, true, true, true); @@ -2234,12 +2308,12 @@ private String parseUserSQLForTableNameDW(boolean hasInsertBeenFound, boolean ha } } - if (localUserSQL.substring(0, 6).equalsIgnoreCase("insert") && !hasInsertBeenFound) { + if (!hasInsertBeenFound && checkSQLLength(6) && localUserSQL.substring(0, 6).equalsIgnoreCase("insert")) { localUserSQL = localUserSQL.substring(6); return parseUserSQLForTableNameDW(true, hasIntoBeenFound, hasTableBeenFound, isExpectingTableName); } - if (localUserSQL.substring(0, 4).equalsIgnoreCase("into") && !hasIntoBeenFound) { + if (!hasIntoBeenFound && checkSQLLength(6) && localUserSQL.substring(0, 4).equalsIgnoreCase("into")) { // is it really "into"? // if the "into" is followed by a blank space or /*, then yes. if (Character.isWhitespace(localUserSQL.charAt(4)) @@ -2258,11 +2332,16 @@ private String parseUserSQLForTableNameDW(boolean hasInsertBeenFound, boolean ha // It could be encapsulated in [], "", or have a database name preceding the table name. // If it's encapsulated in [] or "", we need be more careful with parsing as anything could go into []/"". // For ] or ", they can be escaped by ]] or "", watch out for this too. - if (localUserSQL.substring(0, 1).equalsIgnoreCase("[")) { + if (checkSQLLength(1) && localUserSQL.substring(0, 1).equalsIgnoreCase("[")) { int tempint = localUserSQL.indexOf("]", 1); + // ] has not been found, this is wrong. + if (tempint < 0) { + throw new IllegalArgumentException("Invalid SQL Query."); + } + // keep checking if it's escaped - while (localUserSQL.charAt(tempint + 1) == ']') { + while (tempint >= 0 && checkSQLLength(tempint + 2) && localUserSQL.charAt(tempint + 1) == ']') { tempint = localUserSQL.indexOf("]", tempint + 2); } @@ -2274,11 +2353,16 @@ private String parseUserSQLForTableNameDW(boolean hasInsertBeenFound, boolean ha } // do the same for "" - if (localUserSQL.substring(0, 1).equalsIgnoreCase("\"")) { + if (checkSQLLength(1) && localUserSQL.substring(0, 1).equalsIgnoreCase("\"")) { int tempint = localUserSQL.indexOf("\"", 1); + // \" has not been found, this is wrong. + if (tempint < 0) { + throw new IllegalArgumentException("Invalid SQL Query."); + } + // keep checking if it's escaped - while (localUserSQL.charAt(tempint + 1) == '\"') { + while (tempint >= 0 && checkSQLLength(tempint + 2) && localUserSQL.charAt(tempint + 1) == '\"') { tempint = localUserSQL.indexOf("\"", tempint + 2); } @@ -2291,11 +2375,13 @@ private String parseUserSQLForTableNameDW(boolean hasInsertBeenFound, boolean ha // At this point, the next chunk of string is the table name, without starting with [ or ". while (localUserSQL.length() > 0) { - // Keep going until the end of the table name is signalled - either a ., whitespace, or comment is - // encountered + // Keep going until the end of the table name is signalled - either a ., whitespace, ; or comment is + // encountered. if (localUserSQL.charAt(0) == '.' || Character.isWhitespace(localUserSQL.charAt(0)) - || checkAndRemoveComments()) { + || checkAndRemoveCommentsAndSpace(false)) { return sb.toString() + parseUserSQLForTableNameDW(true, true, true, false); + } else if (localUserSQL.charAt(0) == ';') { + throw new IllegalArgumentException("End of query detected before VALUES have been found."); } else { sb.append(localUserSQL.charAt(0)); localUserSQL = localUserSQL.substring(1); @@ -2303,20 +2389,16 @@ private String parseUserSQLForTableNameDW(boolean hasInsertBeenFound, boolean ha } // It shouldn't come here. If we did, something is wrong. - throw new IllegalArgumentException("localUserSQL"); + throw new IllegalArgumentException("Invalid SQL Query."); } private ArrayList parseUserSQLForColumnListDW() { - localUserSQL = localUserSQL.trim(); - // ignore all comments - if (checkAndRemoveComments()) { - return parseUserSQLForColumnListDW(); - } + while (checkAndRemoveCommentsAndSpace(false)) {} // check if optional column list was provided // Columns can have the form of c1, [c1] or "c1". It can escape ] or " by ]] or "". - if (localUserSQL.substring(0, 1).equalsIgnoreCase("(")) { + if (checkSQLLength(1) && localUserSQL.substring(0, 1).equalsIgnoreCase("(")) { localUserSQL = localUserSQL.substring(1); return parseUserSQLForColumnListDWHelper(new ArrayList()); } @@ -2324,200 +2406,229 @@ private ArrayList parseUserSQLForColumnListDW() { } private ArrayList parseUserSQLForColumnListDWHelper(ArrayList listOfColumns) { - localUserSQL = localUserSQL.trim(); - // ignore all comments - if (checkAndRemoveComments()) { - return parseUserSQLForColumnListDWHelper(listOfColumns); - } - - if (localUserSQL.charAt(0) == ')') { - localUserSQL = localUserSQL.substring(1); - return listOfColumns; - } + while (checkAndRemoveCommentsAndSpace(false)) {} - if (localUserSQL.charAt(0) == ',') { - localUserSQL = localUserSQL.substring(1); - return parseUserSQLForColumnListDWHelper(listOfColumns); - } + StringBuilder sb = new StringBuilder(); + while (localUserSQL.length() > 0) { + while (checkAndRemoveCommentsAndSpace(false)) {} - if (localUserSQL.charAt(0) == '[') { - int tempint = localUserSQL.indexOf("]", 1); + // exit condition + if (checkSQLLength(1) && localUserSQL.charAt(0) == ')') { + localUserSQL = localUserSQL.substring(1); + return listOfColumns; + } - // keep checking if it's escaped - while (localUserSQL.charAt(tempint + 1) == ']') { - localUserSQL = localUserSQL.substring(0, tempint) + localUserSQL.substring(tempint + 1); - tempint = localUserSQL.indexOf("]", tempint + 1); + // ignore , + // we've confirmed length is more than 0. + if (localUserSQL.charAt(0) == ',') { + localUserSQL = localUserSQL.substring(1); + while (checkAndRemoveCommentsAndSpace(false)) {} } - // we've found a ] that is actually trying to close the square bracket. - String tempstr = localUserSQL.substring(1, tempint); - localUserSQL = localUserSQL.substring(tempint + 1); - listOfColumns.add(tempstr); - return parseUserSQLForColumnListDWHelper(listOfColumns); - } + // handle [] case + if (localUserSQL.charAt(0) == '[') { + int tempint = localUserSQL.indexOf("]", 1); - if (localUserSQL.charAt(0) == '\"') { - int tempint = localUserSQL.indexOf("\"", 1); + // ] has not been found, this is wrong. + if (tempint < 0) { + throw new IllegalArgumentException("Invalid SQL Query."); + } - // keep checking if it's escaped - while (localUserSQL.charAt(tempint + 1) == '\"') { - localUserSQL = localUserSQL.substring(0, tempint) + localUserSQL.substring(tempint + 1); - tempint = localUserSQL.indexOf("\"", tempint + 1); + // keep checking if it's escaped + while (tempint >= 0 && checkSQLLength(tempint + 2) && localUserSQL.charAt(tempint + 1) == ']') { + localUserSQL = localUserSQL.substring(0, tempint) + localUserSQL.substring(tempint + 1); + tempint = localUserSQL.indexOf("]", tempint + 1); + } + + // we've found a ] that is actually trying to close the square bracket. + String tempstr = localUserSQL.substring(1, tempint); + localUserSQL = localUserSQL.substring(tempint + 1); + listOfColumns.add(tempstr); + continue; // proceed with the rest of the string } - // we've found a " that is actually trying to close the quote. - String tempstr = localUserSQL.substring(1, tempint); - localUserSQL = localUserSQL.substring(tempint + 1); - listOfColumns.add(tempstr); - return parseUserSQLForColumnListDWHelper(listOfColumns); - } + // handle "" case + if (localUserSQL.charAt(0) == '\"') { + int tempint = localUserSQL.indexOf("\"", 1); - // At this point, the next chunk of string is the column name, without starting with [ or ". - StringBuilder sb = new StringBuilder(); - while (localUserSQL.length() > 0) { - if (localUserSQL.charAt(0) == ',') { - localUserSQL = localUserSQL.substring(1); - listOfColumns.add(sb.toString()); - return parseUserSQLForColumnListDWHelper(listOfColumns); - } else if (localUserSQL.charAt(0) == ')') { - localUserSQL = localUserSQL.substring(1); - listOfColumns.add(sb.toString()); - return listOfColumns; - } else if (checkAndRemoveComments()) { - localUserSQL = localUserSQL.trim(); - } else { - sb.append(localUserSQL.charAt(0)); - localUserSQL = localUserSQL.substring(1); - localUserSQL = localUserSQL.trim(); + // \" has not been found, this is wrong. + if (tempint < 0) { + throw new IllegalArgumentException("Invalid SQL Query."); + } + + // keep checking if it's escaped + while (tempint >= 0 && checkSQLLength(tempint + 2) && localUserSQL.charAt(tempint + 1) == '\"') { + localUserSQL = localUserSQL.substring(0, tempint) + localUserSQL.substring(tempint + 1); + tempint = localUserSQL.indexOf("\"", tempint + 1); + } + + // we've found a " that is actually trying to close the quote. + String tempstr = localUserSQL.substring(1, tempint); + localUserSQL = localUserSQL.substring(tempint + 1); + listOfColumns.add(tempstr); + continue; // proceed with the rest of the string + } + + // At this point, the next chunk of string is the column name, without starting with [ or ". + while (localUserSQL.length() > 0) { + if (checkAndRemoveCommentsAndSpace(false)) { + continue; + } + if (localUserSQL.charAt(0) == ',') { + localUserSQL = localUserSQL.substring(1); + listOfColumns.add(sb.toString()); + sb.setLength(0); + break; // exit this while loop, but continue parsing. + } else if (localUserSQL.charAt(0) == ')') { + localUserSQL = localUserSQL.substring(1); + listOfColumns.add(sb.toString()); + return listOfColumns; // reached exit condition. + } else { + sb.append(localUserSQL.charAt(0)); + localUserSQL = localUserSQL.substring(1); + localUserSQL = localUserSQL.trim(); // add an entry. + } } } // It shouldn't come here. If we did, something is wrong. - throw new IllegalArgumentException("localUserSQL"); + // most likely we couldn't hit the exit condition and just parsed until the end of the string. + throw new IllegalArgumentException("Invalid SQL Query."); } private ArrayList parseUserSQLForValueListDW(boolean hasValuesBeenFound) { - localUserSQL = localUserSQL.trim(); - // ignore all comments - if (checkAndRemoveComments()) { - return parseUserSQLForValueListDW(hasValuesBeenFound); - } + if (checkAndRemoveCommentsAndSpace(false)) {} if (!hasValuesBeenFound) { // look for keyword "VALUES" - if (localUserSQL.substring(0, 6).equalsIgnoreCase("VALUES")) { + if (checkSQLLength(6) && localUserSQL.substring(0, 6).equalsIgnoreCase("VALUES")) { localUserSQL = localUserSQL.substring(6); - localUserSQL = localUserSQL.trim(); - // ignore all comments - if (checkAndRemoveComments()) { - return parseUserSQLForValueListDW(true); - } + while (checkAndRemoveCommentsAndSpace(false)) {} - if (localUserSQL.substring(0, 1).equalsIgnoreCase("(")) { + if (checkSQLLength(1) && localUserSQL.substring(0, 1).equalsIgnoreCase("(")) { localUserSQL = localUserSQL.substring(1); return parseUserSQLForValueListDWHelper(new ArrayList()); } } } else { // ignore all comments - if (checkAndRemoveComments()) { - return parseUserSQLForValueListDW(hasValuesBeenFound); - } + while (checkAndRemoveCommentsAndSpace(false)) {} - if (localUserSQL.substring(0, 1).equalsIgnoreCase("(")) { + if (checkSQLLength(1) && localUserSQL.substring(0, 1).equalsIgnoreCase("(")) { localUserSQL = localUserSQL.substring(1); return parseUserSQLForValueListDWHelper(new ArrayList()); } } // shouldn't come here, as the list of values is mandatory. - throw new IllegalArgumentException("localUserSQL"); + throw new IllegalArgumentException("Invalid SQL Query."); } private ArrayList parseUserSQLForValueListDWHelper(ArrayList listOfValues) { - localUserSQL = localUserSQL.trim(); - // ignore all comments - if (checkAndRemoveComments()) { - return parseUserSQLForValueListDWHelper(listOfValues); - } - - if (localUserSQL.charAt(0) == ')') { - localUserSQL = localUserSQL.substring(1); - return listOfValues; - } - - if (localUserSQL.charAt(0) == ',') { - localUserSQL = localUserSQL.substring(1); - return parseUserSQLForValueListDWHelper(listOfValues); - } - - if (localUserSQL.charAt(0) == '\'') { - int tempint = localUserSQL.indexOf("\'", 1); - - // keep checking if it's escaped - while (localUserSQL.charAt(tempint + 1) == '\'') { - localUserSQL = localUserSQL.substring(0, tempint) + localUserSQL.substring(tempint + 1); - tempint = localUserSQL.indexOf("\'", tempint + 1); - } - - // we've found a ' that is actually trying to close the quote. - // Include 's around the string as well, so we can distinguish '?' and ? later on. - String tempstr = localUserSQL.substring(0, tempint + 1); - localUserSQL = localUserSQL.substring(tempint + 1); - listOfValues.add(tempstr); - return parseUserSQLForValueListDWHelper(listOfValues); - } + while (checkAndRemoveCommentsAndSpace(false)) {} // At this point, the next chunk of string is the value, without starting with ' (most likely a ?). StringBuilder sb = new StringBuilder(); while (localUserSQL.length() > 0) { + if (checkAndRemoveCommentsAndSpace(false)) { + continue; + } if (localUserSQL.charAt(0) == ',' || localUserSQL.charAt(0) == ')') { if (localUserSQL.charAt(0) == ',') { localUserSQL = localUserSQL.substring(1); + if (!sb.toString().equals("?")) { + // throw IllegalArgumentException and fallback to original logic for batch insert + throw new IllegalArgumentException( + "Only fully parameterized queries are allowed for using Bulk Copy API for batch insert at the moment."); + } listOfValues.add(sb.toString()); - return parseUserSQLForValueListDWHelper(listOfValues); + sb.setLength(0); } else { localUserSQL = localUserSQL.substring(1); listOfValues.add(sb.toString()); - return listOfValues; + return listOfValues; // reached exit condition. } - } else if (checkAndRemoveComments()) { - localUserSQL = localUserSQL.trim(); } else { sb.append(localUserSQL.charAt(0)); localUserSQL = localUserSQL.substring(1); - localUserSQL = localUserSQL.trim(); + localUserSQL = localUserSQL.trim(); // add entry. } } + // Don't need this anymore since we removed support for non-parameterized query. + // if (localUserSQL.charAt(0) == '\'') { + // int tempint = localUserSQL.indexOf("\'", 1); + // + // // \' has not been found, this is wrong. + // if (tempint < 0) { + // throw new IllegalArgumentException("Invalid SQL Query."); + // } + // + // // keep checking if it's escaped + // while (tempint >= 0 && checkSQLLength(tempint + 2) && localUserSQL.charAt(tempint + 1) == '\'') { + // localUserSQL = localUserSQL.substring(0, tempint) + localUserSQL.substring(tempint + 1); + // tempint = localUserSQL.indexOf("\'", tempint + 1); + // } + // + // // we've found a ' that is actually trying to close the quote. + // // Include 's around the string as well, so we can distinguish '?' and ? later on. + // String tempstr = localUserSQL.substring(0, tempint + 1); + // localUserSQL = localUserSQL.substring(tempint + 1); + // listOfValues.add(tempstr); + // return parseUserSQLForValueListDWHelper(listOfValues); + // } + // It shouldn't come here. If we did, something is wrong. - throw new IllegalArgumentException("localUserSQL"); + throw new IllegalArgumentException("Invalid SQL Query."); } - private boolean checkAndRemoveComments() { + private boolean checkAndRemoveCommentsAndSpace(boolean checkForSemicolon) { + localUserSQL = localUserSQL.trim(); + + while (checkForSemicolon && null != localUserSQL && localUserSQL.length() > 0 + && localUserSQL.charAt(0) == ';') { + localUserSQL = localUserSQL.substring(1); + } + if (null == localUserSQL || localUserSQL.length() < 2) { return false; } if (localUserSQL.substring(0, 2).equalsIgnoreCase("/*")) { int temp = localUserSQL.indexOf("*/") + 2; + if (temp <= 0) { + localUserSQL = ""; + return false; + } localUserSQL = localUserSQL.substring(temp); return true; } if (localUserSQL.substring(0, 2).equalsIgnoreCase("--")) { - int temp = localUserSQL.indexOf("\n") + 2; + int temp = localUserSQL.indexOf("\n") + 1; + if (temp <= 0) { + localUserSQL = ""; + return false; + } localUserSQL = localUserSQL.substring(temp); return true; } + return false; } + private boolean checkSQLLength(int length) { + if (null == localUserSQL || localUserSQL.length() < length) { + throw new IllegalArgumentException("Invalid SQL Query."); + } + return true; + } + private final class PrepStmtBatchExecCmd extends TDSCommand { private final SQLServerPreparedStatement stmt; SQLServerException batchException; diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerSpatialDatatype.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerSpatialDatatype.java index ca2a28793..4d18fcc28 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerSpatialDatatype.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerSpatialDatatype.java @@ -7,12 +7,17 @@ import java.math.BigDecimal; import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.text.MessageFormat; import java.util.ArrayList; import java.util.List; import java.util.Locale; +/** + * Abstract parent class for Spatial Datatypes that contains common functionalities. + */ + abstract class SQLServerSpatialDatatype { /** WKT = Well-Known-Text, WKB = Well-Knwon-Binary */ @@ -38,7 +43,8 @@ abstract class SQLServerSpatialDatatype { protected int currentFigureIndex = 0; protected int currentSegmentIndex = 0; protected int currentShapeIndex = 0; - protected double points[]; + protected double xValues[]; + protected double yValues[]; protected double zValues[]; protected double mValues[]; protected Figure figures[]; @@ -91,14 +97,115 @@ abstract class SQLServerSpatialDatatype { * @param noZM * flag to indicate if Z and M coordinates should be included */ - protected abstract void serializeToWkb(boolean noZM); + protected void serializeToWkb(boolean noZM, SQLServerSpatialDatatype type) { + ByteBuffer buf = ByteBuffer.allocate(determineWkbCapacity()); + createSerializationProperties(); + + buf.order(ByteOrder.LITTLE_ENDIAN); + buf.putInt(srid); + buf.put(version); + buf.put(serializationProperties); + + if (!isSinglePoint && !isSingleLineSegment) { + buf.putInt(numberOfPoints); + } + + if (type instanceof Geometry) { + for (int i = 0; i < numberOfPoints; i++) { + buf.putDouble(xValues[i]); + buf.putDouble(yValues[i]); + } + } else { // Geography + for (int i = 0; i < numberOfPoints; i++) { + buf.putDouble(yValues[i]); + buf.putDouble(xValues[i]); + } + } + + if (!noZM) { + if (hasZvalues) { + for (int i = 0; i < numberOfPoints; i++) { + buf.putDouble(zValues[i]); + } + } + + if (hasMvalues) { + for (int i = 0; i < numberOfPoints; i++) { + buf.putDouble(mValues[i]); + } + } + } + + if (isSinglePoint || isSingleLineSegment) { + wkb = buf.array(); + return; + } + + buf.putInt(numberOfFigures); + for (int i = 0; i < numberOfFigures; i++) { + buf.put(figures[i].getFiguresAttribute()); + buf.putInt(figures[i].getPointOffset()); + } + + buf.putInt(numberOfShapes); + for (int i = 0; i < numberOfShapes; i++) { + buf.putInt(shapes[i].getParentOffset()); + buf.putInt(shapes[i].getFigureOffset()); + buf.put(shapes[i].getOpenGISType()); + } + + if (version == 2 && null != segments) { + buf.putInt(numberOfSegments); + for (int i = 0; i < numberOfSegments; i++) { + buf.put(segments[i].getSegmentType()); + } + } + + if (noZM) { + wkbNoZM = buf.array(); + } else { + wkb = buf.array(); + } + } /** * Deserializes the buffer (that contains WKB representation of Geometry/Geography data), and stores it into * multiple corresponding data structures. * */ - protected abstract void parseWkb(); + protected void parseWkb(SQLServerSpatialDatatype type) throws SQLServerException { + srid = readInt(); + version = readByte(); + serializationProperties = readByte(); + + interpretSerializationPropBytes(); + readNumberOfPoints(); + readPoints(type); + + if (hasZvalues) { + readZvalues(); + } + + if (hasMvalues) { + readMvalues(); + } + + if (!(isSinglePoint || isSingleLineSegment)) { + readNumberOfFigures(); + readFigures(); + readNumberOfShapes(); + readShapes(); + } + + determineInternalType(); + + if (buffer.hasRemaining()) { + if (version == 2 && internalType.getTypeCode() != 8 && internalType.getTypeCode() != 11) { + readNumberOfSegments(); + readSegments(); + } + } + } /** * Constructs the WKT representation of Geometry/Geography from the deserialized data. @@ -120,7 +227,7 @@ abstract class SQLServerSpatialDatatype { */ protected void constructWKT(SQLServerSpatialDatatype sd, InternalSpatialDatatype isd, int pointIndexEnd, int figureIndexEnd, int segmentIndexEnd, int shapeIndexEnd) throws SQLServerException { - if (null == points || numberOfPoints == 0) { + if (numberOfPoints == 0) { if (isd.getTypeCode() == 11) { // FULLGLOBE if (sd instanceof Geometry) { MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_illegalTypeForGeometry")); @@ -174,8 +281,7 @@ protected void constructWKT(SQLServerSpatialDatatype sd, InternalSpatialDatatype constructCurvepolygonWKT(currentFigureIndex, figureIndexEnd, currentSegmentIndex, segmentIndexEnd); break; default: - MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_illegalWKTposition")); - throw new SQLServerException(form.format(new Object[] {currentWktPos}), null, 0, null); + throwIllegalWKTPosition(); } appendToWKTBuffers(")"); @@ -217,8 +323,7 @@ protected void parseWKTForSerialization(SQLServerSpatialDatatype sd, int startPo try { isd = InternalSpatialDatatype.valueOf(nextToken); } catch (Exception e) { - MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_illegalWKTposition")); - throw new SQLServerException(form.format(new Object[] {currentWktPos}), null, 0, null); + throwIllegalWKTPosition(); } byte fa = 0; @@ -235,8 +340,7 @@ protected void parseWKTForSerialization(SQLServerSpatialDatatype sd, int startPo } if (startPos != 0) { - MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_illegalWKTposition")); - throw new SQLServerException(form.format(new Object[] {currentWktPos}), null, 0, null); + throwIllegalWKTPosition(); } shapeList.add(new Shape(parentShapeIndex, -1, isd.getTypeCode())); @@ -256,6 +360,7 @@ protected void parseWKTForSerialization(SQLServerSpatialDatatype sd, int startPo case "POINT": if (startPos == 0 && nextToken.toUpperCase().equals("POINT")) { isSinglePoint = true; + internalType = InternalSpatialDatatype.POINT; } if (isGeoCollection) { @@ -315,8 +420,7 @@ protected void parseWKTForSerialization(SQLServerSpatialDatatype sd, int startPo break; default: - MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_illegalWKTposition")); - throw new SQLServerException(form.format(new Object[] {currentWktPos}), null, 0, null); + throwIllegalWKTPosition(); } readCloseBracket(); } @@ -333,41 +437,39 @@ protected void parseWKTForSerialization(SQLServerSpatialDatatype sd, int startPo * */ protected void constructPointWKT(int pointIndex) { - int firstPointIndex = pointIndex * 2; - int secondPointIndex = firstPointIndex + 1; - int zValueIndex = pointIndex; - int mValueIndex = pointIndex; - - if (points[firstPointIndex] % 1 == 0) { - appendToWKTBuffers((int) points[firstPointIndex]); + if (xValues[pointIndex] % 1 == 0) { + appendToWKTBuffers((int) xValues[pointIndex]); } else { - appendToWKTBuffers(points[firstPointIndex]); + appendToWKTBuffers(xValues[pointIndex]); } appendToWKTBuffers(" "); - if (points[secondPointIndex] % 1 == 0) { - appendToWKTBuffers((int) points[secondPointIndex]); + if (yValues[pointIndex] % 1 == 0) { + appendToWKTBuffers((int) yValues[pointIndex]); } else { - appendToWKTBuffers(points[secondPointIndex]); + appendToWKTBuffers(yValues[pointIndex]); } appendToWKTBuffers(" "); - if (hasZvalues && !Double.isNaN(zValues[zValueIndex]) && !(zValues[zValueIndex] == 0)) { - if (zValues[zValueIndex] % 1 == 0) { - WKTsb.append((int) zValues[zValueIndex]); + if (hasZvalues && !Double.isNaN(zValues[pointIndex])) { + if (zValues[pointIndex] % 1 == 0) { + WKTsb.append((long) zValues[pointIndex]); } else { - WKTsb.append(zValues[zValueIndex]); + WKTsb.append(zValues[pointIndex]); } WKTsb.append(" "); + } else if (hasMvalues && !Double.isNaN(mValues[pointIndex])) { + // Handle the case where the user has POINT (1 2 NULL M) value. + WKTsb.append("NULL "); + } - if (hasMvalues && !Double.isNaN(mValues[mValueIndex]) && !(mValues[mValueIndex] <= 0)) { - if (mValues[mValueIndex] % 1 == 0) { - WKTsb.append((int) mValues[mValueIndex]); - } else { - WKTsb.append(mValues[mValueIndex]); - } - WKTsb.append(" "); + if (hasMvalues && !Double.isNaN(mValues[pointIndex])) { + if (mValues[pointIndex] % 1 == 0) { + WKTsb.append((long) mValues[pointIndex]); + } else { + WKTsb.append(mValues[pointIndex]); } + WKTsb.append(" "); } currentPointIndex++; @@ -738,6 +840,9 @@ protected void readPointWkt() throws SQLServerException { int numOfCoordinates = 0; double sign; double coords[] = new double[4]; + for (int i = 0; i < coords.length; i++) { + coords[i] = Double.NaN; + } while (numOfCoordinates < 4) { sign = 1; @@ -760,9 +865,22 @@ protected void readPointWkt() throws SQLServerException { try { coords[numOfCoordinates] = sign * new BigDecimal(wkt.substring(startPos, currentWktPos)).doubleValue(); + + if (numOfCoordinates == 2) { + hasZvalues = true; + } else if (numOfCoordinates == 3) { + hasMvalues = true; + } } catch (Exception e) { // modify to conversion exception - MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_illegalWKTposition")); - throw new SQLServerException(form.format(new Object[] {currentWktPos}), null, 0, null); + // handle NULL case + // the first check ensures that there is enough space for the wkt to have NULL + if (wkt.length() > currentWktPos + 3 + && wkt.substring(currentWktPos, currentWktPos + 4).equalsIgnoreCase("null")) { + coords[numOfCoordinates] = Double.NaN; + currentWktPos = currentWktPos + 4; + } else { + throwIllegalWKTPosition(); + } } numOfCoordinates++; @@ -772,17 +890,16 @@ protected void readPointWkt() throws SQLServerException { // After skipping white space after the 4th coordinate has been read, the next // character has to be either a , or ), or the WKT is invalid. if (numOfCoordinates == 4) { - if (wkt.charAt(currentWktPos) != ',' && wkt.charAt(currentWktPos) != ')') { - MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_illegalWKTposition")); - throw new SQLServerException(form.format(new Object[] {currentWktPos}), null, 0, null); + if (checkSQLLength(currentWktPos + 1) && wkt.charAt(currentWktPos) != ',' + && wkt.charAt(currentWktPos) != ')') { + throwIllegalWKTPosition(); } } - if (wkt.charAt(currentWktPos) == ',') { + if (checkSQLLength(currentWktPos + 1) && wkt.charAt(currentWktPos) == ',') { // need at least 2 coordinates if (numOfCoordinates == 1) { - MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_illegalWKTposition")); - throw new SQLServerException(form.format(new Object[] {currentWktPos}), null, 0, null); + throwIllegalWKTPosition(); } currentWktPos++; skipWhiteSpaces(); @@ -791,13 +908,6 @@ protected void readPointWkt() throws SQLServerException { skipWhiteSpaces(); } - if (numOfCoordinates == 4) { - hasZvalues = true; - hasMvalues = true; - } else if (numOfCoordinates == 3) { - hasZvalues = true; - } - pointList.add(new Point(coords[0], coords[1], coords[2], coords[3])); } @@ -863,13 +973,12 @@ && checkEmptyKeyword(parentShapeIndex, InternalSpatialDatatype.valueOf(nextToken skipWhiteSpaces(); - if (wkt.charAt(currentWktPos) == ',') { // more rings to follow + if (checkSQLLength(currentWktPos + 1) && wkt.charAt(currentWktPos) == ',') { // more rings to follow readComma(); } else if (wkt.charAt(currentWktPos) == ')') { // about to exit while loop continue; } else { // unexpected input - MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_illegalWKTposition")); - throw new SQLServerException(form.format(new Object[] {currentWktPos}), null, 0, null); + throwIllegalWKTPosition(); } } } @@ -899,17 +1008,15 @@ protected void readCurvePolygon() throws SQLServerException { readLineWkt(); readCloseBracket(); } else { - MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_illegalWKTposition")); - throw new SQLServerException(form.format(new Object[] {currentWktPos}), null, 0, null); + throwIllegalWKTPosition(); } - if (wkt.charAt(currentWktPos) == ',') { // more polygons to follow + if (checkSQLLength(currentWktPos + 1) && wkt.charAt(currentWktPos) == ',') { // more polygons to follow readComma(); } else if (wkt.charAt(currentWktPos) == ')') { // about to exit while loop continue; } else { // unexpected input - MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_illegalWKTposition")); - throw new SQLServerException(form.format(new Object[] {currentWktPos}), null, 0, null); + throwIllegalWKTPosition(); } } } @@ -935,13 +1042,12 @@ protected void readMultiPolygonWkt(int thisShapeIndex, String nextToken) throws readShapeWkt(thisShapeIndex, nextToken); readCloseBracket(); - if (wkt.charAt(currentWktPos) == ',') { // more polygons to follow + if (checkSQLLength(currentWktPos + 1) && wkt.charAt(currentWktPos) == ',') { // more polygons to follow readComma(); } else if (wkt.charAt(currentWktPos) == ')') { // about to exit while loop continue; } else { // unexpected input - MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_illegalWKTposition")); - throw new SQLServerException(form.format(new Object[] {currentWktPos}), null, 0, null); + throwIllegalWKTPosition(); } } } @@ -1006,19 +1112,17 @@ protected void readCompoundCurveWkt(boolean isFirstIteration) throws SQLServerEx readSegmentWkt(SEGMENT_FIRST_LINE, isFirstIteration); readCloseBracket(); } else { - MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_illegalWKTposition")); - throw new SQLServerException(form.format(new Object[] {currentWktPos}), null, 0, null); + throwIllegalWKTPosition(); } isFirstIteration = false; - if (wkt.charAt(currentWktPos) == ',') { // more polygons to follow + if (checkSQLLength(currentWktPos + 1) && wkt.charAt(currentWktPos) == ',') { // more polygons to follow readComma(); } else if (wkt.charAt(currentWktPos) == ')') { // about to exit while loop continue; } else { // unexpected input - MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_illegalWKTposition")); - throw new SQLServerException(form.format(new Object[] {currentWktPos}), null, 0, null); + throwIllegalWKTPosition(); } } } @@ -1047,11 +1151,12 @@ protected String getNextStringToken() { */ protected void populateStructures() { if (pointList.size() > 0) { - points = new double[pointList.size() * 2]; + xValues = new double[pointList.size()]; + yValues = new double[pointList.size()]; for (int i = 0; i < pointList.size(); i++) { - points[i * 2] = pointList.get(i).getX(); - points[i * 2 + 1] = pointList.get(i).getY(); + xValues[i] = pointList.get(i).getX(); + yValues[i] = pointList.get(i).getY(); } if (hasZvalues) { @@ -1123,8 +1228,7 @@ protected void readOpenBracket() throws SQLServerException { currentWktPos++; skipWhiteSpaces(); } else { - MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_illegalWKTposition")); - throw new SQLServerException(form.format(new Object[] {currentWktPos}), null, 0, null); + throwIllegalWKTPosition(); } } @@ -1134,8 +1238,7 @@ protected void readCloseBracket() throws SQLServerException { currentWktPos++; skipWhiteSpaces(); } else { - MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_illegalWKTposition")); - throw new SQLServerException(form.format(new Object[] {currentWktPos}), null, 0, null); + throwIllegalWKTPosition(); } } @@ -1234,71 +1337,75 @@ protected void interpretSerializationPropBytes() { isLargerThanHemisphere = (serializationProperties & isLargerThanHemisphereMask) != 0; } - protected void readNumberOfPoints() { + protected void readNumberOfPoints() throws SQLServerException { if (isSinglePoint) { numberOfPoints = 1; } else if (isSingleLineSegment) { numberOfPoints = 2; } else { - numberOfPoints = buffer.getInt(); + numberOfPoints = readInt(); + checkNegSize(numberOfPoints); } } - protected void readZvalues() { + protected void readZvalues() throws SQLServerException { zValues = new double[numberOfPoints]; for (int i = 0; i < numberOfPoints; i++) { - zValues[i] = buffer.getDouble(); + zValues[i] = readDouble(); } } - protected void readMvalues() { + protected void readMvalues() throws SQLServerException { mValues = new double[numberOfPoints]; for (int i = 0; i < numberOfPoints; i++) { - mValues[i] = buffer.getDouble(); + mValues[i] = readDouble(); } } - protected void readNumberOfFigures() { - numberOfFigures = buffer.getInt(); + protected void readNumberOfFigures() throws SQLServerException { + numberOfFigures = readInt(); + checkNegSize(numberOfFigures); } - protected void readFigures() { + protected void readFigures() throws SQLServerException { byte fa; int po; figures = new Figure[numberOfFigures]; for (int i = 0; i < numberOfFigures; i++) { - fa = buffer.get(); - po = buffer.getInt(); + fa = readByte(); + po = readInt(); figures[i] = new Figure(fa, po); } } - protected void readNumberOfShapes() { - numberOfShapes = buffer.getInt(); + protected void readNumberOfShapes() throws SQLServerException { + numberOfShapes = readInt(); + checkNegSize(numberOfShapes); } - protected void readShapes() { + protected void readShapes() throws SQLServerException { int po; int fo; byte ogt; shapes = new Shape[numberOfShapes]; for (int i = 0; i < numberOfShapes; i++) { - po = buffer.getInt(); - fo = buffer.getInt(); - ogt = buffer.get(); + po = readInt(); + fo = readInt(); + ogt = readByte(); shapes[i] = new Shape(po, fo, ogt); } } - protected void readNumberOfSegments() { - numberOfSegments = buffer.getInt(); + protected void readNumberOfSegments() throws SQLServerException { + numberOfSegments = readInt(); + checkNegSize(numberOfSegments); } - protected void readSegments() { + protected void readSegments() throws SQLServerException { byte st; segments = new Segment[numberOfSegments]; for (int i = 0; i < numberOfSegments; i++) { - st = buffer.get(); + st = readByte(); segments[i] = new Segment(st); } } @@ -1348,12 +1455,22 @@ protected boolean checkEmptyKeyword(int parentShapeIndex, InternalSpatialDatatyp } if (!potentialEmptyKeyword.equals("")) { - MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_illegalWKTposition")); - throw new SQLServerException(form.format(new Object[] {currentWktPos}), null, 0, null); + throwIllegalWKTPosition(); } return false; } + protected void throwIllegalWKT() throws SQLServerException { + String strError = SQLServerException.getErrString("R_illegalWKT"); + throw new SQLServerException(strError, null, 0, null); + } + + protected void throwIllegalWKB() throws SQLServerException { + MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_ParsingError")); + Object[] msgArgs = {JDBCType.VARBINARY}; + throw new SQLServerException(this, form.format(msgArgs), null, 0, false); + } + private void incrementPointNumStartIfPointNotReused(int pointEndIndex) { // We need to increment PointNumStart if the last point was actually not re-used in the points array. // 0 for pointNumEnd indicates that this check is not applicable. @@ -1636,8 +1753,7 @@ private void readComma() throws SQLServerException { currentWktPos++; skipWhiteSpaces(); } else { - MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_illegalWKTposition")); - throw new SQLServerException(form.format(new Object[] {currentWktPos}), null, 0, null); + throwIllegalWKTPosition(); } } @@ -1646,6 +1762,62 @@ private void skipWhiteSpaces() { currentWktPos++; } } + + private void checkNegSize(int num) throws SQLServerException { + if (num < 0) { + throwIllegalWKB(); + } + } + + private void readPoints(SQLServerSpatialDatatype type) throws SQLServerException { + xValues = new double[numberOfPoints]; + yValues = new double[numberOfPoints]; + + if (type instanceof Geometry) { + for (int i = 0; i < numberOfPoints; i++) { + xValues[i] = readDouble(); + yValues[i] = readDouble(); + } + } else { // Geography + for (int i = 0; i < numberOfPoints; i++) { + yValues[i] = readDouble(); + xValues[i] = readDouble(); + } + } + } + + private void checkBuffer(int i) throws SQLServerException { + if (buffer.remaining() < i) { + throwIllegalWKB(); + } + } + + private boolean checkSQLLength(int length) throws SQLServerException { + if (null == wkt || wkt.length() < length) { + throwIllegalWKTPosition(); + } + return true; + } + + private void throwIllegalWKTPosition() throws SQLServerException { + MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_illegalWKTposition")); + throw new SQLServerException(form.format(new Object[] {currentWktPos}), null, 0, null); + } + + protected byte readByte() throws SQLServerException { + checkBuffer(1); + return buffer.get(); + } + + protected int readInt() throws SQLServerException { + checkBuffer(4); + return buffer.getInt(); + } + + protected double readDouble() throws SQLServerException { + checkBuffer(8); + return buffer.getDouble(); + } } @@ -1662,14 +1834,30 @@ class Figure { this.pointOffset = pointOffset; } + /** + * Returns the figuresAttribute value. + * + * @return byte figuresAttribute value. + */ public byte getFiguresAttribute() { return figuresAttribute; } + /** + * Returns the pointOffset value. + * + * @return int pointOffset value. + */ public int getPointOffset() { return pointOffset; } + /** + * Sets the figuresAttribute value. + * + * @param fa + * figuresAttribute value. + */ public void setFiguresAttribute(byte fa) { figuresAttribute = fa; } @@ -1691,18 +1879,39 @@ class Shape { this.openGISType = openGISType; } + /** + * Returns the parentOffset value. + * + * @return int parentOffset value. + */ public int getParentOffset() { return parentOffset; } + /** + * Returns the figureOffset value. + * + * @return int figureOffset value. + */ public int getFigureOffset() { return figureOffset; } + /** + * Returns the openGISType value. + * + * @return byte openGISType value. + */ public byte getOpenGISType() { return openGISType; } + /** + * Sets the figureOffset value. + * + * @param fo + * figureOffset value. + */ public void setFigureOffset(int fo) { figureOffset = fo; } @@ -1721,6 +1930,11 @@ class Segment { this.segmentType = segmentType; } + /** + * Returns the segmentType value. + * + * @return byte segmentType value. + */ public byte getSegmentType() { return segmentType; } @@ -1744,18 +1958,38 @@ class Point { this.m = m; } + /** + * Returns the x coordinate value. + * + * @return double x coordinate value. + */ public double getX() { return x; } + /** + * Returns the y coordinate value. + * + * @return double y coordinate value. + */ public double getY() { return y; } + /** + * Returns the z (elevation) value. + * + * @return double z value. + */ public double getZ() { return z; } + /** + * Returns the m (measure) value. + * + * @return double m value. + */ public double getM() { return m; } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/datatypes/SQLServerSpatialDatatypeTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/datatypes/SQLServerSpatialDatatypeTest.java index 09c435e98..5dc76d12b 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/datatypes/SQLServerSpatialDatatypeTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/datatypes/SQLServerSpatialDatatypeTest.java @@ -316,13 +316,15 @@ public void testFullGlobeWkt() throws SQLException { assertEquals(e.getMessage(), "Fullglobe is not supported for Geometry."); } - pstmt = (SQLServerPreparedStatement) con.prepareStatement("insert into " + geogTableName + " values (?)"); - pstmt.setGeography(1, geogWKT); - pstmt.execute(); + try (SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) con + .prepareStatement("insert into " + geogTableName + " values (?)");) { + pstmt.setGeography(1, geogWKT); + pstmt.execute(); - rs = (SQLServerResultSet) stmt.executeQuery("select c1 from " + geogTableName); - rs.next(); - assertEquals(rs.getGeography(1).asTextZM(), geoWKT); + rs = (SQLServerResultSet) stmt.executeQuery("select c1 from " + geogTableName); + rs.next(); + assertEquals(rs.getGeography(1).asTextZM(), geoWKT); + } } } @@ -345,7 +347,9 @@ public void testIllegalCases() throws SQLException { try { testWkt(geoWKT); } catch (SQLServerException e) { - assertEquals(e.getMessage(), TestResource.getResource("R_illegalCharWkt")); + MessageFormat form = new MessageFormat(TestResource.getResource("R_illegalCharWktPosition")); + Object[] msgArgs1 = {"90"}; + assertEquals(e.getMessage(), form.format(msgArgs1)); } // Not enough closing and opening bracket case @@ -443,12 +447,12 @@ public void testAllTypes() throws SQLException { beforeEachSetup(); String geoWKTPoint = "POINT(30 12.12312312 5 6)"; - String geoWKTLineString = "LINESTRING(1 1, 2 4 3, 3 9 123 332)"; + String geoWKTLineString = "LINESTRING(1 1 0 0, 2 4 3 0, 3 9 123 332)"; String geoWKTCircularString = "CIRCULARSTRING(1 1, 2 4, 3 9)"; String geoWKTCompoundCurve = "COMPOUNDCURVE((1 1, 1 3), (1 3, 3 3), (3 3, 3 1), (3 1, 1 1))"; String geoWKTCurvePolygon = "CURVEPOLYGON(CIRCULARSTRING(2 4, 4 2, 6 4, 4 6, 2 4))"; String geoWKTPolygon = "POLYGON((0 0, 0 3, 3 3, 3 0, 0 0), (1 1, 1 2, 2 1, 1 1))"; - String geoWKTMultiPoint = "MULTIPOINT((2 3), (7 8 9.5))"; + String geoWKTMultiPoint = "MULTIPOINT((2 3), (7 8 9.5 4))"; String geoWKTMultiLineString = "MULTILINESTRING((0 2, 1 1), (1 0, 1 1))"; String geoWKTMultiPolygon = "MULTIPOLYGON(((0 0, 0 3, 3 3, 3 0, 0 0), (1 1, 1 2, 2 1, 1 1)), ((9 9, 9 10, 10 9, 9 9)))"; String geoWKTGeometryCollection = "GEOMETRYCOLLECTION(POLYGON((0 0 2, 1 10 3, 1 0 4, 0 0 2)), POINT(3 3 1 2.5), LINESTRING(1 0, 0 1, -1 0), GEOMETRYCOLLECTION(GEOMETRYCOLLECTION(POINT(1 2 3 4))), GEOMETRYCOLLECTION(GEOMETRYCOLLECTION EMPTY), CURVEPOLYGON((0 0, 0 0, 0 0, 0 0), CIRCULARSTRING(1 3, 3 5, 4 7, 7 3, 1 3), COMPOUNDCURVE((0 -23.43778, 0 23.43778), CIRCULARSTRING(0 23.43778, -45 -23.43778, 0 -23.43778)), COMPOUNDCURVE((0 -23.43778, 7 7, 0 23.43778), CIRCULARSTRING(0 23.43778, 8 8, 8 8, -45 23.43778, -90 23.43778), (-90 23.43778, -90 -23.43778), CIRCULARSTRING(-90 -23.43778, -45 -23.43778, 0 -23.43778))))"; @@ -470,111 +474,113 @@ public void testAllTypes() throws SQLException { Geography geogWKT; // Geometry - pstmt = (SQLServerPreparedStatement) con.prepareStatement("insert into " + geomTableName + " values (?)"); - - geomWKT = Geometry.STGeomFromText(geoWKTPoint, 0); - pstmt.setGeometry(1, geomWKT); - pstmt.executeUpdate(); - - geomWKT = Geometry.STGeomFromText(geoWKTLineString, 0); - pstmt.setGeometry(1, geomWKT); - pstmt.executeUpdate(); - - geomWKT = Geometry.STGeomFromText(geoWKTCircularString, 0); - pstmt.setGeometry(1, geomWKT); - pstmt.executeUpdate(); - - geomWKT = Geometry.STGeomFromText(geoWKTCompoundCurve, 0); - pstmt.setGeometry(1, geomWKT); - pstmt.executeUpdate(); - - geomWKT = Geometry.STGeomFromText(geoWKTCurvePolygon, 0); - pstmt.setGeometry(1, geomWKT); - pstmt.executeUpdate(); - - geomWKT = Geometry.STGeomFromText(geoWKTPolygon, 0); - pstmt.setGeometry(1, geomWKT); - pstmt.executeUpdate(); - - geomWKT = Geometry.STGeomFromText(geoWKTMultiPoint, 0); - pstmt.setGeometry(1, geomWKT); - pstmt.executeUpdate(); - - geomWKT = Geometry.STGeomFromText(geoWKTMultiLineString, 0); - pstmt.setGeometry(1, geomWKT); - pstmt.executeUpdate(); - - geomWKT = Geometry.STGeomFromText(geoWKTMultiPolygon, 0); - pstmt.setGeometry(1, geomWKT); - pstmt.executeUpdate(); - - geomWKT = Geometry.STGeomFromText(geoWKTGeometryCollection, 0); - pstmt.setGeometry(1, geomWKT); - pstmt.executeUpdate(); - - rs = (SQLServerResultSet) stmt.executeQuery("select c1 from " + geomTableName); - for (int i = 0; i < geoWKTList.size(); i++) { - rs.next(); - assertEquals(rs.getGeometry(1).asTextZM(), geoWKTList.get(i)); + try (SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) con + .prepareStatement("insert into " + geomTableName + " values (?)");) { + geomWKT = Geometry.STGeomFromText(geoWKTPoint, 0); + pstmt.setGeometry(1, geomWKT); + pstmt.executeUpdate(); + + geomWKT = Geometry.STGeomFromText(geoWKTLineString, 0); + pstmt.setGeometry(1, geomWKT); + pstmt.executeUpdate(); + + geomWKT = Geometry.STGeomFromText(geoWKTCircularString, 0); + pstmt.setGeometry(1, geomWKT); + pstmt.executeUpdate(); + + geomWKT = Geometry.STGeomFromText(geoWKTCompoundCurve, 0); + pstmt.setGeometry(1, geomWKT); + pstmt.executeUpdate(); + + geomWKT = Geometry.STGeomFromText(geoWKTCurvePolygon, 0); + pstmt.setGeometry(1, geomWKT); + pstmt.executeUpdate(); + + geomWKT = Geometry.STGeomFromText(geoWKTPolygon, 0); + pstmt.setGeometry(1, geomWKT); + pstmt.executeUpdate(); + + geomWKT = Geometry.STGeomFromText(geoWKTMultiPoint, 0); + pstmt.setGeometry(1, geomWKT); + pstmt.executeUpdate(); + + geomWKT = Geometry.STGeomFromText(geoWKTMultiLineString, 0); + pstmt.setGeometry(1, geomWKT); + pstmt.executeUpdate(); + + geomWKT = Geometry.STGeomFromText(geoWKTMultiPolygon, 0); + pstmt.setGeometry(1, geomWKT); + pstmt.executeUpdate(); + + geomWKT = Geometry.STGeomFromText(geoWKTGeometryCollection, 0); + pstmt.setGeometry(1, geomWKT); + pstmt.executeUpdate(); + + rs = (SQLServerResultSet) stmt.executeQuery("select c1 from " + geomTableName); + for (int i = 0; i < geoWKTList.size(); i++) { + rs.next(); + assertEquals(rs.getGeometry(1).asTextZM(), geoWKTList.get(i)); + } } // Geography - pstmt = (SQLServerPreparedStatement) con.prepareStatement("insert into " + geogTableName + " values (?)"); + try (SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) con + .prepareStatement("insert into " + geogTableName + " values (?)");) { + geogWKT = Geography.STGeomFromText(geoWKTPoint, 4326); + pstmt.setGeography(1, geogWKT); - geogWKT = Geography.STGeomFromText(geoWKTPoint, 4326); - pstmt.setGeography(1, geogWKT); + pstmt.executeUpdate(); - pstmt.executeUpdate(); + geogWKT = Geography.STGeomFromText(geoWKTLineString, 4326); + pstmt.setGeography(1, geogWKT); - geogWKT = Geography.STGeomFromText(geoWKTLineString, 4326); - pstmt.setGeography(1, geogWKT); + pstmt.executeUpdate(); - pstmt.executeUpdate(); + geogWKT = Geography.STGeomFromText(geoWKTCircularString, 4326); + pstmt.setGeography(1, geogWKT); - geogWKT = Geography.STGeomFromText(geoWKTCircularString, 4326); - pstmt.setGeography(1, geogWKT); + pstmt.executeUpdate(); - pstmt.executeUpdate(); + geogWKT = Geography.STGeomFromText(geoWKTCompoundCurve, 4326); + pstmt.setGeography(1, geogWKT); - geogWKT = Geography.STGeomFromText(geoWKTCompoundCurve, 4326); - pstmt.setGeography(1, geogWKT); + pstmt.executeUpdate(); - pstmt.executeUpdate(); + geogWKT = Geography.STGeomFromText(geoWKTCurvePolygon, 4326); + pstmt.setGeography(1, geogWKT); - geogWKT = Geography.STGeomFromText(geoWKTCurvePolygon, 4326); - pstmt.setGeography(1, geogWKT); + pstmt.executeUpdate(); - pstmt.executeUpdate(); + geogWKT = Geography.STGeomFromText(geoWKTPolygon, 4326); + pstmt.setGeography(1, geogWKT); - geogWKT = Geography.STGeomFromText(geoWKTPolygon, 4326); - pstmt.setGeography(1, geogWKT); + pstmt.executeUpdate(); - pstmt.executeUpdate(); + geogWKT = Geography.STGeomFromText(geoWKTMultiPoint, 4326); + pstmt.setGeography(1, geogWKT); - geogWKT = Geography.STGeomFromText(geoWKTMultiPoint, 4326); - pstmt.setGeography(1, geogWKT); + pstmt.executeUpdate(); - pstmt.executeUpdate(); + geogWKT = Geography.STGeomFromText(geoWKTMultiLineString, 4326); + pstmt.setGeography(1, geogWKT); - geogWKT = Geography.STGeomFromText(geoWKTMultiLineString, 4326); - pstmt.setGeography(1, geogWKT); + pstmt.executeUpdate(); - pstmt.executeUpdate(); + geogWKT = Geography.STGeomFromText(geoWKTMultiPolygon, 4326); + pstmt.setGeography(1, geogWKT); - geogWKT = Geography.STGeomFromText(geoWKTMultiPolygon, 4326); - pstmt.setGeography(1, geogWKT); + pstmt.executeUpdate(); - pstmt.executeUpdate(); + geogWKT = Geography.STGeomFromText(geoWKTGeometryCollection, 4326); + pstmt.setGeography(1, geogWKT); - geogWKT = Geography.STGeomFromText(geoWKTGeometryCollection, 4326); - pstmt.setGeography(1, geogWKT); + pstmt.executeUpdate(); - pstmt.executeUpdate(); - - rs = (SQLServerResultSet) stmt.executeQuery("select c1 from " + geogTableName); - for (int i = 0; i < geoWKTList.size(); i++) { - rs.next(); - assertEquals(rs.getGeography(1).asTextZM(), geoWKTList.get(i)); + rs = (SQLServerResultSet) stmt.executeQuery("select c1 from " + geogTableName); + for (int i = 0; i < geoWKTList.size(); i++) { + rs.next(); + assertEquals(rs.getGeography(1).asTextZM(), geoWKTList.get(i)); + } } } } @@ -584,7 +590,7 @@ public void testMixedAllTypes() throws SQLException { if (isDenaliOrLater) { beforeEachSetupSpatialDatatype(); - String geoWKTPoint = "POINT(30 12.12312312 5 6)"; + String geoWKTPoint = "POINT(30 12.12312312 0)"; String geoWKTLineString = "LINESTRING(1 1, 2 4 3, 3 9 123 332)"; String geoWKTCircularString = "CIRCULARSTRING(1 1, 2 4, 3 9)"; String geoWKTCompoundCurve = "COMPOUNDCURVE((1 1, 1 3), (1 3, 3 3), (3 3, 3 1), (3 1, 1 1))"; @@ -615,117 +621,117 @@ public void testMixedAllTypes() throws SQLException { Geometry geomWKT; Geography geogWKT; - pstmt = (SQLServerPreparedStatement) con - .prepareStatement("insert into " + spatialDatatypeTableName + " values (?, ?, ?, ?, ?)"); - - geomWKT = Geometry.STGeomFromText(geoWKTPoint, 0); - geogWKT = Geography.STGeomFromText(geoWKTPoint, 4326); - pstmt.setGeometry(1, geomWKT); - pstmt.setGeography(2, geogWKT); - pstmt.setString(3, s); - pstmt.setDouble(4, d); - pstmt.setInt(5, i2); - - pstmt.executeUpdate(); - - geomWKT = Geometry.STGeomFromText(geoWKTLineString, 0); - geogWKT = Geography.STGeomFromText(geoWKTLineString, 4326); - pstmt.setGeometry(1, geomWKT); - pstmt.setGeography(2, geogWKT); - pstmt.setString(3, s); - pstmt.setDouble(4, d); - pstmt.setInt(5, i2); - - pstmt.executeUpdate(); - - geomWKT = Geometry.STGeomFromText(geoWKTCircularString, 0); - geogWKT = Geography.STGeomFromText(geoWKTCircularString, 4326); - pstmt.setGeometry(1, geomWKT); - pstmt.setGeography(2, geogWKT); - pstmt.setString(3, s); - pstmt.setDouble(4, d); - pstmt.setInt(5, i2); - - pstmt.executeUpdate(); - - geomWKT = Geometry.STGeomFromText(geoWKTCompoundCurve, 0); - geogWKT = Geography.STGeomFromText(geoWKTCompoundCurve, 4326); - pstmt.setGeometry(1, geomWKT); - pstmt.setGeography(2, geogWKT); - pstmt.setString(3, s); - pstmt.setDouble(4, d); - pstmt.setInt(5, i2); - - pstmt.executeUpdate(); - - geomWKT = Geometry.STGeomFromText(geoWKTCurvePolygon, 0); - geogWKT = Geography.STGeomFromText(geoWKTCurvePolygon, 4326); - pstmt.setGeometry(1, geomWKT); - pstmt.setGeography(2, geogWKT); - pstmt.setString(3, s); - pstmt.setDouble(4, d); - pstmt.setInt(5, i2); - - pstmt.executeUpdate(); - - geomWKT = Geometry.STGeomFromText(geoWKTPolygon, 0); - geogWKT = Geography.STGeomFromText(geoWKTPolygon, 4326); - pstmt.setGeometry(1, geomWKT); - pstmt.setGeography(2, geogWKT); - pstmt.setString(3, s); - pstmt.setDouble(4, d); - pstmt.setInt(5, i2); - - pstmt.executeUpdate(); - - geomWKT = Geometry.STGeomFromText(geoWKTMultiPoint, 0); - geogWKT = Geography.STGeomFromText(geoWKTMultiPoint, 4326); - pstmt.setGeometry(1, geomWKT); - pstmt.setGeography(2, geogWKT); - pstmt.setString(3, s); - pstmt.setDouble(4, d); - pstmt.setInt(5, i2); - - pstmt.executeUpdate(); - - geomWKT = Geometry.STGeomFromText(geoWKTMultiLineString, 0); - geogWKT = Geography.STGeomFromText(geoWKTMultiLineString, 4326); - pstmt.setGeometry(1, geomWKT); - pstmt.setGeography(2, geogWKT); - pstmt.setString(3, s); - pstmt.setDouble(4, d); - pstmt.setInt(5, i2); - - pstmt.executeUpdate(); - - geomWKT = Geometry.STGeomFromText(geoWKTMultiPolygon, 0); - geogWKT = Geography.STGeomFromText(geoWKTMultiPolygon, 4326); - pstmt.setGeometry(1, geomWKT); - pstmt.setGeography(2, geogWKT); - pstmt.setString(3, s); - pstmt.setDouble(4, d); - pstmt.setInt(5, i2); - - pstmt.executeUpdate(); - - geomWKT = Geometry.STGeomFromText(geoWKTGeometryCollection, 0); - geogWKT = Geography.STGeomFromText(geoWKTGeometryCollection, 4326); - pstmt.setGeometry(1, geomWKT); - pstmt.setGeography(2, geogWKT); - pstmt.setString(3, s); - pstmt.setDouble(4, d); - pstmt.setInt(5, i2); - - pstmt.executeUpdate(); - - rs = (SQLServerResultSet) stmt.executeQuery("select * from " + spatialDatatypeTableName); - for (int i = 0; i < geoWKTList.size(); i++) { - rs.next(); - assertEquals(rs.getGeometry(1).asTextZM(), geoWKTList.get(i)); - assertEquals(rs.getGeography(2).asTextZM(), geoWKTList.get(i)); - assertEquals(rs.getString(3), s); - assertEquals((Double) rs.getDouble(4), d); - assertEquals(rs.getInt(5), i2); + try (SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) con + .prepareStatement("insert into " + spatialDatatypeTableName + " values (?, ?, ?, ?, ?)");) { + geomWKT = Geometry.STGeomFromText(geoWKTPoint, 0); + geogWKT = Geography.STGeomFromText(geoWKTPoint, 4326); + pstmt.setGeometry(1, geomWKT); + pstmt.setGeography(2, geogWKT); + pstmt.setString(3, s); + pstmt.setDouble(4, d); + pstmt.setInt(5, i2); + + pstmt.executeUpdate(); + + geomWKT = Geometry.STGeomFromText(geoWKTLineString, 0); + geogWKT = Geography.STGeomFromText(geoWKTLineString, 4326); + pstmt.setGeometry(1, geomWKT); + pstmt.setGeography(2, geogWKT); + pstmt.setString(3, s); + pstmt.setDouble(4, d); + pstmt.setInt(5, i2); + + pstmt.executeUpdate(); + + geomWKT = Geometry.STGeomFromText(geoWKTCircularString, 0); + geogWKT = Geography.STGeomFromText(geoWKTCircularString, 4326); + pstmt.setGeometry(1, geomWKT); + pstmt.setGeography(2, geogWKT); + pstmt.setString(3, s); + pstmt.setDouble(4, d); + pstmt.setInt(5, i2); + + pstmt.executeUpdate(); + + geomWKT = Geometry.STGeomFromText(geoWKTCompoundCurve, 0); + geogWKT = Geography.STGeomFromText(geoWKTCompoundCurve, 4326); + pstmt.setGeometry(1, geomWKT); + pstmt.setGeography(2, geogWKT); + pstmt.setString(3, s); + pstmt.setDouble(4, d); + pstmt.setInt(5, i2); + + pstmt.executeUpdate(); + + geomWKT = Geometry.STGeomFromText(geoWKTCurvePolygon, 0); + geogWKT = Geography.STGeomFromText(geoWKTCurvePolygon, 4326); + pstmt.setGeometry(1, geomWKT); + pstmt.setGeography(2, geogWKT); + pstmt.setString(3, s); + pstmt.setDouble(4, d); + pstmt.setInt(5, i2); + + pstmt.executeUpdate(); + + geomWKT = Geometry.STGeomFromText(geoWKTPolygon, 0); + geogWKT = Geography.STGeomFromText(geoWKTPolygon, 4326); + pstmt.setGeometry(1, geomWKT); + pstmt.setGeography(2, geogWKT); + pstmt.setString(3, s); + pstmt.setDouble(4, d); + pstmt.setInt(5, i2); + + pstmt.executeUpdate(); + + geomWKT = Geometry.STGeomFromText(geoWKTMultiPoint, 0); + geogWKT = Geography.STGeomFromText(geoWKTMultiPoint, 4326); + pstmt.setGeometry(1, geomWKT); + pstmt.setGeography(2, geogWKT); + pstmt.setString(3, s); + pstmt.setDouble(4, d); + pstmt.setInt(5, i2); + + pstmt.executeUpdate(); + + geomWKT = Geometry.STGeomFromText(geoWKTMultiLineString, 0); + geogWKT = Geography.STGeomFromText(geoWKTMultiLineString, 4326); + pstmt.setGeometry(1, geomWKT); + pstmt.setGeography(2, geogWKT); + pstmt.setString(3, s); + pstmt.setDouble(4, d); + pstmt.setInt(5, i2); + + pstmt.executeUpdate(); + + geomWKT = Geometry.STGeomFromText(geoWKTMultiPolygon, 0); + geogWKT = Geography.STGeomFromText(geoWKTMultiPolygon, 4326); + pstmt.setGeometry(1, geomWKT); + pstmt.setGeography(2, geogWKT); + pstmt.setString(3, s); + pstmt.setDouble(4, d); + pstmt.setInt(5, i2); + + pstmt.executeUpdate(); + + geomWKT = Geometry.STGeomFromText(geoWKTGeometryCollection, 0); + geogWKT = Geography.STGeomFromText(geoWKTGeometryCollection, 4326); + pstmt.setGeometry(1, geomWKT); + pstmt.setGeography(2, geogWKT); + pstmt.setString(3, s); + pstmt.setDouble(4, d); + pstmt.setInt(5, i2); + + pstmt.executeUpdate(); + + rs = (SQLServerResultSet) stmt.executeQuery("select * from " + spatialDatatypeTableName); + for (int i = 0; i < geoWKTList.size(); i++) { + rs.next(); + assertEquals(rs.getGeometry(1).asTextZM(), geoWKTList.get(i)); + assertEquals(rs.getGeography(2).asTextZM(), geoWKTList.get(i)); + assertEquals(rs.getString(3), s); + assertEquals((Double) rs.getDouble(4), d); + assertEquals(rs.getInt(5), i2); + } } } } @@ -749,23 +755,27 @@ public void testParse() throws SQLException { Geometry geomWKT = Geometry.parse(geoWKT); Geography geogWKT = Geography.parse(geoWKT); - pstmt = (SQLServerPreparedStatement) con.prepareStatement("insert into " + geomTableName + " values (?)"); - pstmt.setGeometry(1, geomWKT); - pstmt.execute(); + try (SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) con + .prepareStatement("insert into " + geomTableName + " values (?)");) { + pstmt.setGeometry(1, geomWKT); + pstmt.execute(); - rs = (SQLServerResultSet) stmt.executeQuery("select c1 from " + geomTableName); - rs.next(); - assertEquals(rs.getGeometry(1).asTextZM(), geoWKT); - assertEquals(rs.getGeometry(1).getSrid(), 0); + rs = (SQLServerResultSet) stmt.executeQuery("select c1 from " + geomTableName); + rs.next(); + assertEquals(rs.getGeometry(1).asTextZM(), geoWKT); + assertEquals(rs.getGeometry(1).getSrid(), 0); + } - pstmt = (SQLServerPreparedStatement) con.prepareStatement("insert into " + geogTableName + " values (?)"); - pstmt.setGeography(1, geogWKT); - pstmt.execute(); + try (SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) con + .prepareStatement("insert into " + geogTableName + " values (?)");) { + pstmt.setGeography(1, geogWKT); + pstmt.execute(); - rs = (SQLServerResultSet) stmt.executeQuery("select c1 from " + geogTableName); - rs.next(); - assertEquals(rs.getGeography(1).asTextZM(), geoWKT); - assertEquals(rs.getGeography(1).getSrid(), 4326); + rs = (SQLServerResultSet) stmt.executeQuery("select c1 from " + geogTableName); + rs.next(); + assertEquals(rs.getGeography(1).asTextZM(), geoWKT); + assertEquals(rs.getGeography(1).getSrid(), 4326); + } } @Test @@ -777,23 +787,27 @@ public void testPoint() throws SQLException { Geometry geomWKT = Geometry.point(1, 2, 0); Geography geogWKT = Geography.point(1, 2, 4326); - pstmt = (SQLServerPreparedStatement) con.prepareStatement("insert into " + geomTableName + " values (?)"); - pstmt.setGeometry(1, geomWKT); - pstmt.execute(); + try (SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) con + .prepareStatement("insert into " + geomTableName + " values (?)");) { + pstmt.setGeometry(1, geomWKT); + pstmt.execute(); - rs = (SQLServerResultSet) stmt.executeQuery("select c1 from " + geomTableName); - rs.next(); - assertEquals(rs.getGeometry(1).asTextZM(), geoWKT); - assertEquals(rs.getGeometry(1).getSrid(), 0); + rs = (SQLServerResultSet) stmt.executeQuery("select c1 from " + geomTableName); + rs.next(); + assertEquals(rs.getGeometry(1).asTextZM(), geoWKT); + assertEquals(rs.getGeometry(1).getSrid(), 0); + } - pstmt = (SQLServerPreparedStatement) con.prepareStatement("insert into " + geogTableName + " values (?)"); - pstmt.setGeography(1, geogWKT); - pstmt.execute(); + try (SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) con + .prepareStatement("insert into " + geogTableName + " values (?)");) { + pstmt.setGeography(1, geogWKT); + pstmt.execute(); - rs = (SQLServerResultSet) stmt.executeQuery("select c1 from " + geogTableName); - rs.next(); - assertEquals(rs.getGeography(1).asTextZM(), geoWKT); - assertEquals(rs.getGeography(1).getSrid(), 4326); + rs = (SQLServerResultSet) stmt.executeQuery("select c1 from " + geogTableName); + rs.next(); + assertEquals(rs.getGeography(1).asTextZM(), geoWKT); + assertEquals(rs.getGeography(1).getSrid(), 4326); + } } @Test @@ -806,21 +820,25 @@ public void testSTAsText() throws SQLException { Geometry geomWKT = Geometry.STGeomFromText(geoWKT, 0); Geography geogWKT = Geography.STGeomFromText(geoWKT, 4326); - pstmt = (SQLServerPreparedStatement) con.prepareStatement("insert into " + geomTableName + " values (?)"); - pstmt.setGeometry(1, geomWKT); - pstmt.execute(); + try (SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) con + .prepareStatement("insert into " + geomTableName + " values (?)");) { + pstmt.setGeometry(1, geomWKT); + pstmt.execute(); - rs = (SQLServerResultSet) stmt.executeQuery("select c1 from " + geomTableName); - rs.next(); - assertEquals(rs.getGeometry(1).STAsText(), geoWKTSS); + rs = (SQLServerResultSet) stmt.executeQuery("select c1 from " + geomTableName); + rs.next(); + assertEquals(rs.getGeometry(1).STAsText(), geoWKTSS); + } - pstmt = (SQLServerPreparedStatement) con.prepareStatement("insert into " + geogTableName + " values (?)"); - pstmt.setGeography(1, geogWKT); - pstmt.execute(); + try (SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) con + .prepareStatement("insert into " + geogTableName + " values (?)");) { + pstmt.setGeography(1, geogWKT); + pstmt.execute(); - rs = (SQLServerResultSet) stmt.executeQuery("select c1 from " + geogTableName); - rs.next(); - assertEquals(rs.getGeography(1).STAsText(), geoWKTSS); + rs = (SQLServerResultSet) stmt.executeQuery("select c1 from " + geogTableName); + rs.next(); + assertEquals(rs.getGeography(1).STAsText(), geoWKTSS); + } } @Test @@ -849,40 +867,121 @@ public void testSTAsBinary() throws SQLException { public void testCheckGeomMetaData() throws SQLException { beforeEachSetup(); - pstmt = (SQLServerPreparedStatement) connection - .prepareStatement("INSERT INTO " + geomTableName + " (c1) VALUES (?)"); - ParameterMetaData paramMetaData = pstmt.getParameterMetaData(); - Geometry g = Geometry.STGeomFromText("POINT (1 2 3 4)", 0); - pstmt.setGeometry(1, g); - pstmt.execute(); + try (SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) connection + .prepareStatement("INSERT INTO " + geomTableName + " (c1) VALUES (?)");) { + ParameterMetaData paramMetaData = pstmt.getParameterMetaData(); + Geometry g = Geometry.STGeomFromText("POINT (1 2 3 4)", 0); + pstmt.setGeometry(1, g); + pstmt.execute(); - int sqlType = paramMetaData.getParameterType(1); - String sqlTypeName = paramMetaData.getParameterTypeName(1); - assertEquals(sqlType, -157); - assertEquals(sqlTypeName, "geometry"); - SQLServerResultSet rs = (SQLServerResultSet) stmt.executeQuery("select * from " + geomTableName); - ResultSetMetaData rsmd = rs.getMetaData(); - assertEquals(rsmd.getColumnType(1), -157); + int sqlType = paramMetaData.getParameterType(1); + String sqlTypeName = paramMetaData.getParameterTypeName(1); + assertEquals(sqlType, -157); + assertEquals(sqlTypeName, "geometry"); + SQLServerResultSet rs = (SQLServerResultSet) stmt.executeQuery("select * from " + geomTableName); + ResultSetMetaData rsmd = rs.getMetaData(); + assertEquals(rsmd.getColumnType(1), -157); + } } @Test public void testCheckGeogMetaData() throws SQLException { beforeEachSetup(); - pstmt = (SQLServerPreparedStatement) connection - .prepareStatement("INSERT INTO " + geogTableName + " (c1) VALUES (?)"); - ParameterMetaData paramMetaData = pstmt.getParameterMetaData(); - Geography g = Geography.STGeomFromText("POINT (1 2 3 4)", 4326); - pstmt.setGeography(1, g); - pstmt.execute(); + try (SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) connection + .prepareStatement("INSERT INTO " + geogTableName + " (c1) VALUES (?)");) { + ParameterMetaData paramMetaData = pstmt.getParameterMetaData(); + Geography g = Geography.STGeomFromText("POINT (1 2 3 4)", 4326); + pstmt.setGeography(1, g); + pstmt.execute(); + + int sqlType = paramMetaData.getParameterType(1); + String sqlTypeName = paramMetaData.getParameterTypeName(1); + assertEquals(sqlType, -158); + assertEquals(sqlTypeName, "geography"); + SQLServerResultSet rs = (SQLServerResultSet) stmt.executeQuery("select * from " + geogTableName); + ResultSetMetaData rsmd = rs.getMetaData(); + assertEquals(rsmd.getColumnType(1), -158); + } + } + + @Test + public void testGetXGetY() throws SQLException { + Geometry geom = Geometry.STGeomFromText("POINT (1 2 3 4)", 0); + Geography geog = Geography.STGeomFromText("POINT (1 2 3 4)", 4326); + + double x = geom.getX(); + double y = geom.getY(); + assertEquals(x, 1); + assertEquals(y, 2); + + x = geog.getLatitude(); + y = geog.getLongitude(); + assertEquals(x, 1); + assertEquals(y, 2); + } + + @Test + public void testNull() throws SQLException { + if (isDenaliOrLater) { + beforeEachSetupSpatialDatatype(); + + String geoWKTPoint = "POINT(30 12.12312312 NULL 6)"; + String geoWKTLineString = "LINESTRING(1 1 NULL NULL, 2 4 0 42, 3 9 NULL 332)"; + + String geoWKTPointExpected = "POINT(30 12.12312312 NULL 6)"; + String geoWKTLineStringExpected = "LINESTRING(1 1, 2 4 0 42, 3 9 NULL 332)"; + + String s = "some string"; + Double d = 31.34; + int i2 = 5; + + List geoWKTList = new ArrayList(); + + geoWKTList.add(geoWKTPoint); + geoWKTList.add(geoWKTLineString); + + List geoWKTListExpected = new ArrayList(); + + geoWKTListExpected.add(geoWKTPointExpected); + geoWKTListExpected.add(geoWKTLineStringExpected); + + Geometry geomWKT; + Geography geogWKT; - int sqlType = paramMetaData.getParameterType(1); - String sqlTypeName = paramMetaData.getParameterTypeName(1); - assertEquals(sqlType, -158); - assertEquals(sqlTypeName, "geography"); - SQLServerResultSet rs = (SQLServerResultSet) stmt.executeQuery("select * from " + geogTableName); - ResultSetMetaData rsmd = rs.getMetaData(); - assertEquals(rsmd.getColumnType(1), -158); + try (SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) con + .prepareStatement("insert into " + spatialDatatypeTableName + " values (?, ?, ?, ?, ?)");) { + geomWKT = Geometry.STGeomFromText(geoWKTPoint, 0); + geogWKT = Geography.STGeomFromText(geoWKTPoint, 4326); + pstmt.setGeometry(1, geomWKT); + pstmt.setGeography(2, geogWKT); + pstmt.setString(3, s); + pstmt.setDouble(4, d); + pstmt.setInt(5, i2); + + pstmt.executeUpdate(); + + geomWKT = Geometry.STGeomFromText(geoWKTLineString, 0); + geogWKT = Geography.STGeomFromText(geoWKTLineString, 4326); + pstmt.setGeometry(1, geomWKT); + pstmt.setGeography(2, geogWKT); + pstmt.setString(3, s); + pstmt.setDouble(4, d); + pstmt.setInt(5, i2); + + pstmt.executeUpdate(); + + rs = (SQLServerResultSet) stmt.executeQuery("select * from " + spatialDatatypeTableName); + for (int i = 0; i < geoWKTList.size(); i++) { + rs.next(); + assertEquals(rs.getGeometry(1).asTextZM(), geoWKTListExpected.get(i)); + assertEquals(rs.getGeography(2).asTextZM(), geoWKTListExpected.get(i)); + assertEquals(rs.getString(3), s); + assertEquals((Double) rs.getDouble(4), d); + assertEquals(rs.getInt(5), i2); + } + } + } } private void beforeEachSetup() throws SQLException { @@ -906,21 +1005,25 @@ private void testWkt(String geoWKT, String geoWKTSS) throws SQLException { Geometry geomWKT = Geometry.STGeomFromText(geoWKT, 0); Geography geogWKT = Geography.STGeomFromText(geoWKT, 4326); - pstmt = (SQLServerPreparedStatement) con.prepareStatement("insert into " + geomTableName + " values (?)"); - pstmt.setGeometry(1, geomWKT); - pstmt.execute(); + try (SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) con + .prepareStatement("insert into " + geomTableName + " values (?)");) { + pstmt.setGeometry(1, geomWKT); + pstmt.execute(); - rs = (SQLServerResultSet) stmt.executeQuery("select c1 from " + geomTableName); - rs.next(); - assertEquals(rs.getGeometry(1).asTextZM(), geoWKTSS); + rs = (SQLServerResultSet) stmt.executeQuery("select c1 from " + geomTableName); + rs.next(); + assertEquals(rs.getGeometry(1).asTextZM(), geoWKTSS); + } - pstmt = (SQLServerPreparedStatement) con.prepareStatement("insert into " + geogTableName + " values (?)"); - pstmt.setGeography(1, geogWKT); - pstmt.execute(); + try (SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) con + .prepareStatement("insert into " + geogTableName + " values (?)");) { + pstmt.setGeography(1, geogWKT); + pstmt.execute(); - rs = (SQLServerResultSet) stmt.executeQuery("select c1 from " + geogTableName); - rs.next(); - assertEquals(rs.getGeography(1).asTextZM(), geoWKTSS); + rs = (SQLServerResultSet) stmt.executeQuery("select c1 from " + geogTableName); + rs.next(); + assertEquals(rs.getGeography(1).asTextZM(), geoWKTSS); + } } private static byte[] hexStringToByteArray(String s) { diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/preparedStatement/BatchExecutionWithBulkCopyTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/preparedStatement/BatchExecutionWithBulkCopyTest.java index 90aab648e..bc685d7b3 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/preparedStatement/BatchExecutionWithBulkCopyTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/preparedStatement/BatchExecutionWithBulkCopyTest.java @@ -6,6 +6,7 @@ import java.lang.reflect.Field; import java.lang.reflect.Method; +import java.sql.BatchUpdateException; import java.sql.Connection; import java.sql.Date; import java.sql.DriverManager; @@ -23,6 +24,8 @@ import org.junit.runner.RunWith; import org.opentest4j.TestAbortedException; +import com.microsoft.sqlserver.jdbc.Geography; +import com.microsoft.sqlserver.jdbc.Geometry; import com.microsoft.sqlserver.jdbc.SQLServerConnection; import com.microsoft.sqlserver.jdbc.SQLServerPreparedStatement; import com.microsoft.sqlserver.jdbc.SQLServerStatement; @@ -35,6 +38,7 @@ public class BatchExecutionWithBulkCopyTest extends AbstractTest { static long UUID = System.currentTimeMillis();; static String tableName = "BulkCopyParseTest" + UUID; + static String unsupportedTableName = "BulkCopyUnsupportedTable" + UUID; static String squareBracketTableName = "[peter]]]]test" + UUID + "]"; static String doubleQuoteTableName = "\"peter\"\"\"\"test" + UUID + "\""; @@ -71,7 +75,7 @@ public void testComments() throws Exception { boolean.class, boolean.class, boolean.class); method.setAccessible(true); - assertEquals((String) method.invoke(pstmt, false, false, false, false), "PeterTable"); + assertEquals("PeterTable", (String) method.invoke(pstmt, false, false, false, false)); } } @@ -90,7 +94,7 @@ public void testBrackets() throws Exception { boolean.class, boolean.class, boolean.class); method.setAccessible(true); - assertEquals((String) method.invoke(pstmt, false, false, false, false), "[Peter[]]Table]"); + assertEquals("[Peter[]]Table]", (String) method.invoke(pstmt, false, false, false, false)); } } @@ -109,7 +113,7 @@ public void testDoubleQuotes() throws Exception { boolean.class, boolean.class, boolean.class); method.setAccessible(true); - assertEquals((String) method.invoke(pstmt, false, false, false, false), "\"Peter\"\"\"\"Table\""); + assertEquals("\"Peter\"\"\"\"Table\"", (String) method.invoke(pstmt, false, false, false, false)); } } @@ -143,21 +147,7 @@ public void testAll() throws Exception { columnListExpected.add("c4"); for (int i = 0; i < columnListExpected.size(); i++) { - assertEquals(columnList.get(i), columnListExpected.get(i)); - } - - method = pstmt.getClass().getDeclaredMethod("parseUserSQLForValueListDW", boolean.class); - method.setAccessible(true); - - ArrayList valueList = (ArrayList) method.invoke(pstmt, false); - ArrayList valueListExpected = new ArrayList(); - valueListExpected.add("1"); - valueListExpected.add("2"); - valueListExpected.add("'?'"); - valueListExpected.add("?"); - - for (int i = 0; i < valueListExpected.size(); i++) { - assertEquals(valueList.get(i), valueListExpected.get(i)); + assertEquals(columnListExpected.get(i), columnList.get(i)); } } } @@ -165,7 +155,7 @@ public void testAll() throws Exception { @Test public void testAllcolumns() throws Exception { String valid = "INSERT INTO " + tableName + " values " + "(" + "?, " + "?, " + "?, " + "?, " + "?, " + "?, " - + "?, " + "?, " + "?, " + ")"; + + "?, " + "?, " + "? " + ")"; try (Connection connection = DriverManager.getConnection(connectionString + ";useBulkCopyForBatchInsert=true;"); SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) connection.prepareStatement(valid); @@ -207,14 +197,14 @@ public void testAllcolumns() throws Exception { rs.next(); for (int i = 0; i < expected.length; i++) { - assertEquals(rs.getObject(i + 1).toString(), expected[i].toString()); + assertEquals(expected[i].toString(), rs.getObject(i + 1).toString()); } } } @Test public void testMixColumns() throws Exception { - String valid = "INSERT INTO " + tableName + " (c1, c3, c5, c8) values " + "(" + "?, " + "?, " + "?, " + "?, " + String valid = "INSERT INTO " + tableName + " (c1, c3, c5, c8) values " + "(" + "?, " + "?, " + "?, " + "? " + ")"; try (Connection connection = DriverManager.getConnection(connectionString + ";useBulkCopyForBatchInsert=true;"); @@ -253,7 +243,7 @@ public void testMixColumns() throws Exception { rs.next(); for (int i = 0; i < expected.length; i++) { if (null != rs.getObject(i + 1)) { - assertEquals(rs.getObject(i + 1).toString(), expected[i].toString()); + assertEquals(expected[i].toString(), rs.getObject(i + 1).toString()); } } } @@ -262,7 +252,7 @@ public void testMixColumns() throws Exception { @Test public void testNullOrEmptyColumns() throws Exception { String valid = "INSERT INTO " + tableName + " (c1, c2, c3, c4, c5, c6, c7) values " + "(" + "?, " + "?, " - + "?, " + "?, " + "?, " + "?, " + "?, " + ")"; + + "?, " + "?, " + "?, " + "?, " + "? " + ")"; try (Connection connection = DriverManager.getConnection(connectionString + ";useBulkCopyForBatchInsert=true;"); SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) connection.prepareStatement(valid); @@ -297,13 +287,14 @@ public void testNullOrEmptyColumns() throws Exception { rs.next(); for (int i = 0; i < expected.length; i++) { if (null != rs.getObject(i + 1)) { - assertEquals(rs.getObject(i + 1), expected[i]); + assertEquals(expected[i], rs.getObject(i + 1)); } } } } - @Test + // Non-parameterized queries are not supported anymore. + // @Test public void testAllFilledColumns() throws Exception { String valid = "INSERT INTO " + tableName + " values " + "(" + "1234, " + "false, " + "a, " + "null, " + "null, " + "123.45, " + "b, " + "varc, " + "sadf, " + ")"; @@ -335,7 +326,7 @@ public void testAllFilledColumns() throws Exception { rs.next(); for (int i = 0; i < expected.length; i++) { - assertEquals(rs.getObject(i + 1), expected[i]); + assertEquals(expected[i], rs.getObject(i + 1)); } } } @@ -363,7 +354,7 @@ public void testSquareBracketAgainstDB() throws Exception { ResultSet rs = stmt.executeQuery("SELECT * FROM " + squareBracketTableName); rs.next(); - assertEquals(rs.getObject(1), 1); + assertEquals(1, rs.getObject(1)); } } @@ -390,7 +381,7 @@ public void testDoubleQuoteAgainstDB() throws Exception { ResultSet rs = stmt.executeQuery("SELECT * FROM " + doubleQuoteTableName); rs.next(); - assertEquals(rs.getObject(1), 1); + assertEquals(1, rs.getObject(1)); } } @@ -419,7 +410,7 @@ public void testSchemaAgainstDB() throws Exception { ResultSet rs = stmt.executeQuery("SELECT * FROM " + schemaTableName); rs.next(); - assertEquals(rs.getObject(1), 1); + assertEquals(1, rs.getObject(1)); } } @@ -446,14 +437,14 @@ public void testColumnNameMixAgainstDB() throws Exception { ResultSet rs = stmt.executeQuery("SELECT * FROM " + squareBracketTableName); rs.next(); - assertEquals(rs.getObject(1), 1); + assertEquals(1, rs.getObject(1)); } } @Test - public void testAlColumnsLargeBatch() throws Exception { + public void testAllColumnsLargeBatch() throws Exception { String valid = "INSERT INTO " + tableName + " values " + "(" + "?, " + "?, " + "?, " + "?, " + "?, " + "?, " - + "?, " + "?, " + "?, " + ")"; + + "?, " + "?, " + "? " + ")"; try (Connection connection = DriverManager.getConnection(connectionString + ";useBulkCopyForBatchInsert=true;"); SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) connection.prepareStatement(valid); @@ -495,11 +486,138 @@ public void testAlColumnsLargeBatch() throws Exception { rs.next(); for (int i = 0; i < expected.length; i++) { - assertEquals(rs.getObject(i + 1).toString(), expected[i].toString()); + assertEquals(expected[i].toString(), rs.getObject(i + 1).toString()); } } } + @Test + public void testIllegalNumberOfArgNoColumnList() throws Exception { + String invalid = "insert into " + tableName + " values (?, ?,? ,?) "; + + try (Connection connection = DriverManager.getConnection(connectionString + ";useBulkCopyForBatchInsert=true;"); + SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) connection.prepareStatement(invalid); + Statement stmt = (SQLServerStatement) connection.createStatement();) { + Field f1 = SQLServerConnection.class.getDeclaredField("isAzureDW"); + f1.setAccessible(true); + f1.set(connection, true); + + pstmt.setInt(1, 1); + pstmt.setInt(2, 1); + pstmt.setInt(3, 1); + pstmt.setInt(4, 1); + pstmt.addBatch(); + + pstmt.executeBatch(); + throw new Exception("Test did not throw an exception when it was expected."); + } catch (BatchUpdateException e) { + assertEquals("Column name or number of supplied values does not match table definition.", e.getMessage()); + } + + invalid = "insert into " + tableName + " (c1, c2, c3) values (?, ?,? ,?) "; + + try (Connection connection = DriverManager.getConnection(connectionString + ";useBulkCopyForBatchInsert=true;"); + SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) connection.prepareStatement(invalid); + Statement stmt = (SQLServerStatement) connection.createStatement();) { + Field f1 = SQLServerConnection.class.getDeclaredField("isAzureDW"); + f1.setAccessible(true); + f1.set(connection, true); + + pstmt.setInt(1, 1); + pstmt.setInt(2, 1); + pstmt.setInt(3, 1); + pstmt.setInt(4, 1); + pstmt.addBatch(); + + pstmt.executeBatch(); + throw new Exception("Test did not throw an exception when it was expected."); + } catch (BatchUpdateException e) { + assertEquals("There are fewer columns in the INSERT statement than values specified in the VALUES clause. The number of values in the VALUES clause must match the number of columns specified in the INSERT statement.", e.getMessage()); + } + } + + @Test + public void testNonParameterizedQuery() throws Exception { + String invalid = "insert into " + tableName + " values ((SELECT * from table where c1=?), ?,? ,?) "; + + try (Connection connection = DriverManager.getConnection(connectionString + ";useBulkCopyForBatchInsert=true;"); + SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) connection.prepareStatement(invalid); + Statement stmt = (SQLServerStatement) connection.createStatement();) { + Field f1 = SQLServerConnection.class.getDeclaredField("isAzureDW"); + f1.setAccessible(true); + f1.set(connection, true); + + pstmt.setInt(1, 1); + pstmt.setInt(2, 1); + pstmt.setInt(3, 1); + pstmt.setInt(4, 1); + pstmt.addBatch(); + + pstmt.executeBatch(); + throw new Exception("Test did not throw an exception when it was expected."); + } catch (BatchUpdateException e) { + assertEquals("Incorrect syntax near the keyword 'table'.", e.getMessage()); + } + + invalid = "insert into " + tableName + " values ('?', ?,? ,?) "; + + try (Connection connection = DriverManager.getConnection(connectionString + ";useBulkCopyForBatchInsert=true;"); + SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) connection.prepareStatement(invalid); + Statement stmt = (SQLServerStatement) connection.createStatement();) { + Field f1 = SQLServerConnection.class.getDeclaredField("isAzureDW"); + f1.setAccessible(true); + f1.set(connection, true); + + pstmt.setInt(1, 1); + pstmt.setInt(2, 1); + pstmt.setInt(3, 1); + pstmt.addBatch(); + + pstmt.executeBatch(); + throw new Exception("Test did not throw an exception when it was expected."); + } catch (BatchUpdateException e) { + assertEquals("Column name or number of supplied values does not match table definition.", e.getMessage()); + } + } + + @Test + public void testNonSupportedColumns() throws Exception { + String valid = "insert into " + unsupportedTableName + " values (?, ?, ?, ?)"; + + try (Connection connection = DriverManager.getConnection(connectionString + ";useBulkCopyForBatchInsert=true;"); + SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) connection.prepareStatement(valid); + Statement stmt = (SQLServerStatement) connection.createStatement();) { + Field f1 = SQLServerConnection.class.getDeclaredField("isAzureDW"); + f1.setAccessible(true); + f1.set(connection, true); + + Utils.dropTableIfExists(unsupportedTableName, stmt); + + String createTable = "create table " + unsupportedTableName + + " (c1 geometry, c2 geography, c3 datetime, c4 smalldatetime)"; + stmt.execute(createTable); + + Timestamp myTimestamp = new Timestamp(1200000L); + Geometry g1 = Geometry.STGeomFromText("POINT(1 2 3 4)", 0); + Geography g2 = Geography.STGeomFromText("POINT(1 2 3 4)", 4326); + + pstmt.setGeometry(1, g1); + pstmt.setGeography(2, g2); + pstmt.setDateTime(3, myTimestamp); + pstmt.setSmallDateTime(4, myTimestamp); + pstmt.addBatch(); + + pstmt.executeBatch(); + + ResultSet rs = stmt.executeQuery("SELECT * FROM " + unsupportedTableName); + rs.next(); + assertEquals(g1.toString(), Geometry.STGeomFromWKB((byte[]) rs.getObject(1)).toString()); + assertEquals(g2.toString(), Geography.STGeomFromWKB((byte[]) rs.getObject(2)).toString()); + assertEquals(myTimestamp, rs.getObject(3)); + assertEquals(myTimestamp, rs.getObject(4)); + } + } + @BeforeEach public void testSetup() throws TestAbortedException, Exception { try (Connection connection = DriverManager @@ -522,6 +640,7 @@ public static void terminateVariation() throws SQLException { Utils.dropTableIfExists(tableName, stmt); Utils.dropTableIfExists(squareBracketTableName, stmt); Utils.dropTableIfExists(doubleQuoteTableName, stmt); + Utils.dropTableIfExists(unsupportedTableName, stmt); } } }