From 0e85b0acaed890a8d8291decb53d294de1281652 Mon Sep 17 00:00:00 2001 From: Peter Bae Date: Wed, 27 Sep 2017 14:23:10 -0700 Subject: [PATCH 01/41] creating files --- .../microsoft/sqlserver/jdbc/Geography.java | 5 + .../microsoft/sqlserver/jdbc/Geometry.java | 200 ++++++++++++++++++ .../jdbc/InternalSpatialDatatype.java | 41 ++++ 3 files changed, 246 insertions(+) create mode 100644 src/main/java/com/microsoft/sqlserver/jdbc/Geography.java create mode 100644 src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java create mode 100644 src/main/java/com/microsoft/sqlserver/jdbc/InternalSpatialDatatype.java diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/Geography.java b/src/main/java/com/microsoft/sqlserver/jdbc/Geography.java new file mode 100644 index 000000000..4afc49a50 --- /dev/null +++ b/src/main/java/com/microsoft/sqlserver/jdbc/Geography.java @@ -0,0 +1,5 @@ +package com.microsoft.sqlserver.jdbc; + +public class Geography { + +} diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java b/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java new file mode 100644 index 000000000..2c8753bf6 --- /dev/null +++ b/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java @@ -0,0 +1,200 @@ +package com.microsoft.sqlserver.jdbc; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +public class Geometry { + + private ByteBuffer buffer; + private InternalSpatialDatatype internalType; + private String WKT; + private int srid; + private byte version; + private byte serializationProperties; + private int numberOfPoints; + private int numberOfFigures; + private int numberOfShapes; + private double points[]; + private double zValues[]; + private double mValues[]; + private Figure figures[]; + private Shape shapes[]; + + //serialization properties + private boolean hasZvalues; + private boolean hasMvalues; + private boolean isValid ; + private boolean isSinglePoint; + private boolean isSingleLineSegment; + + private final byte hasZvaluesMask = 0b00000001; // 1 + private final byte hasMvaluesMask = 0b00000010; // 2 + private final byte isValidMask = 0b00000100; // 4 + private final byte isSinglePointMask = 0b00001000; // 8 + private final byte isSingleLineSegmentMask = 0b00010000; // 16 + + + public Geometry(String WellKnownText, int srid) { + this.WKT = WellKnownText; + this.srid = srid; + } + + public Geometry(byte[] hexData) { + buffer = ByteBuffer.wrap(hexData); + buffer.order(ByteOrder.LITTLE_ENDIAN); + + parseHexData(); + } + + public InternalSpatialDatatype getInternalType() { + return internalType; + } + + public int getSRID() { + return srid; + } + + public String toString() { + return WKT; + } + + private void parseHexData() { + srid = buffer.getInt(); + version = buffer.get(); + serializationProperties = buffer.get(); + + interpretSerializationPropBytes(); + + readNumberOfPoints(); + + readPoints(); + + readZvalues(); + + readMvalues(); + + readNumberOfFigures(); + + readFigures(); + + readNumberOfShapes(); + + readShapes(); + + if (version == 2) { + + } + } + + private void interpretSerializationPropBytes() { + hasZvalues = (serializationProperties & hasZvaluesMask) != 0; + hasMvalues = (serializationProperties & hasMvaluesMask) != 0; + isValid = (serializationProperties & isValidMask) != 0; + isSinglePoint = (serializationProperties & isSinglePointMask) != 0; + isSingleLineSegment = (serializationProperties & isSingleLineSegmentMask) != 0; + } + + private void readNumberOfPoints() { + if (isSinglePoint) { + numberOfPoints = 1; + } else if (isSingleLineSegment) { + numberOfPoints = 2; + } else { + numberOfPoints = buffer.getInt(); + } + } + + 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(); + } + } + + private void readZvalues() { + zValues = new double[numberOfPoints]; + for (int i = 0; i < numberOfPoints; i++) { + zValues[i] = buffer.getDouble(); + } + } + + private void readMvalues() { + mValues = new double[numberOfPoints]; + for (int i = 0; i < numberOfPoints; i++) { + mValues[i] = buffer.getDouble(); + } + } + + private void readNumberOfFigures() { + numberOfFigures = buffer.getInt(); + } + + private void readFigures() { + byte fa; + int po; + for (int i = 0; i < numberOfFigures; i++) { + fa = buffer.get(); + po = buffer.getInt(); + figures[i] = new Figure(fa, po); + } + } + + private void readNumberOfShapes() { + numberOfShapes = buffer.getInt(); + } + + private void readShapes() { + int po; + int fo; + byte ogt; + for (int i = 0; i < numberOfShapes; i++) { + po = buffer.getInt(); + fo = buffer.getInt(); + ogt = buffer.get(); + shapes[i] = new Shape(po, fo, ogt); + } + } +} + +class Figure { + private byte figuresAttribute; + private int pointOffset; + + Figure(byte figuresAttribute, int pointOffset) { + this.figuresAttribute = figuresAttribute; + this.pointOffset = pointOffset; + } + + public byte getFiguresAttribute() { + return figuresAttribute; + } + + public int getPointOffset() { + return pointOffset; + } +} + +class Shape { + private int parentOffset; + private int figureOffset; + private byte openGISType; + + Shape(int parentOffset, int figureOffset, byte openGISType) { + this.parentOffset = parentOffset; + this.figureOffset = figureOffset; + this.openGISType = openGISType; + } + + public int getParentOffset() { + return parentOffset; + } + + public int getFigureOffset() { + return figureOffset; + } + + public byte getOpenGISType() { + return openGISType; + } +} \ No newline at end of file diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/InternalSpatialDatatype.java b/src/main/java/com/microsoft/sqlserver/jdbc/InternalSpatialDatatype.java new file mode 100644 index 000000000..a4fc6edfb --- /dev/null +++ b/src/main/java/com/microsoft/sqlserver/jdbc/InternalSpatialDatatype.java @@ -0,0 +1,41 @@ +package com.microsoft.sqlserver.jdbc; + +public enum InternalSpatialDatatype { + POINT((byte)1, "POINT"), + LINESTRING((byte)2, "LINESTRING"), + POLYGON((byte)3, "POLYGON"), + MULTIPOINT((byte)4, "MULTIPOINT"), + MULTILINESTRING((byte)5, "MULTILINESTRING"), + MULTIPOLYGON((byte)6, "MULTIPOLYGON"), + GEOMETRYCOLLECTION((byte)7, "GEOMETRYCOLLECTION"), + CIRCULARSTRING((byte)8, "CIRCULARSTRING"), + COMPOUNDCURVE((byte)9, "COMPOUNDCURVE"), + CURVEPOLYGON((byte)10, "CURVEPOLYGON"), + FULLGLOBE((byte)11, "FULLGLOBE"), + INVALID_TYPE((byte)0, null); + + private byte typeCode; + private String typeName; + + private InternalSpatialDatatype(byte typeCode, String typeName) { + this.typeCode = typeCode; + this.typeName = typeName; + } + + public byte getTypeCode() { + return this.typeCode; + } + + public String getTypeName() { + return this.typeName; + } + + public static InternalSpatialDatatype valueOf(byte typeCode) { + for (InternalSpatialDatatype internalType : values()) { + if (internalType.typeCode == typeCode) { + return internalType; + } + } + return INVALID_TYPE; + } +} From 6f9a03e656d7b4ddbba658a4925b038aa246e393 Mon Sep 17 00:00:00 2001 From: Peter Bae Date: Thu, 28 Sep 2017 08:32:55 -0700 Subject: [PATCH 02/41] stash --- .../microsoft/sqlserver/jdbc/DataTypes.java | 4 ++- .../microsoft/sqlserver/jdbc/Geometry.java | 28 +++++++++++++++++++ .../jdbc/SQLServerPreparedStatement.java | 9 ++++++ src/main/java/microsoft/sql/Types.java | 2 ++ 4 files changed, 42 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/DataTypes.java b/src/main/java/com/microsoft/sqlserver/jdbc/DataTypes.java index 3f6ebdbea..7eebec85f 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/DataTypes.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/DataTypes.java @@ -856,7 +856,8 @@ enum JDBCType DATETIME (Category.TIMESTAMP, microsoft.sql.Types.DATETIME, "java.sql.Timestamp"), SMALLDATETIME (Category.TIMESTAMP, microsoft.sql.Types.SMALLDATETIME, "java.sql.Timestamp"), GUID (Category.CHARACTER, microsoft.sql.Types.GUID, "java.lang.String"), - SQL_VARIANT (Category.SQL_VARIANT, microsoft.sql.Types.SQL_VARIANT, "java.lang.Object"); + SQL_VARIANT (Category.SQL_VARIANT, microsoft.sql.Types.SQL_VARIANT, "java.lang.Object"), + GEOMETRY (Category.GEOMETRY, microsoft.sql.Types.GEOMETRY, "java.lang.Object"); final Category category; @@ -906,6 +907,7 @@ enum Category { TVP, GUID, SQL_VARIANT, + GEOMETRY, } // This SetterConversion enum is based on the Category enum diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java b/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java index 2c8753bf6..fe1bfeff5 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java @@ -14,11 +14,13 @@ public class Geometry { private int numberOfPoints; private int numberOfFigures; private int numberOfShapes; + private int numberOfSegments; private double points[]; private double zValues[]; private double mValues[]; private Figure figures[]; private Shape shapes[]; + private Segment segments[]; //serialization properties private boolean hasZvalues; @@ -82,7 +84,9 @@ private void parseHexData() { readShapes(); if (version == 2) { + readNumberOfSegments(); + readSegments(); } } @@ -155,6 +159,18 @@ private void readShapes() { shapes[i] = new Shape(po, fo, ogt); } } + + private void readNumberOfSegments() { + numberOfSegments = buffer.getInt(); + } + + private void readSegments() { + byte st; + for (int i = 0; i < numberOfSegments; i++) { + st = buffer.get(); + segments[i] = new Segment(st); + } + } } class Figure { @@ -197,4 +213,16 @@ public int getFigureOffset() { public byte getOpenGISType() { return openGISType; } +} + +class Segment { + private byte segmentType; + + Segment(byte segmentType) { + this.segmentType = segmentType; + } + + public byte getSegmentType() { + return segmentType; + } } \ No newline at end of file diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java index 1ba3c62e8..dc940f87b 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java @@ -1566,6 +1566,15 @@ public final void setFloat(int n, setValue(n, JDBCType.REAL, x, JavaType.FLOAT, forceEncrypt); loggerExternal.exiting(getClassNameLogging(), "setFloat"); } + + public final void setGeometry(int n, + Geometry x) throws SQLServerException { + if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) + loggerExternal.entering(getClassNameLogging(), "setGeometry", new Object[] {n, x}); + checkClosed(); + setValue(n, JDBCType.REAL, x, JavaType.FLOAT, false); + loggerExternal.exiting(getClassNameLogging(), "setGeometry"); + } public final void setInt(int n, int value) throws SQLServerException { diff --git a/src/main/java/microsoft/sql/Types.java b/src/main/java/microsoft/sql/Types.java index 9f510c2ec..ecd7eddca 100644 --- a/src/main/java/microsoft/sql/Types.java +++ b/src/main/java/microsoft/sql/Types.java @@ -57,4 +57,6 @@ private Types() { * The constant in the Java programming language, sometimes referred to as a type code, that identifies the Microsoft SQL type SQL_VARIANT. */ public static final int SQL_VARIANT = -156; + + public static final int GEOMETRY = -157; } From 7972b9dd923bdd2c9af70d13be743fbb718515da Mon Sep 17 00:00:00 2001 From: Peter Bae Date: Thu, 5 Oct 2017 10:25:50 -0700 Subject: [PATCH 03/41] Spatial datatypes parse changes --- .../com/microsoft/sqlserver/jdbc/DDC.java | 2 + .../microsoft/sqlserver/jdbc/DataTypes.java | 6 +- .../microsoft/sqlserver/jdbc/Geometry.java | 420 +++++++++++++++++- .../microsoft/sqlserver/jdbc/Parameter.java | 4 + .../jdbc/SQLServerPreparedStatement.java | 2 +- .../sqlserver/jdbc/SQLServerResultSet.java | 16 + .../com/microsoft/sqlserver/jdbc/dtv.java | 3 + 7 files changed, 433 insertions(+), 20 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/DDC.java b/src/main/java/com/microsoft/sqlserver/jdbc/DDC.java index 6bf3af9e6..da88d5a3a 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/DDC.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/DDC.java @@ -614,6 +614,8 @@ static final Object convertStreamToObject(BaseInputStream stream, byte[] byteValue = stream.getBytes(); if (JDBCType.GUID == jdbcType) { return Util.readGUID(byteValue); + } else if (JDBCType.GEOMETRY == jdbcType) { + return new Geometry(byteValue); } else { String hexString = Util.bytesToHexString(byteValue, byteValue.length); diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/DataTypes.java b/src/main/java/com/microsoft/sqlserver/jdbc/DataTypes.java index 7eebec85f..6f4f54abb 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/DataTypes.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/DataTypes.java @@ -148,7 +148,8 @@ enum SSType SQL_VARIANT (Category.SQL_VARIANT, "sql_variant", JDBCType.SQL_VARIANT), UDT (Category.UDT, "udt", JDBCType.VARBINARY), XML (Category.XML, "xml", JDBCType.LONGNVARCHAR), - TIMESTAMP (Category.TIMESTAMP, "timestamp", JDBCType.BINARY); + TIMESTAMP (Category.TIMESTAMP, "timestamp", JDBCType.BINARY), + GEOMETRY (Category.UDT, "geometry", JDBCType.GEOMETRY); final Category category; private final String name; @@ -352,7 +353,8 @@ enum GetterConversion EnumSet.of( JDBCType.Category.BINARY, JDBCType.Category.LONG_BINARY, - JDBCType.Category.CHARACTER)), + JDBCType.Category.CHARACTER, + JDBCType.Category.GEOMETRY)), GUID ( SSType.Category.GUID, diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java b/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java index fe1bfeff5..f8950b727 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java @@ -21,19 +21,24 @@ public class Geometry { private Figure figures[]; private Shape shapes[]; private Segment segments[]; + private StringBuffer WKTsb; + private int pointNumStart = 0; + private int segmentNumStart = 0; + private int shapeNumStart = 0; //serialization properties private boolean hasZvalues; private boolean hasMvalues; - private boolean isValid ; + private boolean isValid; private boolean isSinglePoint; private boolean isSingleLineSegment; - private final byte hasZvaluesMask = 0b00000001; // 1 - private final byte hasMvaluesMask = 0b00000010; // 2 - private final byte isValidMask = 0b00000100; // 4 - private final byte isSinglePointMask = 0b00001000; // 8 - private final byte isSingleLineSegmentMask = 0b00010000; // 16 + private final byte hasZvaluesMask = 0b00000001; + private final byte hasMvaluesMask = 0b00000010; + private final byte isValidMask = 0b00000100; + private final byte isSinglePointMask = 0b00001000; + private final byte isSingleLineSegmentMask = 0b00010000; + private final byte isLargerThanHemisphere = 0b00100000; public Geometry(String WellKnownText, int srid) { @@ -46,6 +51,12 @@ public Geometry(byte[] hexData) { buffer.order(ByteOrder.LITTLE_ENDIAN); parseHexData(); + + WKTsb = new StringBuffer(); + + constructWKT(internalType, numberOfPoints); + + WKT = WKTsb.toString(); } public InternalSpatialDatatype getInternalType() { @@ -71,25 +82,35 @@ private void parseHexData() { readPoints(); - readZvalues(); - - readMvalues(); - - readNumberOfFigures(); - - readFigures(); + if (hasZvalues) { + readZvalues(); + } - readNumberOfShapes(); + if (hasMvalues) { + readMvalues(); + } - readShapes(); + if (isSinglePoint || isSingleLineSegment) { + + } else { + readNumberOfFigures(); + + readFigures(); + + readNumberOfShapes(); + + readShapes(); + } - if (version == 2) { + determineInternalType(); + + if (version == 2 && internalType.getTypeCode() != 8) { readNumberOfSegments(); readSegments(); } } - + private void interpretSerializationPropBytes() { hasZvalues = (serializationProperties & hasZvaluesMask) != 0; hasMvalues = (serializationProperties & hasMvaluesMask) != 0; @@ -137,6 +158,7 @@ private void readNumberOfFigures() { private void readFigures() { byte fa; int po; + figures = new Figure[numberOfFigures]; for (int i = 0; i < numberOfFigures; i++) { fa = buffer.get(); po = buffer.getInt(); @@ -152,6 +174,7 @@ private void readShapes() { int po; int fo; byte ogt; + shapes = new Shape[numberOfShapes]; for (int i = 0; i < numberOfShapes; i++) { po = buffer.getInt(); fo = buffer.getInt(); @@ -166,11 +189,374 @@ private void readNumberOfSegments() { private void readSegments() { byte st; + segments = new Segment[numberOfSegments]; for (int i = 0; i < numberOfSegments; i++) { st = buffer.get(); segments[i] = new Segment(st); } } + + private void determineInternalType() { + if (isSinglePoint) { + internalType = InternalSpatialDatatype.POINT; + } else if (isSingleLineSegment) { + internalType = InternalSpatialDatatype.LINESTRING; + } else { + internalType = InternalSpatialDatatype.valueOf(shapes[0].getOpenGISType()); + } + } + + private void constructWKT(InternalSpatialDatatype it, int pointNumEnd) { + //Might have to divide into Simple or Compound types, instead of type by type + //Refer to the PDF for simple vs compound + WKTsb.append(it.getTypeName()); + + if (null == points || numberOfPoints == 0) { + WKT = internalType + " EMPTY"; + return; + } + + WKTsb.append("("); + + switch (it) { + case POINT: + constructPointWKT(pointNumStart); + break; + case LINESTRING: + case CIRCULARSTRING: + constructLineWKT(pointNumStart, pointNumEnd); + break; + case POLYGON: + case MULTIPOINT: + case MULTILINESTRING: + constructSimpleWKT(0); + break; + case COMPOUNDCURVE: + constructCompoundcurveWKT(segmentNumStart); + break; + case MULTIPOLYGON: + constructMultipolygonWKT(); + break; + case GEOMETRYCOLLECTION: + constructGeometryCollectionWKT(); + break; + case CURVEPOLYGON: + constructCurvepolygonWKT(); + break; + case FULLGLOBE: + WKTsb.append("FULLGLOBE"); + break; + default: + break; + } + + WKTsb.append(")"); + } + + private void constructPointWKT(int pointNum) { + int firstPointIndex = pointNum * 2; + int secondPointIndex = firstPointIndex + 1; + int zValueIndex = pointNum; + int mValueIndex = pointNum; + + + WKTsb.append(points[firstPointIndex]); + WKTsb.append(" "); + + WKTsb.append(points[secondPointIndex]); + WKTsb.append(" "); + + if (hasZvalues && !Double.isNaN(zValues[zValueIndex])) { + WKTsb.append(zValues[zValueIndex]); + WKTsb.append(" "); + + if (hasMvalues && !Double.isNaN(mValues[mValueIndex])) { + WKTsb.append(mValues[mValueIndex]); + WKTsb.append(" "); + } + } + + pointNumStart++; + WKTsb.setLength(WKTsb.length() - 1); // truncate last space + } + + private void constructLineWKT(int startIndex, int endIndex) { + for (int i = startIndex; i < endIndex; i++) { + constructPointWKT(i); + + // add ', ' to separate points, except for the last point + if (i != endIndex - 1) { + WKTsb.append(", "); + } + } + } + + + private void constructSimpleWKT(int startIndex) { + // Method for constructing Simple (i.e. not constructed of other Geometry/Geography objects) + // Geometry/Geography objects. + for (int i = startIndex; i < figures.length; i++) { + WKTsb.append("("); + if (i != figures.length - 1) { //not the last figure + constructLineWKT(figures[i].getPointOffset(), figures[i + 1].getPointOffset()); + } else { + constructLineWKT(figures[i].getPointOffset(), numberOfPoints); + } + + if (i != figures.length - 1) { + WKTsb.append("), "); + } else { + WKTsb.append(")"); + } + } + } + + private void constructCompoundcurveWKT(int startIndex) { + for (int i = startIndex; i < segments.length; i++) { + byte segment = segments[i].getSegmentType(); + constructSegmentWKT(i, segment, 0); + + if (i == segments.length - 1) { + WKTsb.append(")"); + break; + } + + switch (segment) { + case 0: + case 2: + if (segments[i + 1].getSegmentType() != 0) { + WKTsb.append("), "); + } + break; + case 1: + case 3: + if (segments[i + 1].getSegmentType() != 1) { + WKTsb.append("), "); + } + break; + default: + return; + } + } + } + + private void constructSegmentWKT(int currentSegment, byte segment, int pointNumEnd) { + switch (segment) { + case 0: + WKTsb.append(", "); + constructLineWKT(pointNumStart, pointNumStart + 1); + + if (currentSegment == segments.length - 1) { // last segment + break; + } else if (segments[currentSegment + 1].getSegmentType() != 0) { // not being followed by another line, but not the last segment + pointNumStart = pointNumStart - 1; + incrementPointNumStartIfPointNotReused(pointNumEnd); + } + break; + case 1: + WKTsb.append(", "); + constructLineWKT(pointNumStart, pointNumStart + 2); + + if (currentSegment == segments.length - 1) { // last segment + break; + } else if (segments[currentSegment + 1].getSegmentType() != 1) { // not being followed by another arc, but not the last segment + pointNumStart = pointNumStart - 1; // only increment pointNumStart by one less than what we should be, since the last point will be reused + incrementPointNumStartIfPointNotReused(pointNumEnd); + } + + break; + case 2: + WKTsb.append("("); + constructLineWKT(pointNumStart, pointNumStart + 2); + + if (currentSegment == segments.length - 1) { // last segment + break; + } else if (segments[currentSegment + 1].getSegmentType() != 0) { // not being followed by another line, but not the last segment + pointNumStart = pointNumStart - 1; // only increment pointNumStart by one less than what we should be, since the last point will be reused + incrementPointNumStartIfPointNotReused(pointNumEnd); + } + + break; + case 3: + WKTsb.append("CIRCULARSTRING("); + constructLineWKT(pointNumStart, pointNumStart + 3); + + if (currentSegment == segments.length - 1) { // last segment + break; + } else if (segments[currentSegment + 1].getSegmentType() != 1) { // not being followed by another arc + pointNumStart = pointNumStart - 1; // only increment pointNumStart by one less than what we should be, since the last point will be reused + incrementPointNumStartIfPointNotReused(pointNumEnd); + } + + break; + default: + return; + } + } + + private void incrementPointNumStartIfPointNotReused(int pointNumEnd) { + // We need to increment PointNumStart if this constructSegmentWKT was called from a CurvePolygon, and the last point was actually not re-used in the + // points array. + // 0 for pointNumEnd indicates that this check is not applicable. + if (0 != pointNumEnd && (pointNumStart + 1 >= pointNumEnd)) { + pointNumStart++; + } + } + + private void constructMultipolygonWKT() { + for (int i = 0; i < figures.length; i++) { + if (figures[i].getFiguresAttribute() == 2) { // exterior ring + WKTsb.append("(("); + } else { // interior ring + WKTsb.append("("); + } + + if (i == figures.length - 1) { // last polygon + constructLineWKT(figures[i].getPointOffset(), numberOfPoints); + } else { + constructLineWKT(figures[i].getPointOffset(), figures[i + 1].getPointOffset()); + } + + if (i == figures.length - 1) { // last polygon, close off the Multipolygon and return + WKTsb.append("))"); + return; + } else if (figures[i + 1].getFiguresAttribute() == 2) { // not the last polygon, followed by an exterior ring + WKTsb.append(")), "); + } else { // not the last polygon, followed by an interior ring + WKTsb.append("), "); + } + } + } + + private void constructCurvepolygonWKT() { + int currentSegment = 0; + + for (int i = 0; i < figures.length; i++) { + switch (figures[i].getFiguresAttribute()) { + case 1: // line + WKTsb.append("("); + + if (i == figures.length - 1) { + constructLineWKT(pointNumStart, numberOfPoints); + } else { + constructLineWKT(pointNumStart, figures[i + 1].getPointOffset()); + pointNumStart = figures[i + 1].getPointOffset(); + } + + WKTsb.append(")"); + break; + case 2: // arc + WKTsb.append("CIRCULARSTRING("); + + if (i == figures.length - 1) { + constructLineWKT(pointNumStart, numberOfPoints); + } else { + constructLineWKT(pointNumStart, figures[i + 1].getPointOffset()); + pointNumStart = figures[i + 1].getPointOffset(); + } + + WKTsb.append(")"); + + break; + case 3: // composite curve + WKTsb.append("COMPOUNDCURVE("); + + int pointNumEnd = 0; + + if (i == figures.length - 1) { + pointNumEnd = numberOfPoints; + } else { + pointNumEnd = figures[i + 1].getPointOffset(); + } + + while (pointNumStart < pointNumEnd) { + byte segment = segments[currentSegment].getSegmentType(); + constructSegmentWKT(currentSegment, segment, pointNumEnd); + + if (currentSegment == segments.length - 1) { + WKTsb.append(")"); + // about to exit while loop, but not the last segment = we are closing Compoundcurve. + } else if (!(pointNumStart < pointNumEnd)) { + WKTsb.append("))"); + } else { + switch (segment) { + case 0: + case 2: + if (segments[currentSegment + 1].getSegmentType() != 0) { + WKTsb.append("), "); + } + break; + case 1: + case 3: + if (segments[currentSegment + 1].getSegmentType() != 1) { + WKTsb.append("), "); + } + break; + default: + return; + } + } + + currentSegment++; + } + + break; + default: + return; + } + + if (i == figures.length - 1) { + WKTsb.append(")"); + } else { + WKTsb.append(", "); + } + + } + } + + private void constructGeometryCollectionWKT() { + while (shapeNumStart < shapes.length) { + byte openGISType = shapes[shapeNumStart].getOpenGISType(); + + if (openGISType == 7) { + // Another GeometryCollection inside another. + shapeNumStart++; + constructGeometryCollectionWKT(); + return; + } else { + if (shapeNumStart == shapes.length - 1) { + constructWKT(InternalSpatialDatatype.valueOf(openGISType), numberOfPoints); + WKTsb.append(")"); + } else { + int figureIndex = shapes[shapeNumStart].getFigureOffset(); + if (figureIndex == figures.length - 1) { + constructWKT(InternalSpatialDatatype.valueOf(openGISType), numberOfPoints); + } else { + constructWKT(InternalSpatialDatatype.valueOf(openGISType), figures[figureIndex + 1].getPointOffset()); + } + WKTsb.append(", "); + } + } + + shapeNumStart++; + } + + /* + for (int i = shapeNumStart; i < shapes.length; i++) { + if (shapes[i].getOpenGISType() == 7) { + + } else { + constructWKT(InternalSpatialDatatype.valueOf(shapes[i].getOpenGISType())); + } + + if (i == figures.length - 1) { + WKTsb.append(")"); + } else { + WKTsb.append(", "); + } + } + */ + } } class Figure { diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/Parameter.java b/src/main/java/com/microsoft/sqlserver/jdbc/Parameter.java index 86edb5d8a..65ed72f09 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/Parameter.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/Parameter.java @@ -885,6 +885,10 @@ else if ((null != jdbcTypeSetByUser) && ((jdbcTypeSetByUser == JDBCType.NVARCHAR case SQL_VARIANT: param.typeDefinition = SSType.SQL_VARIANT.toString(); break; + + case GEOMETRY: + param.typeDefinition = SSType.GEOMETRY.toString(); + break; default: assert false : "Unexpected JDBC type " + dtv.getJdbcType(); break; diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java index dc940f87b..b7451bd89 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java @@ -1572,7 +1572,7 @@ public final void setGeometry(int n, if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setGeometry", new Object[] {n, x}); checkClosed(); - setValue(n, JDBCType.REAL, x, JavaType.FLOAT, false); + setValue(n, JDBCType.GEOMETRY, x, JavaType.STRING, false); loggerExternal.exiting(getClassNameLogging(), "setGeometry"); } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java index e937b27c3..c5ce92075 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java @@ -2126,6 +2126,22 @@ public float getFloat(String columnName) throws SQLServerException { loggerExternal.exiting(getClassNameLogging(), "getFloat", value); return null != value ? value : 0; } + + public Geometry getGeometry(int columnIndex) throws SQLServerException { + loggerExternal.entering(getClassNameLogging(), "getFloat", columnIndex); + checkClosed(); + Geometry value = (Geometry) getValue(columnIndex, JDBCType.GEOMETRY); + loggerExternal.exiting(getClassNameLogging(), "getFloat", value); + return value; + } + + public Geometry getGeometry(String columnName) throws SQLServerException { + loggerExternal.entering(getClassNameLogging(), "getFloat", columnName); + checkClosed(); + Geometry value = (Geometry) getValue(findColumn(columnName), JDBCType.GEOMETRY); + loggerExternal.exiting(getClassNameLogging(), "getFloat", value); + return value; + } public int getInt(int columnIndex) throws SQLServerException { loggerExternal.entering(getClassNameLogging(), "getInt", columnIndex); diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/dtv.java b/src/main/java/com/microsoft/sqlserver/jdbc/dtv.java index e0ea30378..a4e881ea6 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/dtv.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/dtv.java @@ -1631,6 +1631,9 @@ final void executeOp(DTVExecuteOp op) throws SQLServerException { else if (JDBCType.SQL_VARIANT == jdbcType) { op.execute(this, String.valueOf(value)); } + else if (JDBCType.GEOMETRY == jdbcType) { + op.execute(this, value.toString()); + } else { if (null != cryptoMeta) { // if streaming types check for allowed data length in AE From 513c04dba83b87f4b4390b19ab37ecef3c35e65f Mon Sep 17 00:00:00 2001 From: Peter Bae Date: Fri, 13 Oct 2017 15:35:12 -0700 Subject: [PATCH 04/41] Parse logic to WKT completed - draft --- .../microsoft/sqlserver/jdbc/Geometry.java | 385 ++++++++++++------ 1 file changed, 270 insertions(+), 115 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java b/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java index f8950b727..73a696170 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java @@ -22,9 +22,10 @@ public class Geometry { private Shape shapes[]; private Segment segments[]; private StringBuffer WKTsb; - private int pointNumStart = 0; - private int segmentNumStart = 0; - private int shapeNumStart = 0; + private int currentPointIndex = 0; + private int currentFigureIndex = 0; + private int currentSegmentIndex = 0; + private int currentShapeIndex = 0; //serialization properties private boolean hasZvalues; @@ -54,7 +55,7 @@ public Geometry(byte[] hexData) { WKTsb = new StringBuffer(); - constructWKT(internalType, numberOfPoints); + constructWKT(internalType, numberOfPoints, numberOfFigures, numberOfSegments, numberOfShapes); WKT = WKTsb.toString(); } @@ -206,46 +207,43 @@ private void determineInternalType() { } } - private void constructWKT(InternalSpatialDatatype it, int pointNumEnd) { - //Might have to divide into Simple or Compound types, instead of type by type - //Refer to the PDF for simple vs compound - WKTsb.append(it.getTypeName()); - + private void constructWKT(InternalSpatialDatatype isd, int pointIndexEnd, int figureIndexEnd, int segmentIndexEnd, int shapeIndexEnd) { if (null == points || numberOfPoints == 0) { WKT = internalType + " EMPTY"; return; } + WKTsb.append(isd.getTypeName()); WKTsb.append("("); - - switch (it) { + + switch (isd) { case POINT: - constructPointWKT(pointNumStart); + constructPointWKT(currentPointIndex); break; case LINESTRING: case CIRCULARSTRING: - constructLineWKT(pointNumStart, pointNumEnd); + constructLineWKT(currentPointIndex, pointIndexEnd); break; case POLYGON: case MULTIPOINT: case MULTILINESTRING: - constructSimpleWKT(0); + constructSimpleWKT(currentFigureIndex, figureIndexEnd); break; case COMPOUNDCURVE: - constructCompoundcurveWKT(segmentNumStart); + constructCompoundcurveWKT(currentSegmentIndex, segmentIndexEnd, pointIndexEnd); break; case MULTIPOLYGON: - constructMultipolygonWKT(); + constructMultipolygonWKT(currentFigureIndex, figureIndexEnd); break; case GEOMETRYCOLLECTION: - constructGeometryCollectionWKT(); + constructGeometryCollectionWKT(shapeIndexEnd); break; case CURVEPOLYGON: - constructCurvepolygonWKT(); + constructCurvepolygonWKT(currentFigureIndex, figureIndexEnd, currentSegmentIndex, segmentIndexEnd); break; case FULLGLOBE: - WKTsb.append("FULLGLOBE"); - break; + //TODO: return error + return; default: break; } @@ -253,11 +251,11 @@ private void constructWKT(InternalSpatialDatatype it, int pointNumEnd) { WKTsb.append(")"); } - private void constructPointWKT(int pointNum) { - int firstPointIndex = pointNum * 2; + private void constructPointWKT(int pointIndex) { + int firstPointIndex = pointIndex * 2; int secondPointIndex = firstPointIndex + 1; - int zValueIndex = pointNum; - int mValueIndex = pointNum; + int zValueIndex = pointIndex; + int mValueIndex = pointIndex; WKTsb.append(points[firstPointIndex]); @@ -276,34 +274,33 @@ private void constructPointWKT(int pointNum) { } } - pointNumStart++; + currentPointIndex++; WKTsb.setLength(WKTsb.length() - 1); // truncate last space } - private void constructLineWKT(int startIndex, int endIndex) { - for (int i = startIndex; i < endIndex; i++) { + private void constructLineWKT(int pointStartIndex, int pointEndIndex) { + for (int i = pointStartIndex; i < pointEndIndex; i++) { constructPointWKT(i); // add ', ' to separate points, except for the last point - if (i != endIndex - 1) { + if (i != pointEndIndex - 1) { WKTsb.append(", "); } } } - - private void constructSimpleWKT(int startIndex) { + private void constructSimpleWKT(int figureStartIndex, int figureEndIndex) { // Method for constructing Simple (i.e. not constructed of other Geometry/Geography objects) // Geometry/Geography objects. - for (int i = startIndex; i < figures.length; i++) { + for (int i = figureStartIndex; i < figureEndIndex; i++) { WKTsb.append("("); - if (i != figures.length - 1) { //not the last figure + if (i != numberOfFigures - 1) { //not the last figure constructLineWKT(figures[i].getPointOffset(), figures[i + 1].getPointOffset()); } else { constructLineWKT(figures[i].getPointOffset(), numberOfPoints); } - if (i != figures.length - 1) { + if (i != figureEndIndex - 1) { WKTsb.append("), "); } else { WKTsb.append(")"); @@ -311,12 +308,12 @@ private void constructSimpleWKT(int startIndex) { } } - private void constructCompoundcurveWKT(int startIndex) { - for (int i = startIndex; i < segments.length; i++) { + private void constructCompoundcurveWKT(int segmentStartIndex, int segmentEndIndex, int pointEndIndex) { + for (int i = segmentStartIndex; i < segmentEndIndex; i++) { byte segment = segments[i].getSegmentType(); - constructSegmentWKT(i, segment, 0); + constructSegmentWKT(i, segment, pointEndIndex); - if (i == segments.length - 1) { + if (i == segmentEndIndex - 1) { WKTsb.append(")"); break; } @@ -340,52 +337,53 @@ private void constructCompoundcurveWKT(int startIndex) { } } - private void constructSegmentWKT(int currentSegment, byte segment, int pointNumEnd) { + private void constructSegmentWKT(int currentSegment, byte segment, int pointEndIndex) { switch (segment) { case 0: WKTsb.append(", "); - constructLineWKT(pointNumStart, pointNumStart + 1); + constructLineWKT(currentPointIndex, currentPointIndex + 1); if (currentSegment == segments.length - 1) { // last segment break; } else if (segments[currentSegment + 1].getSegmentType() != 0) { // not being followed by another line, but not the last segment - pointNumStart = pointNumStart - 1; - incrementPointNumStartIfPointNotReused(pointNumEnd); + currentPointIndex = currentPointIndex - 1; + incrementPointNumStartIfPointNotReused(pointEndIndex); } break; + case 1: WKTsb.append(", "); - constructLineWKT(pointNumStart, pointNumStart + 2); + constructLineWKT(currentPointIndex, currentPointIndex + 2); if (currentSegment == segments.length - 1) { // last segment break; } else if (segments[currentSegment + 1].getSegmentType() != 1) { // not being followed by another arc, but not the last segment - pointNumStart = pointNumStart - 1; // only increment pointNumStart by one less than what we should be, since the last point will be reused - incrementPointNumStartIfPointNotReused(pointNumEnd); + currentPointIndex = currentPointIndex - 1; // only increment pointNumStart by one less than what we should be, since the last point will be reused + incrementPointNumStartIfPointNotReused(pointEndIndex); } break; case 2: WKTsb.append("("); - constructLineWKT(pointNumStart, pointNumStart + 2); + constructLineWKT(currentPointIndex, currentPointIndex + 2); if (currentSegment == segments.length - 1) { // last segment break; } else if (segments[currentSegment + 1].getSegmentType() != 0) { // not being followed by another line, but not the last segment - pointNumStart = pointNumStart - 1; // only increment pointNumStart by one less than what we should be, since the last point will be reused - incrementPointNumStartIfPointNotReused(pointNumEnd); + currentPointIndex = currentPointIndex - 1; // only increment pointNumStart by one less than what we should be, since the last point will be reused + incrementPointNumStartIfPointNotReused(pointEndIndex); } break; case 3: WKTsb.append("CIRCULARSTRING("); - constructLineWKT(pointNumStart, pointNumStart + 3); + constructLineWKT(currentPointIndex, currentPointIndex + 3); if (currentSegment == segments.length - 1) { // last segment break; } else if (segments[currentSegment + 1].getSegmentType() != 1) { // not being followed by another arc - pointNumStart = pointNumStart - 1; // only increment pointNumStart by one less than what we should be, since the last point will be reused - incrementPointNumStartIfPointNotReused(pointNumEnd); + currentPointIndex = currentPointIndex - 1; // only increment pointNumStart by one less than what we should be, since the last point will be reused + incrementPointNumStartIfPointNotReused(pointEndIndex); } break; @@ -394,30 +392,29 @@ private void constructSegmentWKT(int currentSegment, byte segment, int pointNumE } } - private void incrementPointNumStartIfPointNotReused(int pointNumEnd) { - // We need to increment PointNumStart if this constructSegmentWKT was called from a CurvePolygon, and the last point was actually not re-used in the - // points array. + 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. - if (0 != pointNumEnd && (pointNumStart + 1 >= pointNumEnd)) { - pointNumStart++; + if (currentPointIndex + 1 >= pointEndIndex) { + currentPointIndex++; } } - private void constructMultipolygonWKT() { - for (int i = 0; i < figures.length; i++) { + private void constructMultipolygonWKT(int figureStartIndex, int figureEndIndex) { + for (int i = figureStartIndex; i < figureEndIndex; i++) { if (figures[i].getFiguresAttribute() == 2) { // exterior ring WKTsb.append("(("); } else { // interior ring WKTsb.append("("); } - if (i == figures.length - 1) { // last polygon + if (i == figures.length - 1) { // last figure constructLineWKT(figures[i].getPointOffset(), numberOfPoints); } else { constructLineWKT(figures[i].getPointOffset(), figures[i + 1].getPointOffset()); } - if (i == figures.length - 1) { // last polygon, close off the Multipolygon and return + if (i == figureEndIndex - 1) { // last polygon of this multipolygon, close off the Multipolygon and return WKTsb.append("))"); return; } else if (figures[i + 1].getFiguresAttribute() == 2) { // not the last polygon, followed by an exterior ring @@ -428,19 +425,17 @@ private void constructMultipolygonWKT() { } } - private void constructCurvepolygonWKT() { - int currentSegment = 0; - - for (int i = 0; i < figures.length; i++) { + private void constructCurvepolygonWKT(int figureStartIndex, int figureEndIndex, int segmentStartIndex, int segmentEndIndex) { + for (int i = figureStartIndex; i < figureEndIndex; i++) { switch (figures[i].getFiguresAttribute()) { case 1: // line WKTsb.append("("); if (i == figures.length - 1) { - constructLineWKT(pointNumStart, numberOfPoints); + constructLineWKT(currentPointIndex, numberOfPoints); } else { - constructLineWKT(pointNumStart, figures[i + 1].getPointOffset()); - pointNumStart = figures[i + 1].getPointOffset(); + constructLineWKT(currentPointIndex, figures[i + 1].getPointOffset()); + //currentPointIndex = figures[i + 1].getPointOffset(); } WKTsb.append(")"); @@ -449,10 +444,10 @@ private void constructCurvepolygonWKT() { WKTsb.append("CIRCULARSTRING("); if (i == figures.length - 1) { - constructLineWKT(pointNumStart, numberOfPoints); + constructLineWKT(currentPointIndex, numberOfPoints); } else { - constructLineWKT(pointNumStart, figures[i + 1].getPointOffset()); - pointNumStart = figures[i + 1].getPointOffset(); + constructLineWKT(currentPointIndex, figures[i + 1].getPointOffset()); + //currentPointIndex = figures[i + 1].getPointOffset(); } WKTsb.append(")"); @@ -461,34 +456,34 @@ private void constructCurvepolygonWKT() { case 3: // composite curve WKTsb.append("COMPOUNDCURVE("); - int pointNumEnd = 0; + int pointEndIndex = 0; if (i == figures.length - 1) { - pointNumEnd = numberOfPoints; + pointEndIndex = numberOfPoints; } else { - pointNumEnd = figures[i + 1].getPointOffset(); + pointEndIndex = figures[i + 1].getPointOffset(); } - while (pointNumStart < pointNumEnd) { - byte segment = segments[currentSegment].getSegmentType(); - constructSegmentWKT(currentSegment, segment, pointNumEnd); + while (currentPointIndex < pointEndIndex) { + byte segment = segments[segmentStartIndex].getSegmentType(); + constructSegmentWKT(segmentStartIndex, segment, pointEndIndex); - if (currentSegment == segments.length - 1) { + if (segmentStartIndex >= segmentEndIndex - 1) { WKTsb.append(")"); - // about to exit while loop, but not the last segment = we are closing Compoundcurve. - } else if (!(pointNumStart < pointNumEnd)) { + // about to exit while loop, but not the last segment = we are closing Compoundcurve. + } else if (!(currentPointIndex < pointEndIndex)) { WKTsb.append("))"); } else { switch (segment) { case 0: case 2: - if (segments[currentSegment + 1].getSegmentType() != 0) { + if (segments[segmentStartIndex + 1].getSegmentType() != 0) { WKTsb.append("), "); } break; case 1: case 3: - if (segments[currentSegment + 1].getSegmentType() != 1) { + if (segments[segmentStartIndex + 1].getSegmentType() != 1) { WKTsb.append("), "); } break; @@ -497,7 +492,7 @@ private void constructCurvepolygonWKT() { } } - currentSegment++; + segmentStartIndex++; } break; @@ -505,7 +500,7 @@ private void constructCurvepolygonWKT() { return; } - if (i == figures.length - 1) { + if (i == figureEndIndex - 1) { WKTsb.append(")"); } else { WKTsb.append(", "); @@ -514,48 +509,208 @@ private void constructCurvepolygonWKT() { } } - private void constructGeometryCollectionWKT() { - while (shapeNumStart < shapes.length) { - byte openGISType = shapes[shapeNumStart].getOpenGISType(); + private void constructGeometryCollectionWKT(int shapeEndIndex) { + currentShapeIndex++; + constructGeometryCollectionWKThelper(shapeEndIndex); + } + + private void constructGeometryCollectionWKThelper(int shapeEndIndex) { + //phase 1: assume that there is no multi - stuff and no geometrycollection + while (currentShapeIndex < shapeEndIndex) { + InternalSpatialDatatype isd = InternalSpatialDatatype.valueOf(shapes[currentShapeIndex].getOpenGISType()); - if (openGISType == 7) { - // Another GeometryCollection inside another. - shapeNumStart++; - constructGeometryCollectionWKT(); - return; - } else { - if (shapeNumStart == shapes.length - 1) { - constructWKT(InternalSpatialDatatype.valueOf(openGISType), numberOfPoints); - WKTsb.append(")"); - } else { - int figureIndex = shapes[shapeNumStart].getFigureOffset(); - if (figureIndex == figures.length - 1) { - constructWKT(InternalSpatialDatatype.valueOf(openGISType), numberOfPoints); + int figureIndex = shapes[currentShapeIndex].getFigureOffset(); + int pointIndexEnd = numberOfPoints; + int figureIndexEnd = numberOfFigures; + int segmentIndexEnd = numberOfSegments; + int shapeIndexEnd = numberOfShapes; + int figureIndexIncrement = 0; + int segmentIndexIncrement = 0; + int localCurrentSegmentIndex = 0; + int localCurrentShapeIndex = 0; + + switch (isd) { + case POINT: + figureIndexIncrement++; + currentShapeIndex++; + break; + case LINESTRING: + case CIRCULARSTRING: + figureIndexIncrement++; + currentShapeIndex++; + pointIndexEnd = figures[figureIndex + 1].getPointOffset(); + break; + case POLYGON:; + case CURVEPOLYGON: + if (currentShapeIndex < shapes.length - 1) { + figureIndexEnd = shapes[currentShapeIndex + 1].getFigureOffset(); + } + + figureIndexIncrement = figureIndexEnd - currentFigureIndex; + currentShapeIndex++; + + // Needed to keep track of which segment we are at, inside the for loop + localCurrentSegmentIndex = currentSegmentIndex; + + if (isd.equals(InternalSpatialDatatype.CURVEPOLYGON)) { + // assume Version 2 + + for (int i = currentFigureIndex; i < figureIndexEnd; i++) { + // Only Compoundcurves (with figure attribute 3) can have segments + if (figures[i].getFiguresAttribute() == 3) { + + int pointOffsetEnd; + if (i == figures.length - 1) { + pointOffsetEnd = numberOfPoints; + } else { + pointOffsetEnd = figures[i + 1].getPointOffset(); + } + + int increment = calculateSegmentIncrement(localCurrentSegmentIndex, pointOffsetEnd - figures[i].getPointOffset()); + + segmentIndexIncrement = segmentIndexIncrement + increment; + localCurrentSegmentIndex = localCurrentSegmentIndex + increment; + } + } + } + + segmentIndexEnd = localCurrentSegmentIndex; + + break; + case MULTIPOINT: + case MULTILINESTRING: + case MULTIPOLYGON: + //Multipoint and MultiLineString can go on for multiple Shapes, but eventually + //the parentOffset will signal the end of the object, or it's reached the end of the + //shapes array. + //There is also no possibility that a MultiPoint or MultiLineString would branch + //into another parent. + + int thisShapesParentOffset = shapes[currentShapeIndex].getParentOffset(); + + // Increment shapeStartIndex to account for the shape index that either Multipoint, MultiLineString + // or MultiPolygon takes up + currentShapeIndex++; + while (currentShapeIndex < shapes.length - 1 && + shapes[currentShapeIndex].getParentOffset() != thisShapesParentOffset) { + figureIndexEnd = shapes[currentShapeIndex + 1].getFigureOffset(); + currentShapeIndex++; + } + + figureIndexIncrement = figureIndexEnd - currentFigureIndex; + break; + case GEOMETRYCOLLECTION: + WKTsb.append(isd.getTypeName()); + WKTsb.append("("); + + int geometryCollectionParentIndex = shapes[currentShapeIndex].getParentOffset(); + + // Needed to keep track of which shape we are at, inside the for loop + localCurrentShapeIndex = currentShapeIndex; + + + while (localCurrentShapeIndex < shapes.length - 1 && + shapes[localCurrentShapeIndex + 1].getParentOffset() > geometryCollectionParentIndex) { + localCurrentShapeIndex++; + } + // increment localCurrentShapeIndex one more time since it will be used as a shapeEndIndex parameter + // for constructGeometryCollectionWKT, and the shapeEndIndex parameter is used non-inclusively + localCurrentShapeIndex++; + + currentShapeIndex++; + constructGeometryCollectionWKThelper(localCurrentShapeIndex); + + if (currentShapeIndex < shapeEndIndex) { + WKTsb.append("), "); } else { - constructWKT(InternalSpatialDatatype.valueOf(openGISType), figures[figureIndex + 1].getPointOffset()); + WKTsb.append(")"); } - WKTsb.append(", "); - } + + continue; + case COMPOUNDCURVE: + if (currentFigureIndex == figures.length - 1) { + pointIndexEnd = numberOfPoints; + } else { + pointIndexEnd = figures[currentFigureIndex + 1].getPointOffset(); + } + + int increment = calculateSegmentIncrement(currentSegmentIndex, pointIndexEnd - + figures[currentFigureIndex].getPointOffset()); + + segmentIndexIncrement = increment; + segmentIndexEnd = currentSegmentIndex + increment; + figureIndexIncrement++; + currentShapeIndex++; + break; + case FULLGLOBE: + WKTsb.append("FULLGLOBE"); + break; + default: + break; } - shapeNumStart++; + constructWKT(isd, pointIndexEnd, figureIndexEnd, segmentIndexEnd, shapeIndexEnd); + currentFigureIndex = currentFigureIndex + figureIndexIncrement; + currentSegmentIndex = currentSegmentIndex + segmentIndexIncrement; + + if (currentShapeIndex < shapeEndIndex) { + WKTsb.append(", "); + } } + } + + //Calculates how many segments will be used by this CompoundCurve + private int calculateSegmentIncrement(int segmentStart, + int pointDifference) { - /* - for (int i = shapeNumStart; i < shapes.length; i++) { - if (shapes[i].getOpenGISType() == 7) { - - } else { - constructWKT(InternalSpatialDatatype.valueOf(shapes[i].getOpenGISType())); - } - - if (i == figures.length - 1) { - WKTsb.append(")"); - } else { - WKTsb.append(", "); + int segmentIncrement = 0; + + while (pointDifference > 0) { + switch (segments[segmentStart].getSegmentType()) { + case 0: + pointDifference = pointDifference - 1; + + if (segmentStart == segments.length - 1 || pointDifference < 1) { // last segment + break; + } else if (segments[segmentStart + 1].getSegmentType() != 0) { // one point will be reused + pointDifference = pointDifference + 1; + } + break; + case 1: + pointDifference = pointDifference - 2; + + if (segmentStart == segments.length - 1 || pointDifference < 1) { // last segment + break; + } else if (segments[segmentStart + 1].getSegmentType() != 1) { // one point will be reused + pointDifference = pointDifference + 1; + } + break; + case 2: + pointDifference = pointDifference - 2; + + if (segmentStart == segments.length - 1 || pointDifference < 1) { // last segment + break; + } else if (segments[segmentStart + 1].getSegmentType() != 0) { // one point will be reused + pointDifference = pointDifference + 1; + } + break; + case 3: + pointDifference = pointDifference - 3; + + if (segmentStart == segments.length - 1 || pointDifference < 1) { // last segment + break; + } else if (segments[segmentStart + 1].getSegmentType() != 1) { // one point will be reused + pointDifference = pointDifference + 1; + } + break; + default: + return segmentIncrement; } + segmentStart++; + segmentIncrement++; } - */ + + return segmentIncrement; } } From 3abc1164115504a6f501f9ace8a603e431869792 Mon Sep 17 00:00:00 2001 From: Peter Bae Date: Fri, 20 Oct 2017 10:24:30 -0700 Subject: [PATCH 05/41] Serialization implementation --- .../microsoft/sqlserver/jdbc/Geometry.java | 465 +++++++++++++++++- .../com/microsoft/sqlserver/jdbc/dtv.java | 2 +- 2 files changed, 440 insertions(+), 27 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java b/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java index 73a696170..34f73e6f2 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java @@ -1,15 +1,20 @@ package com.microsoft.sqlserver.jdbc; +import java.math.BigDecimal; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; public class Geometry { private ByteBuffer buffer; private InternalSpatialDatatype internalType; - private String WKT; + private String wkt; + private byte[] wkb; private int srid; - private byte version; + private byte version = 1; private byte serializationProperties; private int numberOfPoints; private int numberOfFigures; @@ -28,36 +33,52 @@ public class Geometry { private int currentShapeIndex = 0; //serialization properties - private boolean hasZvalues; - private boolean hasMvalues; - private boolean isValid; - private boolean isSinglePoint; - private boolean isSingleLineSegment; + private boolean hasZvalues = false; + private boolean hasMvalues = false; + //TODO: when is a geometry/geography not valid? + //Also, from the driver's point of view, should this ever be false? + private boolean isValid = true; + private boolean isSinglePoint = false; + private boolean isSingleLineSegment = false; + //TODO: how do i use this? + private boolean isLargerThanHemisphere = false; - private final byte hasZvaluesMask = 0b00000001; - private final byte hasMvaluesMask = 0b00000010; - private final byte isValidMask = 0b00000100; - private final byte isSinglePointMask = 0b00001000; - private final byte isSingleLineSegmentMask = 0b00010000; - private final byte isLargerThanHemisphere = 0b00100000; + private final byte hasZvaluesMask = 0b00000001; + private final byte hasMvaluesMask = 0b00000010; + private final byte isValidMask = 0b00000100; + private final byte isSinglePointMask = 0b00001000; + private final byte isSingleLineSegmentMask = 0b00010000; + private final byte isLargerThanHemisphereMask = 0b00100000; + // WKT to WKB properties + private int currentWktPos = 0; + private List pointList = new ArrayList(); + private List
figureList = new ArrayList
(); + private List shapeList = new ArrayList(); + private List segmentList = new ArrayList(); + public Geometry(String WellKnownText, int srid) { - this.WKT = WellKnownText; + this.wkt = WellKnownText; this.srid = srid; + + //TODO: do lazy conversion later + parseWKTForSerialization(currentWktPos, 0); + serializeToWkb(); } - - public Geometry(byte[] hexData) { - buffer = ByteBuffer.wrap(hexData); + + public Geometry(byte[] wkb) { + this.wkb = wkb; + buffer = ByteBuffer.wrap(wkb); buffer.order(ByteOrder.LITTLE_ENDIAN); - parseHexData(); + parseWkb(); WKTsb = new StringBuffer(); constructWKT(internalType, numberOfPoints, numberOfFigures, numberOfSegments, numberOfShapes); - WKT = WKTsb.toString(); + wkt = WKTsb.toString(); } public InternalSpatialDatatype getInternalType() { @@ -68,11 +89,145 @@ public int getSRID() { return srid; } + public byte[] getWkb() { + return wkb; + } + public String toString() { - return WKT; + return wkt; + } + + private void serializeToWkb() { + 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 (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) { + buf.putInt(numberOfSegments); + for (int i = 0; i < numberOfSegments; i++) { + buf.put(segments[i].getSegmentType()); + } + } + + wkb = buf.array(); + return; } - private void parseHexData() { + private void createSerializationProperties() { + serializationProperties = 0; + if (hasZvalues) { + serializationProperties += hasZvaluesMask; + } + + if (hasMvalues) { + serializationProperties += hasMvaluesMask; + } + + if (isValid) { + serializationProperties += isValidMask; + } + + if (isSinglePoint) { + serializationProperties += isSinglePointMask; + } + + if (isSingleLineSegment) { + serializationProperties += isSingleLineSegmentMask; + } + + //TODO look into how the isLargerThanHemisphere is created + if (version == 2) { + if (isLargerThanHemisphere) { + serializationProperties += isLargerThanHemisphereMask; + } + } + } + + private int determineWkbCapacity() { + int totalSize = 0; + + totalSize+=6; // SRID + version + SerializationPropertiesByte + + if (isSinglePoint || isSingleLineSegment) { + totalSize += 16 * numberOfPoints; + + if (hasZvalues) { + totalSize += 8 * numberOfPoints; + } + + if (hasMvalues) { + totalSize += 8 * numberOfPoints; + } + + return totalSize; + } + + int pointSize = 16; + if (hasZvalues) { + pointSize += 8; + } + + if (hasMvalues) { + pointSize += 8; + } + + totalSize += 12; // 4 bytes for 3 ints, each representing the number of points, shapes and figures + totalSize += numberOfPoints * pointSize; + totalSize += numberOfFigures * 5; + totalSize += numberOfShapes * 9; + + if (version == 2) { + totalSize += 4; // 4 bytes for 1 int, representing the number of segments + totalSize += numberOfSegments; + } + + return totalSize; + } + + private void parseWkb() { srid = buffer.getInt(); version = buffer.get(); serializationProperties = buffer.get(); @@ -91,9 +246,8 @@ private void parseHexData() { readMvalues(); } - if (isSinglePoint || isSingleLineSegment) { - - } else { + //TODO: do I need to do anything when it's isSinglePoint or isSingleLineSegment? + if (!(isSinglePoint || isSingleLineSegment)) { readNumberOfFigures(); readFigures(); @@ -209,7 +363,7 @@ private void determineInternalType() { private void constructWKT(InternalSpatialDatatype isd, int pointIndexEnd, int figureIndexEnd, int segmentIndexEnd, int shapeIndexEnd) { if (null == points || numberOfPoints == 0) { - WKT = internalType + " EMPTY"; + wkt = internalType + " EMPTY"; return; } @@ -540,7 +694,7 @@ private void constructGeometryCollectionWKThelper(int shapeEndIndex) { currentShapeIndex++; pointIndexEnd = figures[figureIndex + 1].getPointOffset(); break; - case POLYGON:; + case POLYGON: case CURVEPOLYGON: if (currentShapeIndex < shapes.length - 1) { figureIndexEnd = shapes[currentShapeIndex + 1].getFigureOffset(); @@ -712,6 +866,235 @@ private int calculateSegmentIncrement(int segmentStart, return segmentIncrement; } + + private void parseWKTForSerialization(int startPos, int parentShapeIndex) { + //after every iteration of this while loop, the currentWktPosition will be set to the + //end of the geometry/geography shape, except for the very first iteration of it. + //This means that there has to be comma (that separates the previous shape with the next shape), + //or we expect a ')' that will close the entire shape and exit the method. + + System.out.println("delete me"); + + while (hasMoreToken()) { + if (startPos != 0) { + if (wkt.charAt(currentWktPos) == ')') { + return; + } else if (wkt.charAt(currentWktPos) == ',') { + currentWktPos++; + } else { + //TODO: throw exception here + return; + } + } + + String nextToken = getNextStringToken().toUpperCase(Locale.US); + + readOpenBracket(); + + if (nextToken.equals("CIRCULARSTRING") || nextToken.equals("COMPOUNDCURVE") || + nextToken.equals("CURVEPOLYGON")) { + version = 2; + } else { + version = 1; + } + + switch (nextToken) { + case "POINT": + if (startPos == 0 && nextToken.toUpperCase().equals("POINT")) { + isSinglePoint = true; + } + readPointWkt(); + break; + case "LINESTRING": + case "CIRCULARSTRING": + readLineWkt(parentShapeIndex); + + if (startPos == 0 && nextToken.toUpperCase().equals("LINESTRING") && pointList.size() == 2) { + isSingleLineSegment = true; + } + break; + case "POLYGON": + case "CURVEPOLYGON": + + break; + case "MULTIPOINT": + case "MULTILINESTRING": + case "MULTIPOLYGON": + + break; + case "GEOMETRYCOLLECTION": + //Geometrycollection i believe has to use return, in order to know the parent shape index. + continue; + case "COMPOUNDCURVE": + + break; + case "FULLGLOBE": + + break; + default: + break; + } + //all geometry methods return when the depth reaches 0. ( gives + 1 depth and ) takes away 1 depth. + readCloseBracket(); + } + + populateStructures(); + } + + private void readPointWkt() { + int numOfCoordinates = 0; + double sign; + double coords[] = new double[4]; + + while (numOfCoordinates < 4) { + sign = 1; + if (wkt.charAt(currentWktPos) == '-') { + sign = -1; + currentWktPos++; + } + + int startPos = currentWktPos; + + if (wkt.charAt(currentWktPos) == ')') { + break; + } + + while (currentWktPos < wkt.length() && + !(wkt.charAt(currentWktPos) == ',' || wkt.charAt(currentWktPos) == ' ')) { + currentWktPos++; + } + + try { + coords[numOfCoordinates] = sign * + new BigDecimal(wkt.substring(startPos, currentWktPos)).doubleValue(); + } catch (Exception e) { //modify to conversion exception + throw new IllegalArgumentException(); + } + + skipWhiteSpaces(); + if (wkt.charAt(currentWktPos) == ',') { + currentWktPos++; + skipWhiteSpaces(); + break; + } + skipWhiteSpaces(); + + numOfCoordinates++; + } + + if (numOfCoordinates == 4) { + hasZvalues = true; + hasMvalues = true; + } else if (numOfCoordinates == 3) { + hasMvalues = true; + } + + pointList.add(new Point(coords[0], coords[1], coords[2], coords[3])); + } + + private void readLineWkt(int parentShapeOffset) { + shapeList.add(new Shape(parentShapeOffset - 1, figureList.size(), InternalSpatialDatatype.LINESTRING.getTypeCode())); + figureList.add(new Figure((byte) 1, pointList.size())); + while (currentWktPos < wkt.length() && wkt.charAt(currentWktPos) != ')') { + readPointWkt(); + } + } + + private void readOpenBracket() { + if (wkt.charAt(currentWktPos) == '(') { + currentWktPos++; + skipWhiteSpaces(); + } else { + throw new IllegalArgumentException(); + } + } + + private void readCloseBracket() { + skipWhiteSpaces(); + if (wkt.charAt(currentWktPos) == ')') { + currentWktPos++; + } else { + throw new IllegalArgumentException(); + } + } + + private boolean hasMoreToken() { + skipWhiteSpaces(); + return currentWktPos < wkt.length(); + } + + private void skipWhiteSpaces() { + while (currentWktPos < wkt.length() && Character.isWhitespace(wkt.charAt(currentWktPos))) { + currentWktPos++; + } + } + + private String getNextStringToken() { + skipWhiteSpaces(); + int endIndex = currentWktPos; + while (endIndex < wkt.length() && Character.isLetter(wkt.charAt(endIndex))) { + endIndex++; + } + int temp = currentWktPos; + currentWktPos = endIndex; + skipWhiteSpaces(); + + return wkt.substring(temp, endIndex); + } + + private void populateStructures() { + if (pointList.size() > 0) { + points = new double[pointList.size() * 2]; + + for (int i = 0; i < pointList.size(); i++) { + points[i * 2] = pointList.get(i).getX(); + points[i * 2 + 1] = pointList.get(i).getY(); + } + + if (hasZvalues) { + zValues = new double[pointList.size()]; + for (int i = 0; i < pointList.size(); i++) { + zValues[i] = pointList.get(i).getZ(); + } + } + + if (hasMvalues) { + mValues = new double[pointList.size()]; + for (int i = 0; i < pointList.size(); i++) { + mValues[i] = pointList.get(i).getM(); + } + } + } + + if (figureList.size() > 0) { + figures = new Figure[figureList.size()]; + + for (int i = 0; i < figureList.size(); i++) { + figures[i] = figureList.get(i); + } + } + + if (shapeList.size() > 0) { + shapes = new Shape[shapeList.size()]; + + for (int i = 0; i < shapeList.size(); i++) { + shapes[i] = shapeList.get(i); + } + } + + if (segmentList.size() > 0) { + segments = new Segment[segmentList.size()]; + + for (int i = 0; i < segmentList.size(); i++) { + segments[i] = segmentList.get(i); + } + } + + numberOfPoints = pointList.size(); + numberOfFigures = figureList.size(); + numberOfShapes = shapeList.size(); + numberOfSegments = segmentList.size(); + } } class Figure { @@ -766,4 +1149,34 @@ class Segment { public byte getSegmentType() { return segmentType; } +} + +class Point { + private final double x; + private final double y; + private final double z; + private final double m; + + Point(double x, double y, double z, double m) { + this.x = x; + this.y = y; + this.z = z; + this.m = m; + } + + public double getX() { + return x; + } + + public double getY() { + return y; + } + + public double getZ() { + return z; + } + + public double getM() { + return m; + } } \ No newline at end of file diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/dtv.java b/src/main/java/com/microsoft/sqlserver/jdbc/dtv.java index a4e881ea6..447ec0549 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/dtv.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/dtv.java @@ -1632,7 +1632,7 @@ else if (JDBCType.SQL_VARIANT == jdbcType) { op.execute(this, String.valueOf(value)); } else if (JDBCType.GEOMETRY == jdbcType) { - op.execute(this, value.toString()); + op.execute(this, ((Geometry) value).getWkb()); } else { if (null != cryptoMeta) { From 40fe6eecd81b23a4b8c13bc124b81101cb61716a Mon Sep 17 00:00:00 2001 From: Peter Bae Date: Tue, 24 Oct 2017 14:58:17 -0700 Subject: [PATCH 06/41] more serialization logic --- .../microsoft/sqlserver/jdbc/Geometry.java | 303 ++++++++++++++++-- 1 file changed, 273 insertions(+), 30 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java b/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java index 34f73e6f2..59969ac41 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java @@ -37,12 +37,26 @@ public class Geometry { private boolean hasMvalues = false; //TODO: when is a geometry/geography not valid? //Also, from the driver's point of view, should this ever be false? - private boolean isValid = true; + private boolean isValid = false; private boolean isSinglePoint = false; private boolean isSingleLineSegment = false; //TODO: how do i use this? private boolean isLargerThanHemisphere = false; + private final byte FA_INTERIOR_RING = 0; + private final byte FA_STROKE = 1; + private final byte FA_EXTERIOR_RING = 2; + + private final byte FA_POINT = 0; + private final byte FA_LINE = 1; + private final byte FA_ARC = 2; + private final byte FA_COMPOSITE_CURVE = 3; + + private final byte SEGMENT_LINE = 0; + private final byte SEGMENT_ARC = 1; + private final byte SEGMENT_FIRST_LINE = 2; + private final byte SEGMENT_FIRST_ARC = 3; + private final byte hasZvaluesMask = 0b00000001; private final byte hasMvaluesMask = 0b00000010; private final byte isValidMask = 0b00000100; @@ -57,13 +71,15 @@ public class Geometry { private List
figureList = new ArrayList
(); private List shapeList = new ArrayList(); private List segmentList = new ArrayList(); + + private List version_one_shape_indexes = new ArrayList(); public Geometry(String WellKnownText, int srid) { this.wkt = WellKnownText; this.srid = srid; //TODO: do lazy conversion later - parseWKTForSerialization(currentWktPos, 0); + parseWKTForSerialization(currentWktPos, -1, false); serializeToWkb(); } @@ -145,7 +161,7 @@ private void serializeToWkb() { buf.put(shapes[i].getOpenGISType()); } - if (version == 2) { + if (version == 2 && null != segments) { buf.putInt(numberOfSegments); for (int i = 0; i < numberOfSegments; i++) { buf.put(segments[i].getSegmentType()); @@ -381,7 +397,7 @@ private void constructWKT(InternalSpatialDatatype isd, int pointIndexEnd, int fi case POLYGON: case MULTIPOINT: case MULTILINESTRING: - constructSimpleWKT(currentFigureIndex, figureIndexEnd); + constructShapeWKT(currentFigureIndex, figureIndexEnd); break; case COMPOUNDCURVE: constructCompoundcurveWKT(currentSegmentIndex, segmentIndexEnd, pointIndexEnd); @@ -443,9 +459,8 @@ private void constructLineWKT(int pointStartIndex, int pointEndIndex) { } } - private void constructSimpleWKT(int figureStartIndex, int figureEndIndex) { - // Method for constructing Simple (i.e. not constructed of other Geometry/Geography objects) - // Geometry/Geography objects. + private void constructShapeWKT(int figureStartIndex, int figureEndIndex) { + // Method for constructing shapes (simple Geometry/Geography entities that are contained within a single bracket) for (int i = figureStartIndex; i < figureEndIndex; i++) { WKTsb.append("("); if (i != numberOfFigures - 1) { //not the last figure @@ -867,14 +882,12 @@ private int calculateSegmentIncrement(int segmentStart, return segmentIncrement; } - private void parseWKTForSerialization(int startPos, int parentShapeIndex) { + private void parseWKTForSerialization(int startPos, int parentShapeIndex, boolean isGeoCollection) { //after every iteration of this while loop, the currentWktPosition will be set to the //end of the geometry/geography shape, except for the very first iteration of it. //This means that there has to be comma (that separates the previous shape with the next shape), //or we expect a ')' that will close the entire shape and exit the method. - System.out.println("delete me"); - while (hasMoreToken()) { if (startPos != 0) { if (wkt.charAt(currentWktPos) == ')') { @@ -882,51 +895,125 @@ private void parseWKTForSerialization(int startPos, int parentShapeIndex) { } else if (wkt.charAt(currentWktPos) == ',') { currentWktPos++; } else { - //TODO: throw exception here - return; + //TODO: throw exception here? + //return; } } String nextToken = getNextStringToken().toUpperCase(Locale.US); + String nextPotentialToken; + int thisShapeIndex; + InternalSpatialDatatype isd = InternalSpatialDatatype.valueOf(nextToken); + byte fa = 0; readOpenBracket(); - if (nextToken.equals("CIRCULARSTRING") || nextToken.equals("COMPOUNDCURVE") || - nextToken.equals("CURVEPOLYGON")) { + if (version == 1 && (nextToken.equals("CIRCULARSTRING") || nextToken.equals("COMPOUNDCURVE") || + nextToken.equals("CURVEPOLYGON"))) { version = 2; - } else { - version = 1; } - + switch (nextToken) { case "POINT": if (startPos == 0 && nextToken.toUpperCase().equals("POINT")) { isSinglePoint = true; } + + if (isGeoCollection) { + shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); + figureList.add(new Figure(FA_LINE, pointList.size())); + } + readPointWkt(); break; case "LINESTRING": case "CIRCULARSTRING": - readLineWkt(parentShapeIndex); + shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); + fa = isd.getTypeCode() == InternalSpatialDatatype.LINESTRING.getTypeCode() ? FA_STROKE : FA_EXTERIOR_RING; + figureList.add(new Figure(fa, pointList.size())); + + readLineWkt(); if (startPos == 0 && nextToken.toUpperCase().equals("LINESTRING") && pointList.size() == 2) { isSingleLineSegment = true; } break; case "POLYGON": - case "CURVEPOLYGON": - - break; case "MULTIPOINT": case "MULTILINESTRING": - case "MULTIPOLYGON": + thisShapeIndex = shapeList.size(); + shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); + + readShapeWkt(thisShapeIndex, nextToken); break; - case "GEOMETRYCOLLECTION": - //Geometrycollection i believe has to use return, in order to know the parent shape index. - continue; + case "MULTIPOLYGON": + thisShapeIndex = shapeList.size(); + shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); + + while (currentWktPos < wkt.length() && wkt.charAt(currentWktPos) != ')') { + shapeList.add(new Shape(thisShapeIndex, figureList.size(), InternalSpatialDatatype.POLYGON.getTypeCode())); //exterior polygon + readOpenBracket(); + readShapeWkt(thisShapeIndex, nextToken); + readCloseBracket(); + + if (wkt.charAt(currentWktPos) == ',') { // more polygons to follow + readComma(); + } else if (wkt.charAt(currentWktPos) == ')') { // about to exit while loop + continue; + } else { // unexpected input + throw new IllegalArgumentException(); + } + } + + break; case "COMPOUNDCURVE": + shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); + figureList.add(new Figure(FA_COMPOSITE_CURVE, pointList.size())); + + readCompoundCurveWkt(true); + + break; + case "CURVEPOLYGON": + shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); + while (currentWktPos < wkt.length() && wkt.charAt(currentWktPos) != ')') { + nextPotentialToken = getNextStringToken().toUpperCase(Locale.US); + if (nextPotentialToken.equals("CIRCULARSTRING")) { + figureList.add(new Figure(FA_ARC, pointList.size())); + readOpenBracket(); + readLineWkt(); + readCloseBracket(); + } else if (nextPotentialToken.equals("COMPOUNDCURVE")) { + figureList.add(new Figure(FA_COMPOSITE_CURVE, pointList.size())); + readOpenBracket(); + readCompoundCurveWkt(true); + readCloseBracket(); + } else if (wkt.charAt(currentWktPos) == '(') { //LineString + figureList.add(new Figure(FA_LINE, pointList.size())); + readOpenBracket(); + readLineWkt(); + readCloseBracket(); + } else { + throw new IllegalArgumentException(); + } + + if (wkt.charAt(currentWktPos) == ',') { // more polygons to follow + readComma(); + } else if (wkt.charAt(currentWktPos) == ')') { // about to exit while loop + continue; + } else { // unexpected input + throw new IllegalArgumentException(); + } + } + + break; + case "GEOMETRYCOLLECTION": + thisShapeIndex = shapeList.size(); + shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); + + parseWKTForSerialization(currentWktPos, thisShapeIndex, true); + break; case "FULLGLOBE": @@ -941,6 +1028,33 @@ private void parseWKTForSerialization(int startPos, int parentShapeIndex) { populateStructures(); } + private void readCompoundCurveWkt(boolean isFirstIteration) { + while (currentWktPos < wkt.length() && wkt.charAt(currentWktPos) != ')') { + String nextPotentialToken = getNextStringToken().toUpperCase(Locale.US); + if (nextPotentialToken.equals("CIRCULARSTRING")) { + readOpenBracket(); + readSegmentWkt(SEGMENT_FIRST_ARC, isFirstIteration); + readCloseBracket(); + } else if (wkt.charAt(currentWktPos) == '(') {//LineString + readOpenBracket(); + readSegmentWkt(SEGMENT_FIRST_LINE, isFirstIteration); + readCloseBracket(); + } else { + throw new IllegalArgumentException(); + } + + isFirstIteration = false; + + if (wkt.charAt(currentWktPos) == ',') { // more polygons to follow + readComma(); + } else if (wkt.charAt(currentWktPos) == ')') { // about to exit while loop + continue; + } else { // unexpected input + throw new IllegalArgumentException(); + } + } + } + private void readPointWkt() { int numOfCoordinates = 0; double sign; @@ -960,7 +1074,10 @@ private void readPointWkt() { } while (currentWktPos < wkt.length() && - !(wkt.charAt(currentWktPos) == ',' || wkt.charAt(currentWktPos) == ' ')) { + (Character.isDigit(wkt.charAt(currentWktPos)) + || wkt.charAt(currentWktPos) == '.' + || wkt.charAt(currentWktPos) == 'E' + || wkt.charAt(currentWktPos) == 'e')) { currentWktPos++; } @@ -975,6 +1092,7 @@ private void readPointWkt() { if (wkt.charAt(currentWktPos) == ',') { currentWktPos++; skipWhiteSpaces(); + numOfCoordinates++; break; } skipWhiteSpaces(); @@ -986,21 +1104,122 @@ private void readPointWkt() { hasZvalues = true; hasMvalues = true; } else if (numOfCoordinates == 3) { - hasMvalues = true; + hasZvalues = true; } pointList.add(new Point(coords[0], coords[1], coords[2], coords[3])); } - private void readLineWkt(int parentShapeOffset) { - shapeList.add(new Shape(parentShapeOffset - 1, figureList.size(), InternalSpatialDatatype.LINESTRING.getTypeCode())); - figureList.add(new Figure((byte) 1, pointList.size())); + private void readLineWkt() { while (currentWktPos < wkt.length() && wkt.charAt(currentWktPos) != ')') { readPointWkt(); } } + + private void readShapeWkt(int parentShapeIndex, String nextToken) { + byte fa = FA_POINT; + while (currentWktPos < wkt.length() && wkt.charAt(currentWktPos) != ')') { + if (nextToken.equals("MULTIPOINT")) { + shapeList.add(new Shape(parentShapeIndex, figureList.size(), InternalSpatialDatatype.POINT.getTypeCode())); + } else if (nextToken.equals("MULTILINESTRING")) { + shapeList.add(new Shape(parentShapeIndex, figureList.size(), InternalSpatialDatatype.LINESTRING.getTypeCode())); + } + + if (version == 1) { + if (nextToken.equals("MULTIPOINT")) { + fa = FA_STROKE; + } else if (nextToken.equals("MULTILINESTRING") || nextToken.equals("POLYGON")) { + fa = FA_EXTERIOR_RING; + } + version_one_shape_indexes.add(figureList.size()); + } else if (version == 2) { + if (nextToken.equals("MULTIPOINT") || nextToken.equals("MULTILINESTRING") || + nextToken.equals("POLYGON") || nextToken.equals("MULTIPOLYGON")) { + fa = FA_LINE; + } + } + + figureList.add(new Figure(fa, pointList.size())); + readOpenBracket(); + readLineWkt(); + readCloseBracket(); + + skipWhiteSpaces(); + + if (wkt.charAt(currentWktPos) == ',') { // more rings to follow + readComma(); + } else if (wkt.charAt(currentWktPos) == ')') { // about to exit while loop + continue; + } else { // unexpected input + throw new IllegalArgumentException(); + } + } + } + + private void readSegmentWkt(int segmentType, boolean isFirstIteration) { + segmentList.add(new Segment((byte) segmentType)); + + int segmentLength = segmentType; + + // under 2 means 0 or 1 (possible values). 0 (line) has 1 point, and 1 (arc) has 2 points, so increment by one + if (segmentLength < 2) { + segmentLength++; + } + + for (int i = 0; i < segmentLength; i++) { + //If a segment type of 2 (first line) or 3 (first arc) is not from the very first iteration of the while loop, + //then the first point has to be a duplicate point from the previous segment, so skip the first point. + if (i == 0 && !isFirstIteration && segmentType >= 2) { + skipFirstPointWkt(); + } else { + readPointWkt(); + } + } + + if (currentWktPos < wkt.length() && wkt.charAt(currentWktPos) != ')') { + if (segmentType == SEGMENT_FIRST_ARC || segmentType == SEGMENT_ARC) { + readSegmentWkt(SEGMENT_ARC, false); + } else if (segmentType == SEGMENT_FIRST_LINE | segmentType == SEGMENT_LINE) { + readSegmentWkt(SEGMENT_LINE, false); + } + } + } + + private void skipFirstPointWkt() { + int numOfCoordinates = 0; + + while (numOfCoordinates < 4) { + if (wkt.charAt(currentWktPos) == '-') { + currentWktPos++; + } + + if (wkt.charAt(currentWktPos) == ')') { + break; + } + + while (currentWktPos < wkt.length() && + (Character.isDigit(wkt.charAt(currentWktPos)) + || wkt.charAt(currentWktPos) == '.' + || wkt.charAt(currentWktPos) == 'E' + || wkt.charAt(currentWktPos) == 'e')) { + currentWktPos++; + } + + skipWhiteSpaces(); + if (wkt.charAt(currentWktPos) == ',') { + currentWktPos++; + skipWhiteSpaces(); + numOfCoordinates++; + break; + } + skipWhiteSpaces(); + + numOfCoordinates++; + } + } private void readOpenBracket() { + skipWhiteSpaces(); if (wkt.charAt(currentWktPos) == '(') { currentWktPos++; skipWhiteSpaces(); @@ -1013,6 +1232,17 @@ private void readCloseBracket() { skipWhiteSpaces(); if (wkt.charAt(currentWktPos) == ')') { currentWktPos++; + skipWhiteSpaces(); + } else { + throw new IllegalArgumentException(); + } + } + + private void readComma() { + skipWhiteSpaces(); + if (wkt.charAt(currentWktPos) == ',') { + currentWktPos++; + skipWhiteSpaces(); } else { throw new IllegalArgumentException(); } @@ -1066,6 +1296,15 @@ private void populateStructures() { } } + // if version is 2, then we need to check for potential shapes (polygon & multi-shapes) that were + // given their figure attributes as if it was version 1, since we don't know what would be the + // version of the geometry/geography before we parse the entire WKT. + if (version == 2) { + for (int i = 0; i < version_one_shape_indexes.size(); i++) { + figureList.get(version_one_shape_indexes.get(i)).setFiguresAttribute((byte) 1); + } + } + if (figureList.size() > 0) { figures = new Figure[figureList.size()]; @@ -1113,6 +1352,10 @@ public byte getFiguresAttribute() { public int getPointOffset() { return pointOffset; } + + public void setFiguresAttribute(byte fa) { + figuresAttribute = fa; + } } class Shape { From 1583949eb520f2cd45a61e86862275fe82f6d226 Mon Sep 17 00:00:00 2001 From: Peter Bae Date: Fri, 27 Oct 2017 11:42:49 -0700 Subject: [PATCH 07/41] Mostly finished with implementation of spatial datatypes --- .../com/microsoft/sqlserver/jdbc/DDC.java | 4 +- .../microsoft/sqlserver/jdbc/DataTypes.java | 10 +- .../microsoft/sqlserver/jdbc/Geography.java | 299 +++- .../microsoft/sqlserver/jdbc/Geometry.java | 1324 ++--------------- .../microsoft/sqlserver/jdbc/Parameter.java | 4 + .../jdbc/SQLServerPreparedStatement.java | 9 + .../sqlserver/jdbc/SQLServerResultSet.java | 16 + .../jdbc/SQLServerSpatialDatatype.java | 1282 ++++++++++++++++ .../com/microsoft/sqlserver/jdbc/dtv.java | 5 +- src/main/java/microsoft/sql/Types.java | 8 + 10 files changed, 1724 insertions(+), 1237 deletions(-) create mode 100644 src/main/java/com/microsoft/sqlserver/jdbc/SQLServerSpatialDatatype.java diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/DDC.java b/src/main/java/com/microsoft/sqlserver/jdbc/DDC.java index da88d5a3a..9ec99c7ba 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/DDC.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/DDC.java @@ -615,7 +615,9 @@ static final Object convertStreamToObject(BaseInputStream stream, if (JDBCType.GUID == jdbcType) { return Util.readGUID(byteValue); } else if (JDBCType.GEOMETRY == jdbcType) { - return new Geometry(byteValue); + return Geometry.STGeomFromWKB(byteValue); + } else if (JDBCType.GEOGRAPHY == jdbcType) { + return new Geography(byteValue); } else { String hexString = Util.bytesToHexString(byteValue, byteValue.length); diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/DataTypes.java b/src/main/java/com/microsoft/sqlserver/jdbc/DataTypes.java index 6f4f54abb..19c6a64a2 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/DataTypes.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/DataTypes.java @@ -149,7 +149,8 @@ enum SSType UDT (Category.UDT, "udt", JDBCType.VARBINARY), XML (Category.XML, "xml", JDBCType.LONGNVARCHAR), TIMESTAMP (Category.TIMESTAMP, "timestamp", JDBCType.BINARY), - GEOMETRY (Category.UDT, "geometry", JDBCType.GEOMETRY); + GEOMETRY (Category.UDT, "geometry", JDBCType.GEOMETRY), + GEOGRAPHY (Category.UDT, "geography", JDBCType.GEOGRAPHY); final Category category; private final String name; @@ -354,7 +355,8 @@ enum GetterConversion JDBCType.Category.BINARY, JDBCType.Category.LONG_BINARY, JDBCType.Category.CHARACTER, - JDBCType.Category.GEOMETRY)), + JDBCType.Category.GEOMETRY, + JDBCType.Category.GEOGRAPHY)), GUID ( SSType.Category.GUID, @@ -859,7 +861,8 @@ enum JDBCType SMALLDATETIME (Category.TIMESTAMP, microsoft.sql.Types.SMALLDATETIME, "java.sql.Timestamp"), GUID (Category.CHARACTER, microsoft.sql.Types.GUID, "java.lang.String"), SQL_VARIANT (Category.SQL_VARIANT, microsoft.sql.Types.SQL_VARIANT, "java.lang.Object"), - GEOMETRY (Category.GEOMETRY, microsoft.sql.Types.GEOMETRY, "java.lang.Object"); + GEOMETRY (Category.GEOMETRY, microsoft.sql.Types.GEOMETRY, "java.lang.Object"), + GEOGRAPHY (Category.GEOGRAPHY, microsoft.sql.Types.GEOGRAPHY, "java.lang.Object"); final Category category; @@ -910,6 +913,7 @@ enum Category { GUID, SQL_VARIANT, GEOMETRY, + GEOGRAPHY } // This SetterConversion enum is based on the Category enum diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/Geography.java b/src/main/java/com/microsoft/sqlserver/jdbc/Geography.java index 4afc49a50..25020f379 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/Geography.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/Geography.java @@ -1,5 +1,300 @@ package com.microsoft.sqlserver.jdbc; -public class Geography { +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Locale; -} +public class Geography extends SQLServerSpatialDatatype { + public Geography(String WellKnownText, int srid) { + this.wkt = WellKnownText; + this.srid = srid; + + parseWKTForSerialization(currentWktPos, -1, false); + serializeToWkb(false); + isNull = false; + } + + public Geography(byte[] wkb) { + this.wkb = wkb; + buffer = ByteBuffer.wrap(wkb); + buffer.order(ByteOrder.LITTLE_ENDIAN); + + parseWkb(); + + WKTsb = new StringBuffer(); + WKTsbNoZM = new StringBuffer(); + + constructWKT(internalType, numberOfPoints, numberOfFigures, numberOfSegments, numberOfShapes); + + wkt = WKTsb.toString(); + wktNoZM = WKTsbNoZM.toString(); + isNull = false; + } + + public Geography() { + // TODO Auto-generated constructor stub + } + + public static Geography STGeomFromText(String wkt, int srid) { + return new Geography(wkt, srid); + } + + public static Geography STGeomFromWKB(byte[] wkb) { + return new Geography(wkb); + } + + public static Geography deserialize(byte[] wkb) { + return new Geography(wkb); + } + + public static Geography parse(String wkt) { + return new Geography(wkt, 0); + } + + public static Geography point(double x, double y, int srid) { + return new Geography("POINT (" + x + " " + y + ")", srid); + } + + public String STAsText() { + if (null == wktNoZM) { + buffer = ByteBuffer.wrap(wkb); + buffer.order(ByteOrder.LITTLE_ENDIAN); + + parseWkb(); + + WKTsb = new StringBuffer(); + WKTsbNoZM = new StringBuffer(); + constructWKT(internalType, numberOfPoints, numberOfFigures, numberOfSegments, numberOfShapes); + wktNoZM = WKTsbNoZM.toString(); + } + return wktNoZM; + } + + public byte[] STAsBinary() { + if (null == wkbNoZM) { + serializeToWkb(true); + } + return wkbNoZM; + } + + public byte[] serialize() { + return wkb; + } + + public boolean hasM() { + return hasMvalues; + } + + public boolean hasZ() { + return hasZvalues; + } + + public Double getX() { + if (null != internalType && internalType == InternalSpatialDatatype.POINT && points.length == 2) { + return points[0]; + } + return null; + } + + public Double getY() { + if (null != internalType && internalType == InternalSpatialDatatype.POINT && points.length == 2) { + return points[1]; + } + return null; + } + + public Double getM() { + if (null != internalType && internalType == InternalSpatialDatatype.POINT && hasM()) { + return mValues[0]; + } + return null; + } + + public Double getZ() { + if (null != internalType && internalType == InternalSpatialDatatype.POINT && hasZ()) { + return zValues[0]; + } + return null; + } + + public int getSrid() { + return srid; + } + + public boolean isNull() { + return isNull; + } + + public int STNumPoints() { + return numberOfPoints; + } + + public String STGeographyType() { + if (null != internalType) { + return internalType.getTypeName(); + } + return null; + } + + public String asTextZM() { + return wkt; + } + + public String toString() { + return wkt; + } + + protected void constructWKT(InternalSpatialDatatype isd, int pointIndexEnd, int figureIndexEnd, int segmentIndexEnd, int shapeIndexEnd) { + if (null == points || numberOfPoints == 0) { + if (isd.getTypeCode() == 11) { // FULLGLOBE + appendToWKTBuffers("FULLGLOBE"); + return; + } + appendToWKTBuffers(internalType + " EMPTY"); + return; + } + + appendToWKTBuffers(isd.getTypeName()); + appendToWKTBuffers("("); + + switch (isd) { + case POINT: + constructPointWKT(currentPointIndex); + break; + case LINESTRING: + case CIRCULARSTRING: + constructLineWKT(currentPointIndex, pointIndexEnd); + break; + case POLYGON: + case MULTIPOINT: + case MULTILINESTRING: + constructShapeWKT(currentFigureIndex, figureIndexEnd); + break; + case COMPOUNDCURVE: + constructCompoundcurveWKT(currentSegmentIndex, segmentIndexEnd, pointIndexEnd); + break; + case MULTIPOLYGON: + constructMultipolygonWKT(currentFigureIndex, figureIndexEnd); + break; + case GEOMETRYCOLLECTION: + constructGeometryCollectionWKT(shapeIndexEnd); + break; + case CURVEPOLYGON: + constructCurvepolygonWKT(currentFigureIndex, figureIndexEnd, currentSegmentIndex, segmentIndexEnd); + break; + default: + break; + } + + appendToWKTBuffers(")"); + } + + protected void parseWKTForSerialization(int startPos, int parentShapeIndex, boolean isGeoCollection) { + //after every iteration of this while loop, the currentWktPosition will be set to the + //end of the geometry/geography shape, except for the very first iteration of it. + //This means that there has to be comma (that separates the previous shape with the next shape), + //or we expect a ')' that will close the entire shape and exit the method. + + parse: while (hasMoreToken()) { + if (startPos != 0) { + if (wkt.charAt(currentWktPos) == ')') { + return; + } else if (wkt.charAt(currentWktPos) == ',') { + currentWktPos++; + } + } + + String nextToken = getNextStringToken().toUpperCase(Locale.US); + int thisShapeIndex; + InternalSpatialDatatype isd = InternalSpatialDatatype.valueOf(nextToken); + byte fa = 0; + + // check for FULLGLOBE before reading the first open bracket, since FULLGLOBE doesn't have one. + if (nextToken.equals("FULLGLOBE")) { + if (startPos != 0) { + throw new IllegalArgumentException("Illegal character at wkt position " + currentWktPos); + } + + shapeList.add(new Shape(parentShapeIndex, -1, isd.getTypeCode())); + isLargerThanHemisphere = true; + version = 2; + break parse; + } + + readOpenBracket(); + + if (version == 1 && (nextToken.equals("CIRCULARSTRING") || nextToken.equals("COMPOUNDCURVE") || + nextToken.equals("CURVEPOLYGON"))) { + version = 2; + } + + switch (nextToken) { + case "POINT": + if (startPos == 0 && nextToken.toUpperCase().equals("POINT")) { + isSinglePoint = true; + } + + if (isGeoCollection) { + shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); + figureList.add(new Figure(FA_LINE, pointList.size())); + } + + readPointWkt(); + break; + case "LINESTRING": + case "CIRCULARSTRING": + shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); + fa = isd.getTypeCode() == InternalSpatialDatatype.LINESTRING.getTypeCode() ? FA_STROKE : FA_EXTERIOR_RING; + figureList.add(new Figure(fa, pointList.size())); + + readLineWkt(); + + if (startPos == 0 && nextToken.toUpperCase().equals("LINESTRING") && pointList.size() == 2) { + isSingleLineSegment = true; + } + break; + case "POLYGON": + case "MULTIPOINT": + case "MULTILINESTRING": + thisShapeIndex = shapeList.size(); + shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); + + readShapeWkt(thisShapeIndex, nextToken); + + break; + case "MULTIPOLYGON": + thisShapeIndex = shapeList.size(); + shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); + + readMultiPolygonWkt(thisShapeIndex, nextToken); + + break; + case "COMPOUNDCURVE": + shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); + figureList.add(new Figure(FA_COMPOSITE_CURVE, pointList.size())); + + readCompoundCurveWkt(true); + + break; + case "CURVEPOLYGON": + shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); + + readCurvePolygon(); + + break; + case "GEOMETRYCOLLECTION": + thisShapeIndex = shapeList.size(); + shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); + + parseWKTForSerialization(currentWktPos, thisShapeIndex, true); + + break; + default: + throw new IllegalArgumentException("Illegal character at wkt position " + currentWktPos); + } + readCloseBracket(); + } + + populateStructures(); + } +} \ No newline at end of file diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java b/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java index 59969ac41..da18702d0 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java @@ -1,89 +1,20 @@ package com.microsoft.sqlserver.jdbc; -import java.math.BigDecimal; import java.nio.ByteBuffer; import java.nio.ByteOrder; -import java.util.ArrayList; -import java.util.List; import java.util.Locale; -public class Geometry { - - private ByteBuffer buffer; - private InternalSpatialDatatype internalType; - private String wkt; - private byte[] wkb; - private int srid; - private byte version = 1; - private byte serializationProperties; - private int numberOfPoints; - private int numberOfFigures; - private int numberOfShapes; - private int numberOfSegments; - private double points[]; - private double zValues[]; - private double mValues[]; - private Figure figures[]; - private Shape shapes[]; - private Segment segments[]; - private StringBuffer WKTsb; - private int currentPointIndex = 0; - private int currentFigureIndex = 0; - private int currentSegmentIndex = 0; - private int currentShapeIndex = 0; - - //serialization properties - private boolean hasZvalues = false; - private boolean hasMvalues = false; - //TODO: when is a geometry/geography not valid? - //Also, from the driver's point of view, should this ever be false? - private boolean isValid = false; - private boolean isSinglePoint = false; - private boolean isSingleLineSegment = false; - //TODO: how do i use this? - private boolean isLargerThanHemisphere = false; - - private final byte FA_INTERIOR_RING = 0; - private final byte FA_STROKE = 1; - private final byte FA_EXTERIOR_RING = 2; - - private final byte FA_POINT = 0; - private final byte FA_LINE = 1; - private final byte FA_ARC = 2; - private final byte FA_COMPOSITE_CURVE = 3; - - private final byte SEGMENT_LINE = 0; - private final byte SEGMENT_ARC = 1; - private final byte SEGMENT_FIRST_LINE = 2; - private final byte SEGMENT_FIRST_ARC = 3; - - private final byte hasZvaluesMask = 0b00000001; - private final byte hasMvaluesMask = 0b00000010; - private final byte isValidMask = 0b00000100; - private final byte isSinglePointMask = 0b00001000; - private final byte isSingleLineSegmentMask = 0b00010000; - private final byte isLargerThanHemisphereMask = 0b00100000; - - // WKT to WKB properties - - private int currentWktPos = 0; - private List pointList = new ArrayList(); - private List
figureList = new ArrayList
(); - private List shapeList = new ArrayList(); - private List segmentList = new ArrayList(); - - private List version_one_shape_indexes = new ArrayList(); - - public Geometry(String WellKnownText, int srid) { +public class Geometry extends SQLServerSpatialDatatype { + private Geometry(String WellKnownText, int srid) { this.wkt = WellKnownText; this.srid = srid; - //TODO: do lazy conversion later parseWKTForSerialization(currentWktPos, -1, false); - serializeToWkb(); + serializeToWkb(false); + isNull = false; } - public Geometry(byte[] wkb) { + private Geometry(byte[] wkb) { this.wkb = wkb; buffer = ByteBuffer.wrap(wkb); buffer.order(ByteOrder.LITTLE_ENDIAN); @@ -91,300 +22,139 @@ public Geometry(byte[] wkb) { parseWkb(); WKTsb = new StringBuffer(); + WKTsbNoZM = new StringBuffer(); constructWKT(internalType, numberOfPoints, numberOfFigures, numberOfSegments, numberOfShapes); wkt = WKTsb.toString(); + wktNoZM = WKTsbNoZM.toString(); + isNull = false; } - public InternalSpatialDatatype getInternalType() { - return internalType; + public Geometry() { + // TODO Auto-generated constructor stub } - public int getSRID() { - return srid; + public static Geometry STGeomFromText(String wkt, int srid) { + return new Geometry(wkt, srid); } - public byte[] getWkb() { - return wkb; + public static Geometry STGeomFromWKB(byte[] wkb) { + return new Geometry(wkb); } - public String toString() { - return wkt; + public static Geometry deserialize(byte[] wkb) { + return new Geometry(wkb); } - private void serializeToWkb() { - 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 (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()); - } - } - - wkb = buf.array(); - return; + public static Geometry parse(String wkt) { + return new Geometry(wkt, 0); } - private void createSerializationProperties() { - serializationProperties = 0; - if (hasZvalues) { - serializationProperties += hasZvaluesMask; - } - - if (hasMvalues) { - serializationProperties += hasMvaluesMask; - } - - if (isValid) { - serializationProperties += isValidMask; - } - - if (isSinglePoint) { - serializationProperties += isSinglePointMask; - } - - if (isSingleLineSegment) { - serializationProperties += isSingleLineSegmentMask; - } - - //TODO look into how the isLargerThanHemisphere is created - if (version == 2) { - if (isLargerThanHemisphere) { - serializationProperties += isLargerThanHemisphereMask; - } - } + public static Geometry point(double x, double y, int srid) { + return new Geometry("POINT (" + x + " " + y + ")", srid); } - - private int determineWkbCapacity() { - int totalSize = 0; - - totalSize+=6; // SRID + version + SerializationPropertiesByte - - if (isSinglePoint || isSingleLineSegment) { - totalSize += 16 * numberOfPoints; - - if (hasZvalues) { - totalSize += 8 * numberOfPoints; - } + + public String STAsText() { + if (null == wktNoZM) { + buffer = ByteBuffer.wrap(wkb); + buffer.order(ByteOrder.LITTLE_ENDIAN); - if (hasMvalues) { - totalSize += 8 * numberOfPoints; - } + parseWkb(); - return totalSize; - } - - int pointSize = 16; - if (hasZvalues) { - pointSize += 8; - } - - if (hasMvalues) { - pointSize += 8; + WKTsb = new StringBuffer(); + WKTsbNoZM = new StringBuffer(); + constructWKT(internalType, numberOfPoints, numberOfFigures, numberOfSegments, numberOfShapes); + wktNoZM = WKTsbNoZM.toString(); } - - totalSize += 12; // 4 bytes for 3 ints, each representing the number of points, shapes and figures - totalSize += numberOfPoints * pointSize; - totalSize += numberOfFigures * 5; - totalSize += numberOfShapes * 9; - - if (version == 2) { - totalSize += 4; // 4 bytes for 1 int, representing the number of segments - totalSize += numberOfSegments; - } - - return totalSize; + return wktNoZM; } - - private void parseWkb() { - srid = buffer.getInt(); - version = buffer.get(); - serializationProperties = buffer.get(); - - interpretSerializationPropBytes(); - - readNumberOfPoints(); - - readPoints(); - - if (hasZvalues) { - readZvalues(); - } - - if (hasMvalues) { - readMvalues(); - } - - //TODO: do I need to do anything when it's isSinglePoint or isSingleLineSegment? - if (!(isSinglePoint || isSingleLineSegment)) { - readNumberOfFigures(); - - readFigures(); - - readNumberOfShapes(); - - readShapes(); - } - - determineInternalType(); - - if (version == 2 && internalType.getTypeCode() != 8) { - readNumberOfSegments(); - - readSegments(); + + public byte[] STAsBinary() { + if (null == wkbNoZM) { + serializeToWkb(true); } + return wkbNoZM; } - - private void interpretSerializationPropBytes() { - hasZvalues = (serializationProperties & hasZvaluesMask) != 0; - hasMvalues = (serializationProperties & hasMvaluesMask) != 0; - isValid = (serializationProperties & isValidMask) != 0; - isSinglePoint = (serializationProperties & isSinglePointMask) != 0; - isSingleLineSegment = (serializationProperties & isSingleLineSegmentMask) != 0; + + public byte[] serialize() { + return wkb; } - private void readNumberOfPoints() { - if (isSinglePoint) { - numberOfPoints = 1; - } else if (isSingleLineSegment) { - numberOfPoints = 2; - } else { - numberOfPoints = buffer.getInt(); - } + public boolean hasM() { + return hasMvalues; } - - 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(); - } + + public boolean hasZ() { + return hasZvalues; } - private void readZvalues() { - zValues = new double[numberOfPoints]; - for (int i = 0; i < numberOfPoints; i++) { - zValues[i] = buffer.getDouble(); + public Double getX() { + if (null != internalType && internalType == InternalSpatialDatatype.POINT && points.length == 2) { + return points[0]; } + return null; } - private void readMvalues() { - mValues = new double[numberOfPoints]; - for (int i = 0; i < numberOfPoints; i++) { - mValues[i] = buffer.getDouble(); + public Double getY() { + if (null != internalType && internalType == InternalSpatialDatatype.POINT && points.length == 2) { + return points[1]; } + return null; } - private void readNumberOfFigures() { - numberOfFigures = buffer.getInt(); + public Double getM() { + if (null != internalType && internalType == InternalSpatialDatatype.POINT && hasM()) { + return mValues[0]; + } + return null; } - private void readFigures() { - byte fa; - int po; - figures = new Figure[numberOfFigures]; - for (int i = 0; i < numberOfFigures; i++) { - fa = buffer.get(); - po = buffer.getInt(); - figures[i] = new Figure(fa, po); + public Double getZ() { + if (null != internalType && internalType == InternalSpatialDatatype.POINT && hasZ()) { + return zValues[0]; } + return null; + } + + public int getSrid() { + return srid; } - private void readNumberOfShapes() { - numberOfShapes = buffer.getInt(); + public boolean isNull() { + return isNull; } - private void readShapes() { - 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(); - shapes[i] = new Shape(po, fo, ogt); + public int STNumPoints() { + return numberOfPoints; + } + + public String STGeometryType() { + if (null != internalType) { + return internalType.getTypeName(); } + return null; } - private void readNumberOfSegments() { - numberOfSegments = buffer.getInt(); + public String asTextZM() { + return wkt; } - private void readSegments() { - byte st; - segments = new Segment[numberOfSegments]; - for (int i = 0; i < numberOfSegments; i++) { - st = buffer.get(); - segments[i] = new Segment(st); - } - } - - private void determineInternalType() { - if (isSinglePoint) { - internalType = InternalSpatialDatatype.POINT; - } else if (isSingleLineSegment) { - internalType = InternalSpatialDatatype.LINESTRING; - } else { - internalType = InternalSpatialDatatype.valueOf(shapes[0].getOpenGISType()); - } + public String toString() { + return wkt; } - private void constructWKT(InternalSpatialDatatype isd, int pointIndexEnd, int figureIndexEnd, int segmentIndexEnd, int shapeIndexEnd) { + protected void constructWKT(InternalSpatialDatatype isd, int pointIndexEnd, int figureIndexEnd, int segmentIndexEnd, int shapeIndexEnd) { if (null == points || numberOfPoints == 0) { - wkt = internalType + " EMPTY"; + if (isd.getTypeCode() == 11) { // FULLGLOBE + throw new IllegalArgumentException("Fullglobe is not supported for Geometry."); + } + appendToWKTBuffers(internalType + " EMPTY"); return; } - WKTsb.append(isd.getTypeName()); - WKTsb.append("("); + appendToWKTBuffers(isd.getTypeName()); + appendToWKTBuffers("("); switch (isd) { case POINT: @@ -411,478 +181,14 @@ private void constructWKT(InternalSpatialDatatype isd, int pointIndexEnd, int fi case CURVEPOLYGON: constructCurvepolygonWKT(currentFigureIndex, figureIndexEnd, currentSegmentIndex, segmentIndexEnd); break; - case FULLGLOBE: - //TODO: return error - return; default: - break; - } - - WKTsb.append(")"); - } - - private void constructPointWKT(int pointIndex) { - int firstPointIndex = pointIndex * 2; - int secondPointIndex = firstPointIndex + 1; - int zValueIndex = pointIndex; - int mValueIndex = pointIndex; - - - WKTsb.append(points[firstPointIndex]); - WKTsb.append(" "); - - WKTsb.append(points[secondPointIndex]); - WKTsb.append(" "); - - if (hasZvalues && !Double.isNaN(zValues[zValueIndex])) { - WKTsb.append(zValues[zValueIndex]); - WKTsb.append(" "); - - if (hasMvalues && !Double.isNaN(mValues[mValueIndex])) { - WKTsb.append(mValues[mValueIndex]); - WKTsb.append(" "); - } + throw new IllegalArgumentException(); } - currentPointIndex++; - WKTsb.setLength(WKTsb.length() - 1); // truncate last space - } - - private void constructLineWKT(int pointStartIndex, int pointEndIndex) { - for (int i = pointStartIndex; i < pointEndIndex; i++) { - constructPointWKT(i); - - // add ', ' to separate points, except for the last point - if (i != pointEndIndex - 1) { - WKTsb.append(", "); - } - } - } - - private void constructShapeWKT(int figureStartIndex, int figureEndIndex) { - // Method for constructing shapes (simple Geometry/Geography entities that are contained within a single bracket) - for (int i = figureStartIndex; i < figureEndIndex; i++) { - WKTsb.append("("); - if (i != numberOfFigures - 1) { //not the last figure - constructLineWKT(figures[i].getPointOffset(), figures[i + 1].getPointOffset()); - } else { - constructLineWKT(figures[i].getPointOffset(), numberOfPoints); - } - - if (i != figureEndIndex - 1) { - WKTsb.append("), "); - } else { - WKTsb.append(")"); - } - } - } - - private void constructCompoundcurveWKT(int segmentStartIndex, int segmentEndIndex, int pointEndIndex) { - for (int i = segmentStartIndex; i < segmentEndIndex; i++) { - byte segment = segments[i].getSegmentType(); - constructSegmentWKT(i, segment, pointEndIndex); - - if (i == segmentEndIndex - 1) { - WKTsb.append(")"); - break; - } - - switch (segment) { - case 0: - case 2: - if (segments[i + 1].getSegmentType() != 0) { - WKTsb.append("), "); - } - break; - case 1: - case 3: - if (segments[i + 1].getSegmentType() != 1) { - WKTsb.append("), "); - } - break; - default: - return; - } - } - } - - private void constructSegmentWKT(int currentSegment, byte segment, int pointEndIndex) { - switch (segment) { - case 0: - WKTsb.append(", "); - constructLineWKT(currentPointIndex, currentPointIndex + 1); - - if (currentSegment == segments.length - 1) { // last segment - break; - } else if (segments[currentSegment + 1].getSegmentType() != 0) { // not being followed by another line, but not the last segment - currentPointIndex = currentPointIndex - 1; - incrementPointNumStartIfPointNotReused(pointEndIndex); - } - break; - - case 1: - WKTsb.append(", "); - constructLineWKT(currentPointIndex, currentPointIndex + 2); - - if (currentSegment == segments.length - 1) { // last segment - break; - } else if (segments[currentSegment + 1].getSegmentType() != 1) { // not being followed by another arc, but not the last segment - currentPointIndex = currentPointIndex - 1; // only increment pointNumStart by one less than what we should be, since the last point will be reused - incrementPointNumStartIfPointNotReused(pointEndIndex); - } - - break; - case 2: - WKTsb.append("("); - constructLineWKT(currentPointIndex, currentPointIndex + 2); - - if (currentSegment == segments.length - 1) { // last segment - break; - } else if (segments[currentSegment + 1].getSegmentType() != 0) { // not being followed by another line, but not the last segment - currentPointIndex = currentPointIndex - 1; // only increment pointNumStart by one less than what we should be, since the last point will be reused - incrementPointNumStartIfPointNotReused(pointEndIndex); - } - - break; - case 3: - WKTsb.append("CIRCULARSTRING("); - constructLineWKT(currentPointIndex, currentPointIndex + 3); - - if (currentSegment == segments.length - 1) { // last segment - break; - } else if (segments[currentSegment + 1].getSegmentType() != 1) { // not being followed by another arc - currentPointIndex = currentPointIndex - 1; // only increment pointNumStart by one less than what we should be, since the last point will be reused - incrementPointNumStartIfPointNotReused(pointEndIndex); - } - - break; - default: - return; - } + appendToWKTBuffers(")"); } - 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. - if (currentPointIndex + 1 >= pointEndIndex) { - currentPointIndex++; - } - } - - private void constructMultipolygonWKT(int figureStartIndex, int figureEndIndex) { - for (int i = figureStartIndex; i < figureEndIndex; i++) { - if (figures[i].getFiguresAttribute() == 2) { // exterior ring - WKTsb.append("(("); - } else { // interior ring - WKTsb.append("("); - } - - if (i == figures.length - 1) { // last figure - constructLineWKT(figures[i].getPointOffset(), numberOfPoints); - } else { - constructLineWKT(figures[i].getPointOffset(), figures[i + 1].getPointOffset()); - } - - if (i == figureEndIndex - 1) { // last polygon of this multipolygon, close off the Multipolygon and return - WKTsb.append("))"); - return; - } else if (figures[i + 1].getFiguresAttribute() == 2) { // not the last polygon, followed by an exterior ring - WKTsb.append(")), "); - } else { // not the last polygon, followed by an interior ring - WKTsb.append("), "); - } - } - } - - private void constructCurvepolygonWKT(int figureStartIndex, int figureEndIndex, int segmentStartIndex, int segmentEndIndex) { - for (int i = figureStartIndex; i < figureEndIndex; i++) { - switch (figures[i].getFiguresAttribute()) { - case 1: // line - WKTsb.append("("); - - if (i == figures.length - 1) { - constructLineWKT(currentPointIndex, numberOfPoints); - } else { - constructLineWKT(currentPointIndex, figures[i + 1].getPointOffset()); - //currentPointIndex = figures[i + 1].getPointOffset(); - } - - WKTsb.append(")"); - break; - case 2: // arc - WKTsb.append("CIRCULARSTRING("); - - if (i == figures.length - 1) { - constructLineWKT(currentPointIndex, numberOfPoints); - } else { - constructLineWKT(currentPointIndex, figures[i + 1].getPointOffset()); - //currentPointIndex = figures[i + 1].getPointOffset(); - } - - WKTsb.append(")"); - - break; - case 3: // composite curve - WKTsb.append("COMPOUNDCURVE("); - - int pointEndIndex = 0; - - if (i == figures.length - 1) { - pointEndIndex = numberOfPoints; - } else { - pointEndIndex = figures[i + 1].getPointOffset(); - } - - while (currentPointIndex < pointEndIndex) { - byte segment = segments[segmentStartIndex].getSegmentType(); - constructSegmentWKT(segmentStartIndex, segment, pointEndIndex); - - if (segmentStartIndex >= segmentEndIndex - 1) { - WKTsb.append(")"); - // about to exit while loop, but not the last segment = we are closing Compoundcurve. - } else if (!(currentPointIndex < pointEndIndex)) { - WKTsb.append("))"); - } else { - switch (segment) { - case 0: - case 2: - if (segments[segmentStartIndex + 1].getSegmentType() != 0) { - WKTsb.append("), "); - } - break; - case 1: - case 3: - if (segments[segmentStartIndex + 1].getSegmentType() != 1) { - WKTsb.append("), "); - } - break; - default: - return; - } - } - - segmentStartIndex++; - } - - break; - default: - return; - } - - if (i == figureEndIndex - 1) { - WKTsb.append(")"); - } else { - WKTsb.append(", "); - } - - } - } - - private void constructGeometryCollectionWKT(int shapeEndIndex) { - currentShapeIndex++; - constructGeometryCollectionWKThelper(shapeEndIndex); - } - - private void constructGeometryCollectionWKThelper(int shapeEndIndex) { - //phase 1: assume that there is no multi - stuff and no geometrycollection - while (currentShapeIndex < shapeEndIndex) { - InternalSpatialDatatype isd = InternalSpatialDatatype.valueOf(shapes[currentShapeIndex].getOpenGISType()); - - int figureIndex = shapes[currentShapeIndex].getFigureOffset(); - int pointIndexEnd = numberOfPoints; - int figureIndexEnd = numberOfFigures; - int segmentIndexEnd = numberOfSegments; - int shapeIndexEnd = numberOfShapes; - int figureIndexIncrement = 0; - int segmentIndexIncrement = 0; - int localCurrentSegmentIndex = 0; - int localCurrentShapeIndex = 0; - - switch (isd) { - case POINT: - figureIndexIncrement++; - currentShapeIndex++; - break; - case LINESTRING: - case CIRCULARSTRING: - figureIndexIncrement++; - currentShapeIndex++; - pointIndexEnd = figures[figureIndex + 1].getPointOffset(); - break; - case POLYGON: - case CURVEPOLYGON: - if (currentShapeIndex < shapes.length - 1) { - figureIndexEnd = shapes[currentShapeIndex + 1].getFigureOffset(); - } - - figureIndexIncrement = figureIndexEnd - currentFigureIndex; - currentShapeIndex++; - - // Needed to keep track of which segment we are at, inside the for loop - localCurrentSegmentIndex = currentSegmentIndex; - - if (isd.equals(InternalSpatialDatatype.CURVEPOLYGON)) { - // assume Version 2 - - for (int i = currentFigureIndex; i < figureIndexEnd; i++) { - // Only Compoundcurves (with figure attribute 3) can have segments - if (figures[i].getFiguresAttribute() == 3) { - - int pointOffsetEnd; - if (i == figures.length - 1) { - pointOffsetEnd = numberOfPoints; - } else { - pointOffsetEnd = figures[i + 1].getPointOffset(); - } - - int increment = calculateSegmentIncrement(localCurrentSegmentIndex, pointOffsetEnd - figures[i].getPointOffset()); - - segmentIndexIncrement = segmentIndexIncrement + increment; - localCurrentSegmentIndex = localCurrentSegmentIndex + increment; - } - } - } - - segmentIndexEnd = localCurrentSegmentIndex; - - break; - case MULTIPOINT: - case MULTILINESTRING: - case MULTIPOLYGON: - //Multipoint and MultiLineString can go on for multiple Shapes, but eventually - //the parentOffset will signal the end of the object, or it's reached the end of the - //shapes array. - //There is also no possibility that a MultiPoint or MultiLineString would branch - //into another parent. - - int thisShapesParentOffset = shapes[currentShapeIndex].getParentOffset(); - - // Increment shapeStartIndex to account for the shape index that either Multipoint, MultiLineString - // or MultiPolygon takes up - currentShapeIndex++; - while (currentShapeIndex < shapes.length - 1 && - shapes[currentShapeIndex].getParentOffset() != thisShapesParentOffset) { - figureIndexEnd = shapes[currentShapeIndex + 1].getFigureOffset(); - currentShapeIndex++; - } - - figureIndexIncrement = figureIndexEnd - currentFigureIndex; - break; - case GEOMETRYCOLLECTION: - WKTsb.append(isd.getTypeName()); - WKTsb.append("("); - - int geometryCollectionParentIndex = shapes[currentShapeIndex].getParentOffset(); - - // Needed to keep track of which shape we are at, inside the for loop - localCurrentShapeIndex = currentShapeIndex; - - - while (localCurrentShapeIndex < shapes.length - 1 && - shapes[localCurrentShapeIndex + 1].getParentOffset() > geometryCollectionParentIndex) { - localCurrentShapeIndex++; - } - // increment localCurrentShapeIndex one more time since it will be used as a shapeEndIndex parameter - // for constructGeometryCollectionWKT, and the shapeEndIndex parameter is used non-inclusively - localCurrentShapeIndex++; - - currentShapeIndex++; - constructGeometryCollectionWKThelper(localCurrentShapeIndex); - - if (currentShapeIndex < shapeEndIndex) { - WKTsb.append("), "); - } else { - WKTsb.append(")"); - } - - continue; - case COMPOUNDCURVE: - if (currentFigureIndex == figures.length - 1) { - pointIndexEnd = numberOfPoints; - } else { - pointIndexEnd = figures[currentFigureIndex + 1].getPointOffset(); - } - - int increment = calculateSegmentIncrement(currentSegmentIndex, pointIndexEnd - - figures[currentFigureIndex].getPointOffset()); - - segmentIndexIncrement = increment; - segmentIndexEnd = currentSegmentIndex + increment; - figureIndexIncrement++; - currentShapeIndex++; - break; - case FULLGLOBE: - WKTsb.append("FULLGLOBE"); - break; - default: - break; - } - - constructWKT(isd, pointIndexEnd, figureIndexEnd, segmentIndexEnd, shapeIndexEnd); - currentFigureIndex = currentFigureIndex + figureIndexIncrement; - currentSegmentIndex = currentSegmentIndex + segmentIndexIncrement; - - if (currentShapeIndex < shapeEndIndex) { - WKTsb.append(", "); - } - } - } - - //Calculates how many segments will be used by this CompoundCurve - private int calculateSegmentIncrement(int segmentStart, - int pointDifference) { - - int segmentIncrement = 0; - - while (pointDifference > 0) { - switch (segments[segmentStart].getSegmentType()) { - case 0: - pointDifference = pointDifference - 1; - - if (segmentStart == segments.length - 1 || pointDifference < 1) { // last segment - break; - } else if (segments[segmentStart + 1].getSegmentType() != 0) { // one point will be reused - pointDifference = pointDifference + 1; - } - break; - case 1: - pointDifference = pointDifference - 2; - - if (segmentStart == segments.length - 1 || pointDifference < 1) { // last segment - break; - } else if (segments[segmentStart + 1].getSegmentType() != 1) { // one point will be reused - pointDifference = pointDifference + 1; - } - break; - case 2: - pointDifference = pointDifference - 2; - - if (segmentStart == segments.length - 1 || pointDifference < 1) { // last segment - break; - } else if (segments[segmentStart + 1].getSegmentType() != 0) { // one point will be reused - pointDifference = pointDifference + 1; - } - break; - case 3: - pointDifference = pointDifference - 3; - - if (segmentStart == segments.length - 1 || pointDifference < 1) { // last segment - break; - } else if (segments[segmentStart + 1].getSegmentType() != 1) { // one point will be reused - pointDifference = pointDifference + 1; - } - break; - default: - return segmentIncrement; - } - segmentStart++; - segmentIncrement++; - } - - return segmentIncrement; - } - - private void parseWKTForSerialization(int startPos, int parentShapeIndex, boolean isGeoCollection) { + protected void parseWKTForSerialization(int startPos, int parentShapeIndex, boolean isGeoCollection) { //after every iteration of this while loop, the currentWktPosition will be set to the //end of the geometry/geography shape, except for the very first iteration of it. //This means that there has to be comma (that separates the previous shape with the next shape), @@ -894,14 +200,10 @@ private void parseWKTForSerialization(int startPos, int parentShapeIndex, boolea return; } else if (wkt.charAt(currentWktPos) == ',') { currentWktPos++; - } else { - //TODO: throw exception here? - //return; } } String nextToken = getNextStringToken().toUpperCase(Locale.US); - String nextPotentialToken; int thisShapeIndex; InternalSpatialDatatype isd = InternalSpatialDatatype.valueOf(nextToken); byte fa = 0; @@ -951,21 +253,8 @@ private void parseWKTForSerialization(int startPos, int parentShapeIndex, boolea thisShapeIndex = shapeList.size(); shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); - while (currentWktPos < wkt.length() && wkt.charAt(currentWktPos) != ')') { - shapeList.add(new Shape(thisShapeIndex, figureList.size(), InternalSpatialDatatype.POLYGON.getTypeCode())); //exterior polygon - readOpenBracket(); - readShapeWkt(thisShapeIndex, nextToken); - readCloseBracket(); - - if (wkt.charAt(currentWktPos) == ',') { // more polygons to follow - readComma(); - } else if (wkt.charAt(currentWktPos) == ')') { // about to exit while loop - continue; - } else { // unexpected input - throw new IllegalArgumentException(); - } - } - + readMultiPolygonWkt(thisShapeIndex, nextToken); + break; case "COMPOUNDCURVE": shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); @@ -977,35 +266,7 @@ private void parseWKTForSerialization(int startPos, int parentShapeIndex, boolea case "CURVEPOLYGON": shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); - while (currentWktPos < wkt.length() && wkt.charAt(currentWktPos) != ')') { - nextPotentialToken = getNextStringToken().toUpperCase(Locale.US); - if (nextPotentialToken.equals("CIRCULARSTRING")) { - figureList.add(new Figure(FA_ARC, pointList.size())); - readOpenBracket(); - readLineWkt(); - readCloseBracket(); - } else if (nextPotentialToken.equals("COMPOUNDCURVE")) { - figureList.add(new Figure(FA_COMPOSITE_CURVE, pointList.size())); - readOpenBracket(); - readCompoundCurveWkt(true); - readCloseBracket(); - } else if (wkt.charAt(currentWktPos) == '(') { //LineString - figureList.add(new Figure(FA_LINE, pointList.size())); - readOpenBracket(); - readLineWkt(); - readCloseBracket(); - } else { - throw new IllegalArgumentException(); - } - - if (wkt.charAt(currentWktPos) == ',') { // more polygons to follow - readComma(); - } else if (wkt.charAt(currentWktPos) == ')') { // about to exit while loop - continue; - } else { // unexpected input - throw new IllegalArgumentException(); - } - } + readCurvePolygon(); break; case "GEOMETRYCOLLECTION": @@ -1016,410 +277,13 @@ private void parseWKTForSerialization(int startPos, int parentShapeIndex, boolea break; case "FULLGLOBE": - - break; + throw new IllegalArgumentException("Fullglobe is not supported for Geometry."); default: - break; + throw new IllegalArgumentException("Illegal character at wkt position " + currentWktPos); } - //all geometry methods return when the depth reaches 0. ( gives + 1 depth and ) takes away 1 depth. readCloseBracket(); } populateStructures(); } - - private void readCompoundCurveWkt(boolean isFirstIteration) { - while (currentWktPos < wkt.length() && wkt.charAt(currentWktPos) != ')') { - String nextPotentialToken = getNextStringToken().toUpperCase(Locale.US); - if (nextPotentialToken.equals("CIRCULARSTRING")) { - readOpenBracket(); - readSegmentWkt(SEGMENT_FIRST_ARC, isFirstIteration); - readCloseBracket(); - } else if (wkt.charAt(currentWktPos) == '(') {//LineString - readOpenBracket(); - readSegmentWkt(SEGMENT_FIRST_LINE, isFirstIteration); - readCloseBracket(); - } else { - throw new IllegalArgumentException(); - } - - isFirstIteration = false; - - if (wkt.charAt(currentWktPos) == ',') { // more polygons to follow - readComma(); - } else if (wkt.charAt(currentWktPos) == ')') { // about to exit while loop - continue; - } else { // unexpected input - throw new IllegalArgumentException(); - } - } - } - - private void readPointWkt() { - int numOfCoordinates = 0; - double sign; - double coords[] = new double[4]; - - while (numOfCoordinates < 4) { - sign = 1; - if (wkt.charAt(currentWktPos) == '-') { - sign = -1; - currentWktPos++; - } - - int startPos = currentWktPos; - - if (wkt.charAt(currentWktPos) == ')') { - break; - } - - while (currentWktPos < wkt.length() && - (Character.isDigit(wkt.charAt(currentWktPos)) - || wkt.charAt(currentWktPos) == '.' - || wkt.charAt(currentWktPos) == 'E' - || wkt.charAt(currentWktPos) == 'e')) { - currentWktPos++; - } - - try { - coords[numOfCoordinates] = sign * - new BigDecimal(wkt.substring(startPos, currentWktPos)).doubleValue(); - } catch (Exception e) { //modify to conversion exception - throw new IllegalArgumentException(); - } - - skipWhiteSpaces(); - if (wkt.charAt(currentWktPos) == ',') { - currentWktPos++; - skipWhiteSpaces(); - numOfCoordinates++; - break; - } - skipWhiteSpaces(); - - numOfCoordinates++; - } - - 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])); - } - - private void readLineWkt() { - while (currentWktPos < wkt.length() && wkt.charAt(currentWktPos) != ')') { - readPointWkt(); - } - } - - private void readShapeWkt(int parentShapeIndex, String nextToken) { - byte fa = FA_POINT; - while (currentWktPos < wkt.length() && wkt.charAt(currentWktPos) != ')') { - if (nextToken.equals("MULTIPOINT")) { - shapeList.add(new Shape(parentShapeIndex, figureList.size(), InternalSpatialDatatype.POINT.getTypeCode())); - } else if (nextToken.equals("MULTILINESTRING")) { - shapeList.add(new Shape(parentShapeIndex, figureList.size(), InternalSpatialDatatype.LINESTRING.getTypeCode())); - } - - if (version == 1) { - if (nextToken.equals("MULTIPOINT")) { - fa = FA_STROKE; - } else if (nextToken.equals("MULTILINESTRING") || nextToken.equals("POLYGON")) { - fa = FA_EXTERIOR_RING; - } - version_one_shape_indexes.add(figureList.size()); - } else if (version == 2) { - if (nextToken.equals("MULTIPOINT") || nextToken.equals("MULTILINESTRING") || - nextToken.equals("POLYGON") || nextToken.equals("MULTIPOLYGON")) { - fa = FA_LINE; - } - } - - figureList.add(new Figure(fa, pointList.size())); - readOpenBracket(); - readLineWkt(); - readCloseBracket(); - - skipWhiteSpaces(); - - if (wkt.charAt(currentWktPos) == ',') { // more rings to follow - readComma(); - } else if (wkt.charAt(currentWktPos) == ')') { // about to exit while loop - continue; - } else { // unexpected input - throw new IllegalArgumentException(); - } - } - } - - private void readSegmentWkt(int segmentType, boolean isFirstIteration) { - segmentList.add(new Segment((byte) segmentType)); - - int segmentLength = segmentType; - - // under 2 means 0 or 1 (possible values). 0 (line) has 1 point, and 1 (arc) has 2 points, so increment by one - if (segmentLength < 2) { - segmentLength++; - } - - for (int i = 0; i < segmentLength; i++) { - //If a segment type of 2 (first line) or 3 (first arc) is not from the very first iteration of the while loop, - //then the first point has to be a duplicate point from the previous segment, so skip the first point. - if (i == 0 && !isFirstIteration && segmentType >= 2) { - skipFirstPointWkt(); - } else { - readPointWkt(); - } - } - - if (currentWktPos < wkt.length() && wkt.charAt(currentWktPos) != ')') { - if (segmentType == SEGMENT_FIRST_ARC || segmentType == SEGMENT_ARC) { - readSegmentWkt(SEGMENT_ARC, false); - } else if (segmentType == SEGMENT_FIRST_LINE | segmentType == SEGMENT_LINE) { - readSegmentWkt(SEGMENT_LINE, false); - } - } - } - - private void skipFirstPointWkt() { - int numOfCoordinates = 0; - - while (numOfCoordinates < 4) { - if (wkt.charAt(currentWktPos) == '-') { - currentWktPos++; - } - - if (wkt.charAt(currentWktPos) == ')') { - break; - } - - while (currentWktPos < wkt.length() && - (Character.isDigit(wkt.charAt(currentWktPos)) - || wkt.charAt(currentWktPos) == '.' - || wkt.charAt(currentWktPos) == 'E' - || wkt.charAt(currentWktPos) == 'e')) { - currentWktPos++; - } - - skipWhiteSpaces(); - if (wkt.charAt(currentWktPos) == ',') { - currentWktPos++; - skipWhiteSpaces(); - numOfCoordinates++; - break; - } - skipWhiteSpaces(); - - numOfCoordinates++; - } - } - - private void readOpenBracket() { - skipWhiteSpaces(); - if (wkt.charAt(currentWktPos) == '(') { - currentWktPos++; - skipWhiteSpaces(); - } else { - throw new IllegalArgumentException(); - } - } - - private void readCloseBracket() { - skipWhiteSpaces(); - if (wkt.charAt(currentWktPos) == ')') { - currentWktPos++; - skipWhiteSpaces(); - } else { - throw new IllegalArgumentException(); - } - } - - private void readComma() { - skipWhiteSpaces(); - if (wkt.charAt(currentWktPos) == ',') { - currentWktPos++; - skipWhiteSpaces(); - } else { - throw new IllegalArgumentException(); - } - } - - private boolean hasMoreToken() { - skipWhiteSpaces(); - return currentWktPos < wkt.length(); - } - - private void skipWhiteSpaces() { - while (currentWktPos < wkt.length() && Character.isWhitespace(wkt.charAt(currentWktPos))) { - currentWktPos++; - } - } - - private String getNextStringToken() { - skipWhiteSpaces(); - int endIndex = currentWktPos; - while (endIndex < wkt.length() && Character.isLetter(wkt.charAt(endIndex))) { - endIndex++; - } - int temp = currentWktPos; - currentWktPos = endIndex; - skipWhiteSpaces(); - - return wkt.substring(temp, endIndex); - } - - private void populateStructures() { - if (pointList.size() > 0) { - points = new double[pointList.size() * 2]; - - for (int i = 0; i < pointList.size(); i++) { - points[i * 2] = pointList.get(i).getX(); - points[i * 2 + 1] = pointList.get(i).getY(); - } - - if (hasZvalues) { - zValues = new double[pointList.size()]; - for (int i = 0; i < pointList.size(); i++) { - zValues[i] = pointList.get(i).getZ(); - } - } - - if (hasMvalues) { - mValues = new double[pointList.size()]; - for (int i = 0; i < pointList.size(); i++) { - mValues[i] = pointList.get(i).getM(); - } - } - } - - // if version is 2, then we need to check for potential shapes (polygon & multi-shapes) that were - // given their figure attributes as if it was version 1, since we don't know what would be the - // version of the geometry/geography before we parse the entire WKT. - if (version == 2) { - for (int i = 0; i < version_one_shape_indexes.size(); i++) { - figureList.get(version_one_shape_indexes.get(i)).setFiguresAttribute((byte) 1); - } - } - - if (figureList.size() > 0) { - figures = new Figure[figureList.size()]; - - for (int i = 0; i < figureList.size(); i++) { - figures[i] = figureList.get(i); - } - } - - if (shapeList.size() > 0) { - shapes = new Shape[shapeList.size()]; - - for (int i = 0; i < shapeList.size(); i++) { - shapes[i] = shapeList.get(i); - } - } - - if (segmentList.size() > 0) { - segments = new Segment[segmentList.size()]; - - for (int i = 0; i < segmentList.size(); i++) { - segments[i] = segmentList.get(i); - } - } - - numberOfPoints = pointList.size(); - numberOfFigures = figureList.size(); - numberOfShapes = shapeList.size(); - numberOfSegments = segmentList.size(); - } } - -class Figure { - private byte figuresAttribute; - private int pointOffset; - - Figure(byte figuresAttribute, int pointOffset) { - this.figuresAttribute = figuresAttribute; - this.pointOffset = pointOffset; - } - - public byte getFiguresAttribute() { - return figuresAttribute; - } - - public int getPointOffset() { - return pointOffset; - } - - public void setFiguresAttribute(byte fa) { - figuresAttribute = fa; - } -} - -class Shape { - private int parentOffset; - private int figureOffset; - private byte openGISType; - - Shape(int parentOffset, int figureOffset, byte openGISType) { - this.parentOffset = parentOffset; - this.figureOffset = figureOffset; - this.openGISType = openGISType; - } - - public int getParentOffset() { - return parentOffset; - } - - public int getFigureOffset() { - return figureOffset; - } - - public byte getOpenGISType() { - return openGISType; - } -} - -class Segment { - private byte segmentType; - - Segment(byte segmentType) { - this.segmentType = segmentType; - } - - public byte getSegmentType() { - return segmentType; - } -} - -class Point { - private final double x; - private final double y; - private final double z; - private final double m; - - Point(double x, double y, double z, double m) { - this.x = x; - this.y = y; - this.z = z; - this.m = m; - } - - public double getX() { - return x; - } - - public double getY() { - return y; - } - - public double getZ() { - return z; - } - - public double getM() { - return m; - } -} \ No newline at end of file diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/Parameter.java b/src/main/java/com/microsoft/sqlserver/jdbc/Parameter.java index 65ed72f09..a2d04a591 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/Parameter.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/Parameter.java @@ -889,6 +889,10 @@ else if ((null != jdbcTypeSetByUser) && ((jdbcTypeSetByUser == JDBCType.NVARCHAR case GEOMETRY: param.typeDefinition = SSType.GEOMETRY.toString(); break; + + case GEOGRAPHY: + param.typeDefinition = SSType.GEOGRAPHY.toString(); + break; default: assert false : "Unexpected JDBC type " + dtv.getJdbcType(); break; diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java index b7451bd89..2b94e8c30 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java @@ -1575,6 +1575,15 @@ public final void setGeometry(int n, setValue(n, JDBCType.GEOMETRY, x, JavaType.STRING, false); loggerExternal.exiting(getClassNameLogging(), "setGeometry"); } + + public final void setGeography(int n, + Geography x) throws SQLServerException { + if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) + loggerExternal.entering(getClassNameLogging(), "setGeography", new Object[] {n, x}); + checkClosed(); + setValue(n, JDBCType.GEOGRAPHY, x, JavaType.STRING, false); + loggerExternal.exiting(getClassNameLogging(), "setGeography"); + } public final void setInt(int n, int value) throws SQLServerException { diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java index c5ce92075..87316e96e 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java @@ -2142,6 +2142,22 @@ public Geometry getGeometry(String columnName) throws SQLServerException { loggerExternal.exiting(getClassNameLogging(), "getFloat", value); return value; } + + public Geography getGeography(int columnIndex) throws SQLServerException { + loggerExternal.entering(getClassNameLogging(), "getFloat", columnIndex); + checkClosed(); + Geography value = (Geography) getValue(columnIndex, JDBCType.GEOGRAPHY); + loggerExternal.exiting(getClassNameLogging(), "getFloat", value); + return value; + } + + public Geography getGeography(String columnName) throws SQLServerException { + loggerExternal.entering(getClassNameLogging(), "getFloat", columnName); + checkClosed(); + Geography value = (Geography) getValue(findColumn(columnName), JDBCType.GEOGRAPHY); + loggerExternal.exiting(getClassNameLogging(), "getFloat", value); + return value; + } public int getInt(int columnIndex) throws SQLServerException { loggerExternal.entering(getClassNameLogging(), "getInt", columnIndex); diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerSpatialDatatype.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerSpatialDatatype.java new file mode 100644 index 000000000..6dbb7e4cf --- /dev/null +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerSpatialDatatype.java @@ -0,0 +1,1282 @@ +package com.microsoft.sqlserver.jdbc; + +import java.math.BigDecimal; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +abstract class SQLServerSpatialDatatype { + protected ByteBuffer buffer; + protected InternalSpatialDatatype internalType; + protected String wkt; + protected String wktNoZM; + protected byte[] wkb; + protected byte[] wkbNoZM; + protected int srid; + protected byte version = 1; + protected int numberOfPoints; + protected int numberOfFigures; + protected int numberOfShapes; + protected int numberOfSegments; + protected StringBuffer WKTsb; + protected StringBuffer WKTsbNoZM; + protected int currentPointIndex = 0; + protected int currentFigureIndex = 0; + protected int currentSegmentIndex = 0; + protected double points[]; + protected double zValues[]; + protected double mValues[]; + protected Figure figures[]; + protected Shape shapes[]; + protected Segment segments[]; + + //serialization properties + protected boolean hasZvalues = false; + protected boolean hasMvalues = false; + protected boolean isValid = false; + protected boolean isSinglePoint = false; + protected boolean isSingleLineSegment = false; + protected boolean isLargerThanHemisphere = false; + protected boolean isNull = true; + + protected final byte FA_INTERIOR_RING = 0; + protected final byte FA_STROKE = 1; + protected final byte FA_EXTERIOR_RING = 2; + + protected final byte FA_POINT = 0; + protected final byte FA_LINE = 1; + protected final byte FA_ARC = 2; + protected final byte FA_COMPOSITE_CURVE = 3; + + // WKT to WKB properties + protected int currentWktPos = 0; + protected List pointList = new ArrayList(); + protected List
figureList = new ArrayList
(); + protected List shapeList = new ArrayList(); + protected List segmentList = new ArrayList(); + + private int currentShapeIndex = 0; + private byte serializationProperties = 0; + + private final byte SEGMENT_LINE = 0; + private final byte SEGMENT_ARC = 1; + private final byte SEGMENT_FIRST_LINE = 2; + private final byte SEGMENT_FIRST_ARC = 3; + + private final byte hasZvaluesMask = 0b00000001; + private final byte hasMvaluesMask = 0b00000010; + private final byte isValidMask = 0b00000100; + private final byte isSinglePointMask = 0b00001000; + private final byte isSingleLineSegmentMask = 0b00010000; + private final byte isLargerThanHemisphereMask = 0b00100000; + + private List version_one_shape_indexes = new ArrayList(); + + protected abstract void constructWKT(InternalSpatialDatatype isd, int pointIndexEnd, + int figureIndexEnd, int segmentIndexEnd, int shapeIndexEnd); + + protected abstract void parseWKTForSerialization(int startPos, int parentShapeIndex, boolean isGeoCollection); + + 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(); + } + + //TODO: do I need to do anything when it's isSinglePoint or isSingleLineSegment? + if (!(isSinglePoint || isSingleLineSegment)) { + readNumberOfFigures(); + + readFigures(); + + readNumberOfShapes(); + + readShapes(); + } + + determineInternalType(); + + if (version == 2 && internalType.getTypeCode() != 8 && internalType.getTypeCode() != 11) { + readNumberOfSegments(); + + readSegments(); + } + } + + 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]); + } else { + appendToWKTBuffers(points[firstPointIndex]); + } + appendToWKTBuffers(" "); + + if (points[secondPointIndex] % 1 == 0) { + appendToWKTBuffers((int) points[secondPointIndex]); + } else { + appendToWKTBuffers(points[secondPointIndex]); + } + appendToWKTBuffers(" "); + + if (hasZvalues && !Double.isNaN(zValues[zValueIndex])) { + if (zValues[zValueIndex] % 1 == 0) { + WKTsb.append((int) zValues[zValueIndex]); + } else { + WKTsb.append(zValues[zValueIndex]); + } + WKTsb.append(" "); + + if (hasMvalues && !Double.isNaN(mValues[mValueIndex])) { + if (mValues[mValueIndex] % 1 == 0) { + WKTsb.append((int) mValues[mValueIndex]); + } else { + WKTsb.append(mValues[mValueIndex]); + } + WKTsb.append(" "); + } + } + + currentPointIndex++; + // truncate last space + WKTsb.setLength(WKTsb.length() - 1); + WKTsbNoZM.setLength(WKTsbNoZM.length() - 1); + } + + protected void constructLineWKT(int pointStartIndex, int pointEndIndex) { + for (int i = pointStartIndex; i < pointEndIndex; i++) { + constructPointWKT(i); + + // add ', ' to separate points, except for the last point + if (i != pointEndIndex - 1) { + appendToWKTBuffers(", "); + } + } + } + + protected void constructShapeWKT(int figureStartIndex, int figureEndIndex) { + // Method for constructing shapes (simple Geometry/Geography entities that are contained within a single bracket) + for (int i = figureStartIndex; i < figureEndIndex; i++) { + appendToWKTBuffers("("); + if (i != numberOfFigures - 1) { //not the last figure + constructLineWKT(figures[i].getPointOffset(), figures[i + 1].getPointOffset()); + } else { + constructLineWKT(figures[i].getPointOffset(), numberOfPoints); + } + + if (i != figureEndIndex - 1) { + appendToWKTBuffers("), "); + } else { + appendToWKTBuffers(")"); + } + } + } + + protected void constructCompoundcurveWKT(int segmentStartIndex, int segmentEndIndex, int pointEndIndex) { + for (int i = segmentStartIndex; i < segmentEndIndex; i++) { + byte segment = segments[i].getSegmentType(); + constructSegmentWKT(i, segment, pointEndIndex); + + if (i == segmentEndIndex - 1) { + appendToWKTBuffers(")"); + break; + } + + switch (segment) { + case 0: + case 2: + if (segments[i + 1].getSegmentType() != 0) { + appendToWKTBuffers("), "); + } + break; + case 1: + case 3: + if (segments[i + 1].getSegmentType() != 1) { + appendToWKTBuffers("), "); + } + break; + default: + return; + } + } + } + + protected void constructMultipolygonWKT(int figureStartIndex, int figureEndIndex) { + for (int i = figureStartIndex; i < figureEndIndex; i++) { + if (figures[i].getFiguresAttribute() == 2) { // exterior ring + appendToWKTBuffers("(("); + } else { // interior ring + appendToWKTBuffers("("); + } + + if (i == figures.length - 1) { // last figure + constructLineWKT(figures[i].getPointOffset(), numberOfPoints); + } else { + constructLineWKT(figures[i].getPointOffset(), figures[i + 1].getPointOffset()); + } + + if (i == figureEndIndex - 1) { // last polygon of this multipolygon, close off the Multipolygon and return + appendToWKTBuffers("))"); + return; + } else if (figures[i + 1].getFiguresAttribute() == 2) { // not the last polygon, followed by an exterior ring + appendToWKTBuffers(")), "); + } else { // not the last polygon, followed by an interior ring + appendToWKTBuffers("), "); + } + } + } + + protected void constructCurvepolygonWKT(int figureStartIndex, int figureEndIndex, int segmentStartIndex, int segmentEndIndex) { + for (int i = figureStartIndex; i < figureEndIndex; i++) { + switch (figures[i].getFiguresAttribute()) { + case 1: // line + appendToWKTBuffers("("); + + if (i == figures.length - 1) { + constructLineWKT(currentPointIndex, numberOfPoints); + } else { + constructLineWKT(currentPointIndex, figures[i + 1].getPointOffset()); + //currentPointIndex = figures[i + 1].getPointOffset(); + } + + appendToWKTBuffers(")"); + break; + case 2: // arc + appendToWKTBuffers("CIRCULARSTRING("); + + if (i == figures.length - 1) { + constructLineWKT(currentPointIndex, numberOfPoints); + } else { + constructLineWKT(currentPointIndex, figures[i + 1].getPointOffset()); + //currentPointIndex = figures[i + 1].getPointOffset(); + } + + appendToWKTBuffers(")"); + + break; + case 3: // composite curve + appendToWKTBuffers("COMPOUNDCURVE("); + + int pointEndIndex = 0; + + if (i == figures.length - 1) { + pointEndIndex = numberOfPoints; + } else { + pointEndIndex = figures[i + 1].getPointOffset(); + } + + while (currentPointIndex < pointEndIndex) { + byte segment = segments[segmentStartIndex].getSegmentType(); + constructSegmentWKT(segmentStartIndex, segment, pointEndIndex); + + if (segmentStartIndex >= segmentEndIndex - 1) { + appendToWKTBuffers(")"); + // about to exit while loop, but not the last segment = we are closing Compoundcurve. + } else if (!(currentPointIndex < pointEndIndex)) { + appendToWKTBuffers("))"); + } else { + switch (segment) { + case 0: + case 2: + if (segments[segmentStartIndex + 1].getSegmentType() != 0) { + appendToWKTBuffers("), "); + } + break; + case 1: + case 3: + if (segments[segmentStartIndex + 1].getSegmentType() != 1) { + appendToWKTBuffers("), "); + } + break; + default: + return; + } + } + + segmentStartIndex++; + } + + break; + default: + return; + } + + if (i == figureEndIndex - 1) { + appendToWKTBuffers(")"); + } else { + appendToWKTBuffers(", "); + } + + } + } + + protected void constructSegmentWKT(int currentSegment, byte segment, int pointEndIndex) { + switch (segment) { + case 0: + appendToWKTBuffers(", "); + constructLineWKT(currentPointIndex, currentPointIndex + 1); + + if (currentSegment == segments.length - 1) { // last segment + break; + } else if (segments[currentSegment + 1].getSegmentType() != 0) { // not being followed by another line, but not the last segment + currentPointIndex = currentPointIndex - 1; + incrementPointNumStartIfPointNotReused(pointEndIndex); + } + break; + + case 1: + appendToWKTBuffers(", "); + constructLineWKT(currentPointIndex, currentPointIndex + 2); + + if (currentSegment == segments.length - 1) { // last segment + break; + } else if (segments[currentSegment + 1].getSegmentType() != 1) { // not being followed by another arc, but not the last segment + currentPointIndex = currentPointIndex - 1; // only increment pointNumStart by one less than what we should be, since the last point will be reused + incrementPointNumStartIfPointNotReused(pointEndIndex); + } + + break; + case 2: + appendToWKTBuffers("("); + constructLineWKT(currentPointIndex, currentPointIndex + 2); + + if (currentSegment == segments.length - 1) { // last segment + break; + } else if (segments[currentSegment + 1].getSegmentType() != 0) { // not being followed by another line, but not the last segment + currentPointIndex = currentPointIndex - 1; // only increment pointNumStart by one less than what we should be, since the last point will be reused + incrementPointNumStartIfPointNotReused(pointEndIndex); + } + + break; + case 3: + appendToWKTBuffers("CIRCULARSTRING("); + constructLineWKT(currentPointIndex, currentPointIndex + 3); + + if (currentSegment == segments.length - 1) { // last segment + break; + } else if (segments[currentSegment + 1].getSegmentType() != 1) { // not being followed by another arc + currentPointIndex = currentPointIndex - 1; // only increment pointNumStart by one less than what we should be, since the last point will be reused + incrementPointNumStartIfPointNotReused(pointEndIndex); + } + + break; + default: + return; + } + } + + protected void constructGeometryCollectionWKT(int shapeEndIndex) { + currentShapeIndex++; + constructGeometryCollectionWKThelper(shapeEndIndex); + } + + protected void readPointWkt() { + int numOfCoordinates = 0; + double sign; + double coords[] = new double[4]; + + while (numOfCoordinates < 4) { + sign = 1; + if (wkt.charAt(currentWktPos) == '-') { + sign = -1; + currentWktPos++; + } + + int startPos = currentWktPos; + + if (wkt.charAt(currentWktPos) == ')') { + break; + } + + while (currentWktPos < wkt.length() && + (Character.isDigit(wkt.charAt(currentWktPos)) + || wkt.charAt(currentWktPos) == '.' + || wkt.charAt(currentWktPos) == 'E' + || wkt.charAt(currentWktPos) == 'e')) { + currentWktPos++; + } + + try { + coords[numOfCoordinates] = sign * + new BigDecimal(wkt.substring(startPos, currentWktPos)).doubleValue(); + } catch (Exception e) { //modify to conversion exception + throw new IllegalArgumentException(); + } + + skipWhiteSpaces(); + if (wkt.charAt(currentWktPos) == ',') { + currentWktPos++; + skipWhiteSpaces(); + numOfCoordinates++; + break; + } + skipWhiteSpaces(); + + numOfCoordinates++; + } + + 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])); + } + + protected void readLineWkt() { + while (currentWktPos < wkt.length() && wkt.charAt(currentWktPos) != ')') { + readPointWkt(); + } + } + + protected void readShapeWkt(int parentShapeIndex, String nextToken) { + byte fa = FA_POINT; + while (currentWktPos < wkt.length() && wkt.charAt(currentWktPos) != ')') { + if (nextToken.equals("MULTIPOINT")) { + shapeList.add(new Shape(parentShapeIndex, figureList.size(), InternalSpatialDatatype.POINT.getTypeCode())); + } else if (nextToken.equals("MULTILINESTRING")) { + shapeList.add(new Shape(parentShapeIndex, figureList.size(), InternalSpatialDatatype.LINESTRING.getTypeCode())); + } + + if (version == 1) { + if (nextToken.equals("MULTIPOINT")) { + fa = FA_STROKE; + } else if (nextToken.equals("MULTILINESTRING") || nextToken.equals("POLYGON")) { + fa = FA_EXTERIOR_RING; + } + version_one_shape_indexes.add(figureList.size()); + } else if (version == 2) { + if (nextToken.equals("MULTIPOINT") || nextToken.equals("MULTILINESTRING") || + nextToken.equals("POLYGON") || nextToken.equals("MULTIPOLYGON")) { + fa = FA_LINE; + } + } + + figureList.add(new Figure(fa, pointList.size())); + readOpenBracket(); + readLineWkt(); + readCloseBracket(); + + skipWhiteSpaces(); + + if (wkt.charAt(currentWktPos) == ',') { // more rings to follow + readComma(); + } else if (wkt.charAt(currentWktPos) == ')') { // about to exit while loop + continue; + } else { // unexpected input + throw new IllegalArgumentException(); + } + } + } + + protected void readCurvePolygon() { + while (currentWktPos < wkt.length() && wkt.charAt(currentWktPos) != ')') { + String nextPotentialToken = getNextStringToken().toUpperCase(Locale.US); + if (nextPotentialToken.equals("CIRCULARSTRING")) { + figureList.add(new Figure(FA_ARC, pointList.size())); + readOpenBracket(); + readLineWkt(); + readCloseBracket(); + } else if (nextPotentialToken.equals("COMPOUNDCURVE")) { + figureList.add(new Figure(FA_COMPOSITE_CURVE, pointList.size())); + readOpenBracket(); + readCompoundCurveWkt(true); + readCloseBracket(); + } else if (wkt.charAt(currentWktPos) == '(') { //LineString + figureList.add(new Figure(FA_LINE, pointList.size())); + readOpenBracket(); + readLineWkt(); + readCloseBracket(); + } else { + throw new IllegalArgumentException(); + } + + if (wkt.charAt(currentWktPos) == ',') { // more polygons to follow + readComma(); + } else if (wkt.charAt(currentWktPos) == ')') { // about to exit while loop + continue; + } else { // unexpected input + throw new IllegalArgumentException(); + } + } + } + + protected void readMultiPolygonWkt(int thisShapeIndex, String nextToken) { + while (currentWktPos < wkt.length() && wkt.charAt(currentWktPos) != ')') { + shapeList.add(new Shape(thisShapeIndex, figureList.size(), InternalSpatialDatatype.POLYGON.getTypeCode())); //exterior polygon + readOpenBracket(); + readShapeWkt(thisShapeIndex, nextToken); + readCloseBracket(); + + if (wkt.charAt(currentWktPos) == ',') { // more polygons to follow + readComma(); + } else if (wkt.charAt(currentWktPos) == ')') { // about to exit while loop + continue; + } else { // unexpected input + throw new IllegalArgumentException(); + } + } + } + + + protected void readSegmentWkt(int segmentType, boolean isFirstIteration) { + segmentList.add(new Segment((byte) segmentType)); + + int segmentLength = segmentType; + + // under 2 means 0 or 1 (possible values). 0 (line) has 1 point, and 1 (arc) has 2 points, so increment by one + if (segmentLength < 2) { + segmentLength++; + } + + for (int i = 0; i < segmentLength; i++) { + //If a segment type of 2 (first line) or 3 (first arc) is not from the very first iteration of the while loop, + //then the first point has to be a duplicate point from the previous segment, so skip the first point. + if (i == 0 && !isFirstIteration && segmentType >= 2) { + skipFirstPointWkt(); + } else { + readPointWkt(); + } + } + + if (currentWktPos < wkt.length() && wkt.charAt(currentWktPos) != ')') { + if (segmentType == SEGMENT_FIRST_ARC || segmentType == SEGMENT_ARC) { + readSegmentWkt(SEGMENT_ARC, false); + } else if (segmentType == SEGMENT_FIRST_LINE | segmentType == SEGMENT_LINE) { + readSegmentWkt(SEGMENT_LINE, false); + } + } + } + + protected void readCompoundCurveWkt(boolean isFirstIteration) { + while (currentWktPos < wkt.length() && wkt.charAt(currentWktPos) != ')') { + String nextPotentialToken = getNextStringToken().toUpperCase(Locale.US); + if (nextPotentialToken.equals("CIRCULARSTRING")) { + readOpenBracket(); + readSegmentWkt(SEGMENT_FIRST_ARC, isFirstIteration); + readCloseBracket(); + } else if (wkt.charAt(currentWktPos) == '(') {//LineString + readOpenBracket(); + readSegmentWkt(SEGMENT_FIRST_LINE, isFirstIteration); + readCloseBracket(); + } else { + throw new IllegalArgumentException(); + } + + isFirstIteration = false; + + if (wkt.charAt(currentWktPos) == ',') { // more polygons to follow + readComma(); + } else if (wkt.charAt(currentWktPos) == ')') { // about to exit while loop + continue; + } else { // unexpected input + throw new IllegalArgumentException(); + } + } + } + + protected String getNextStringToken() { + skipWhiteSpaces(); + int endIndex = currentWktPos; + while (endIndex < wkt.length() && Character.isLetter(wkt.charAt(endIndex))) { + endIndex++; + } + int temp = currentWktPos; + currentWktPos = endIndex; + skipWhiteSpaces(); + + return wkt.substring(temp, endIndex); + } + + protected void populateStructures() { + if (pointList.size() > 0) { + points = new double[pointList.size() * 2]; + + for (int i = 0; i < pointList.size(); i++) { + points[i * 2] = pointList.get(i).getX(); + points[i * 2 + 1] = pointList.get(i).getY(); + } + + if (hasZvalues) { + zValues = new double[pointList.size()]; + for (int i = 0; i < pointList.size(); i++) { + zValues[i] = pointList.get(i).getZ(); + } + } + + if (hasMvalues) { + mValues = new double[pointList.size()]; + for (int i = 0; i < pointList.size(); i++) { + mValues[i] = pointList.get(i).getM(); + } + } + } + + // if version is 2, then we need to check for potential shapes (polygon & multi-shapes) that were + // given their figure attributes as if it was version 1, since we don't know what would be the + // version of the geometry/geography before we parse the entire WKT. + if (version == 2) { + for (int i = 0; i < version_one_shape_indexes.size(); i++) { + figureList.get(version_one_shape_indexes.get(i)).setFiguresAttribute((byte) 1); + } + } + + if (figureList.size() > 0) { + figures = new Figure[figureList.size()]; + + for (int i = 0; i < figureList.size(); i++) { + figures[i] = figureList.get(i); + } + } + + if (shapeList.size() > 0) { + shapes = new Shape[shapeList.size()]; + + for (int i = 0; i < shapeList.size(); i++) { + shapes[i] = shapeList.get(i); + } + } + + if (segmentList.size() > 0) { + segments = new Segment[segmentList.size()]; + + for (int i = 0; i < segmentList.size(); i++) { + segments[i] = segmentList.get(i); + } + } + + numberOfPoints = pointList.size(); + numberOfFigures = figureList.size(); + numberOfShapes = shapeList.size(); + numberOfSegments = segmentList.size(); + } + + protected void readOpenBracket() { + skipWhiteSpaces(); + if (wkt.charAt(currentWktPos) == '(') { + currentWktPos++; + skipWhiteSpaces(); + } else { + throw new IllegalArgumentException(); + } + } + + protected void readCloseBracket() { + skipWhiteSpaces(); + if (wkt.charAt(currentWktPos) == ')') { + currentWktPos++; + skipWhiteSpaces(); + } else { + throw new IllegalArgumentException(); + } + } + + protected boolean hasMoreToken() { + skipWhiteSpaces(); + return currentWktPos < wkt.length(); + } + + private void createSerializationProperties() { + serializationProperties = 0; + if (hasZvalues) { + serializationProperties += hasZvaluesMask; + } + + if (hasMvalues) { + serializationProperties += hasMvaluesMask; + } + + if (isValid) { + serializationProperties += isValidMask; + } + + if (isSinglePoint) { + serializationProperties += isSinglePointMask; + } + + if (isSingleLineSegment) { + serializationProperties += isSingleLineSegmentMask; + } + + //TODO look into how the isLargerThanHemisphere is created + if (version == 2) { + if (isLargerThanHemisphere) { + serializationProperties += isLargerThanHemisphereMask; + } + } + } + + private int determineWkbCapacity() { + int totalSize = 0; + + totalSize+=6; // SRID + version + SerializationPropertiesByte + + if (isSinglePoint || isSingleLineSegment) { + totalSize += 16 * numberOfPoints; + + if (hasZvalues) { + totalSize += 8 * numberOfPoints; + } + + if (hasMvalues) { + totalSize += 8 * numberOfPoints; + } + + return totalSize; + } + + int pointSize = 16; + if (hasZvalues) { + pointSize += 8; + } + + if (hasMvalues) { + pointSize += 8; + } + + totalSize += 12; // 4 bytes for 3 ints, each representing the number of points, shapes and figures + totalSize += numberOfPoints * pointSize; + totalSize += numberOfFigures * 5; + totalSize += numberOfShapes * 9; + + if (version == 2) { + totalSize += 4; // 4 bytes for 1 int, representing the number of segments + totalSize += numberOfSegments; + } + + return totalSize; + } + + private void interpretSerializationPropBytes() { + hasZvalues = (serializationProperties & hasZvaluesMask) != 0; + hasMvalues = (serializationProperties & hasMvaluesMask) != 0; + isValid = (serializationProperties & isValidMask) != 0; + isSinglePoint = (serializationProperties & isSinglePointMask) != 0; + isSingleLineSegment = (serializationProperties & isSingleLineSegmentMask) != 0; + isLargerThanHemisphere = (serializationProperties & isLargerThanHemisphereMask) != 0; + } + + private void readNumberOfPoints() { + if (isSinglePoint) { + numberOfPoints = 1; + } else if (isSingleLineSegment) { + numberOfPoints = 2; + } else { + numberOfPoints = buffer.getInt(); + } + } + + 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(); + } + } + + private void readZvalues() { + zValues = new double[numberOfPoints]; + for (int i = 0; i < numberOfPoints; i++) { + zValues[i] = buffer.getDouble(); + } + } + + private void readMvalues() { + mValues = new double[numberOfPoints]; + for (int i = 0; i < numberOfPoints; i++) { + mValues[i] = buffer.getDouble(); + } + } + + private void readNumberOfFigures() { + numberOfFigures = buffer.getInt(); + } + + private void readFigures() { + byte fa; + int po; + figures = new Figure[numberOfFigures]; + for (int i = 0; i < numberOfFigures; i++) { + fa = buffer.get(); + po = buffer.getInt(); + figures[i] = new Figure(fa, po); + } + } + + private void readNumberOfShapes() { + numberOfShapes = buffer.getInt(); + } + + private void readShapes() { + 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(); + shapes[i] = new Shape(po, fo, ogt); + } + } + + private void readNumberOfSegments() { + numberOfSegments = buffer.getInt(); + } + + private void readSegments() { + byte st; + segments = new Segment[numberOfSegments]; + for (int i = 0; i < numberOfSegments; i++) { + st = buffer.get(); + segments[i] = new Segment(st); + } + } + + private void determineInternalType() { + if (isSinglePoint) { + internalType = InternalSpatialDatatype.POINT; + } else if (isSingleLineSegment) { + internalType = InternalSpatialDatatype.LINESTRING; + } else { + internalType = InternalSpatialDatatype.valueOf(shapes[0].getOpenGISType()); + } + } + + 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. + if (currentPointIndex + 1 >= pointEndIndex) { + currentPointIndex++; + } + } + + private void constructGeometryCollectionWKThelper(int shapeEndIndex) { + //phase 1: assume that there is no multi - stuff and no geometrycollection + while (currentShapeIndex < shapeEndIndex) { + InternalSpatialDatatype isd = InternalSpatialDatatype.valueOf(shapes[currentShapeIndex].getOpenGISType()); + + int figureIndex = shapes[currentShapeIndex].getFigureOffset(); + int pointIndexEnd = numberOfPoints; + int figureIndexEnd = numberOfFigures; + int segmentIndexEnd = numberOfSegments; + int shapeIndexEnd = numberOfShapes; + int figureIndexIncrement = 0; + int segmentIndexIncrement = 0; + int localCurrentSegmentIndex = 0; + int localCurrentShapeIndex = 0; + + switch (isd) { + case POINT: + figureIndexIncrement++; + currentShapeIndex++; + break; + case LINESTRING: + case CIRCULARSTRING: + figureIndexIncrement++; + currentShapeIndex++; + pointIndexEnd = figures[figureIndex + 1].getPointOffset(); + break; + case POLYGON: + case CURVEPOLYGON: + if (currentShapeIndex < shapes.length - 1) { + figureIndexEnd = shapes[currentShapeIndex + 1].getFigureOffset(); + } + + figureIndexIncrement = figureIndexEnd - currentFigureIndex; + currentShapeIndex++; + + // Needed to keep track of which segment we are at, inside the for loop + localCurrentSegmentIndex = currentSegmentIndex; + + if (isd.equals(InternalSpatialDatatype.CURVEPOLYGON)) { + // assume Version 2 + + for (int i = currentFigureIndex; i < figureIndexEnd; i++) { + // Only Compoundcurves (with figure attribute 3) can have segments + if (figures[i].getFiguresAttribute() == 3) { + + int pointOffsetEnd; + if (i == figures.length - 1) { + pointOffsetEnd = numberOfPoints; + } else { + pointOffsetEnd = figures[i + 1].getPointOffset(); + } + + int increment = calculateSegmentIncrement(localCurrentSegmentIndex, pointOffsetEnd - figures[i].getPointOffset()); + + segmentIndexIncrement = segmentIndexIncrement + increment; + localCurrentSegmentIndex = localCurrentSegmentIndex + increment; + } + } + } + + segmentIndexEnd = localCurrentSegmentIndex; + + break; + case MULTIPOINT: + case MULTILINESTRING: + case MULTIPOLYGON: + //Multipoint and MultiLineString can go on for multiple Shapes, but eventually + //the parentOffset will signal the end of the object, or it's reached the end of the + //shapes array. + //There is also no possibility that a MultiPoint or MultiLineString would branch + //into another parent. + + int thisShapesParentOffset = shapes[currentShapeIndex].getParentOffset(); + + // Increment shapeStartIndex to account for the shape index that either Multipoint, MultiLineString + // or MultiPolygon takes up + currentShapeIndex++; + while (currentShapeIndex < shapes.length - 1 && + shapes[currentShapeIndex].getParentOffset() != thisShapesParentOffset) { + figureIndexEnd = shapes[currentShapeIndex + 1].getFigureOffset(); + currentShapeIndex++; + } + + figureIndexIncrement = figureIndexEnd - currentFigureIndex; + break; + case GEOMETRYCOLLECTION: + appendToWKTBuffers(isd.getTypeName()); + appendToWKTBuffers("("); + + int geometryCollectionParentIndex = shapes[currentShapeIndex].getParentOffset(); + + // Needed to keep track of which shape we are at, inside the for loop + localCurrentShapeIndex = currentShapeIndex; + + + while (localCurrentShapeIndex < shapes.length - 1 && + shapes[localCurrentShapeIndex + 1].getParentOffset() > geometryCollectionParentIndex) { + localCurrentShapeIndex++; + } + // increment localCurrentShapeIndex one more time since it will be used as a shapeEndIndex parameter + // for constructGeometryCollectionWKT, and the shapeEndIndex parameter is used non-inclusively + localCurrentShapeIndex++; + + currentShapeIndex++; + constructGeometryCollectionWKThelper(localCurrentShapeIndex); + + if (currentShapeIndex < shapeEndIndex) { + appendToWKTBuffers("), "); + } else { + appendToWKTBuffers(")"); + } + + continue; + case COMPOUNDCURVE: + if (currentFigureIndex == figures.length - 1) { + pointIndexEnd = numberOfPoints; + } else { + pointIndexEnd = figures[currentFigureIndex + 1].getPointOffset(); + } + + int increment = calculateSegmentIncrement(currentSegmentIndex, pointIndexEnd - + figures[currentFigureIndex].getPointOffset()); + + segmentIndexIncrement = increment; + segmentIndexEnd = currentSegmentIndex + increment; + figureIndexIncrement++; + currentShapeIndex++; + break; + case FULLGLOBE: + appendToWKTBuffers("FULLGLOBE"); + break; + default: + break; + } + + constructWKT(isd, pointIndexEnd, figureIndexEnd, segmentIndexEnd, shapeIndexEnd); + currentFigureIndex = currentFigureIndex + figureIndexIncrement; + currentSegmentIndex = currentSegmentIndex + segmentIndexIncrement; + + if (currentShapeIndex < shapeEndIndex) { + appendToWKTBuffers(", "); + } + } + } + + //Calculates how many segments will be used by this CompoundCurve + private int calculateSegmentIncrement(int segmentStart, + int pointDifference) { + + int segmentIncrement = 0; + + while (pointDifference > 0) { + switch (segments[segmentStart].getSegmentType()) { + case 0: + pointDifference = pointDifference - 1; + + if (segmentStart == segments.length - 1 || pointDifference < 1) { // last segment + break; + } else if (segments[segmentStart + 1].getSegmentType() != 0) { // one point will be reused + pointDifference = pointDifference + 1; + } + break; + case 1: + pointDifference = pointDifference - 2; + + if (segmentStart == segments.length - 1 || pointDifference < 1) { // last segment + break; + } else if (segments[segmentStart + 1].getSegmentType() != 1) { // one point will be reused + pointDifference = pointDifference + 1; + } + break; + case 2: + pointDifference = pointDifference - 2; + + if (segmentStart == segments.length - 1 || pointDifference < 1) { // last segment + break; + } else if (segments[segmentStart + 1].getSegmentType() != 0) { // one point will be reused + pointDifference = pointDifference + 1; + } + break; + case 3: + pointDifference = pointDifference - 3; + + if (segmentStart == segments.length - 1 || pointDifference < 1) { // last segment + break; + } else if (segments[segmentStart + 1].getSegmentType() != 1) { // one point will be reused + pointDifference = pointDifference + 1; + } + break; + default: + return segmentIncrement; + } + segmentStart++; + segmentIncrement++; + } + + return segmentIncrement; + } + + private void skipFirstPointWkt() { + int numOfCoordinates = 0; + + while (numOfCoordinates < 4) { + if (wkt.charAt(currentWktPos) == '-') { + currentWktPos++; + } + + if (wkt.charAt(currentWktPos) == ')') { + break; + } + + while (currentWktPos < wkt.length() && + (Character.isDigit(wkt.charAt(currentWktPos)) + || wkt.charAt(currentWktPos) == '.' + || wkt.charAt(currentWktPos) == 'E' + || wkt.charAt(currentWktPos) == 'e')) { + currentWktPos++; + } + + skipWhiteSpaces(); + if (wkt.charAt(currentWktPos) == ',') { + currentWktPos++; + skipWhiteSpaces(); + numOfCoordinates++; + break; + } + skipWhiteSpaces(); + + numOfCoordinates++; + } + } + + private void readComma() { + skipWhiteSpaces(); + if (wkt.charAt(currentWktPos) == ',') { + currentWktPos++; + skipWhiteSpaces(); + } else { + throw new IllegalArgumentException(); + } + } + + private void skipWhiteSpaces() { + while (currentWktPos < wkt.length() && Character.isWhitespace(wkt.charAt(currentWktPos))) { + currentWktPos++; + } + } + + protected void appendToWKTBuffers(Object o) { + WKTsb.append(o); + WKTsbNoZM.append(o); + } +} + + +class Figure { + private byte figuresAttribute; + private int pointOffset; + + Figure(byte figuresAttribute, int pointOffset) { + this.figuresAttribute = figuresAttribute; + this.pointOffset = pointOffset; + } + + public byte getFiguresAttribute() { + return figuresAttribute; + } + + public int getPointOffset() { + return pointOffset; + } + + public void setFiguresAttribute(byte fa) { + figuresAttribute = fa; + } +} + +class Shape { + private int parentOffset; + private int figureOffset; + private byte openGISType; + + Shape(int parentOffset, int figureOffset, byte openGISType) { + this.parentOffset = parentOffset; + this.figureOffset = figureOffset; + this.openGISType = openGISType; + } + + public int getParentOffset() { + return parentOffset; + } + + public int getFigureOffset() { + return figureOffset; + } + + public byte getOpenGISType() { + return openGISType; + } +} + +class Segment { + private byte segmentType; + + Segment(byte segmentType) { + this.segmentType = segmentType; + } + + public byte getSegmentType() { + return segmentType; + } +} + +class Point { + private final double x; + private final double y; + private final double z; + private final double m; + + Point(double x, double y, double z, double m) { + this.x = x; + this.y = y; + this.z = z; + this.m = m; + } + + public double getX() { + return x; + } + + public double getY() { + return y; + } + + public double getZ() { + return z; + } + + public double getM() { + return m; + } +} \ No newline at end of file diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/dtv.java b/src/main/java/com/microsoft/sqlserver/jdbc/dtv.java index 447ec0549..64906464e 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/dtv.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/dtv.java @@ -1632,7 +1632,10 @@ else if (JDBCType.SQL_VARIANT == jdbcType) { op.execute(this, String.valueOf(value)); } else if (JDBCType.GEOMETRY == jdbcType) { - op.execute(this, ((Geometry) value).getWkb()); + op.execute(this, ((Geometry) value).serialize()); + } + else if (JDBCType.GEOGRAPHY == jdbcType) { + op.execute(this, ((Geography) value).serialize()); } else { if (null != cryptoMeta) { diff --git a/src/main/java/microsoft/sql/Types.java b/src/main/java/microsoft/sql/Types.java index ecd7eddca..b2e7cb5d0 100644 --- a/src/main/java/microsoft/sql/Types.java +++ b/src/main/java/microsoft/sql/Types.java @@ -58,5 +58,13 @@ private Types() { */ public static final int SQL_VARIANT = -156; + /** + * The constant in the Java programming language, sometimes referred to as a type code, that identifies the Microsoft SQL type GEOMETRY. + */ public static final int GEOMETRY = -157; + + /** + * The constant in the Java programming language, sometimes referred to as a type code, that identifies the Microsoft SQL type GEOGRAPHY. + */ + public static final int GEOGRAPHY = -158; } From 07d0a9f553d98a0ec35909819d010e9a1006afec Mon Sep 17 00:00:00 2001 From: Peter Bae Date: Wed, 1 Nov 2017 15:46:23 -0700 Subject: [PATCH 08/41] Handle Empty cases and added sanity testing for spatial datatypes --- .../com/microsoft/sqlserver/jdbc/DDC.java | 2 +- .../microsoft/sqlserver/jdbc/Geography.java | 322 +++++---- .../microsoft/sqlserver/jdbc/Geometry.java | 303 ++++---- .../jdbc/SQLServerSpatialDatatype.java | 681 +++++++++++++----- .../SQLServerSpatialDatatypeTest.java | 458 ++++++++++++ 5 files changed, 1328 insertions(+), 438 deletions(-) create mode 100644 src/test/java/com/microsoft/sqlserver/jdbc/datatypes/SQLServerSpatialDatatypeTest.java diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/DDC.java b/src/main/java/com/microsoft/sqlserver/jdbc/DDC.java index 9ec99c7ba..cedc5a459 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/DDC.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/DDC.java @@ -617,7 +617,7 @@ static final Object convertStreamToObject(BaseInputStream stream, } else if (JDBCType.GEOMETRY == jdbcType) { return Geometry.STGeomFromWKB(byteValue); } else if (JDBCType.GEOGRAPHY == jdbcType) { - return new Geography(byteValue); + return Geography.STGeomFromWKB(byteValue); } else { String hexString = Util.bytesToHexString(byteValue, byteValue.length); diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/Geography.java b/src/main/java/com/microsoft/sqlserver/jdbc/Geography.java index 25020f379..2c79a0a0d 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/Geography.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/Geography.java @@ -1,3 +1,11 @@ +/* + * Microsoft JDBC Driver for SQL Server + * + * Copyright(c) Microsoft Corporation All rights reserved. + * + * This program is made available under the terms of the MIT License. See the LICENSE file in the project root for more information. + */ + package com.microsoft.sqlserver.jdbc; import java.nio.ByteBuffer; @@ -5,16 +13,23 @@ import java.util.Locale; public class Geography extends SQLServerSpatialDatatype { - public Geography(String WellKnownText, int srid) { + + /** + * Private constructor used for creating a Geography object from WKT and srid. + */ + private Geography(String WellKnownText, int srid) { this.wkt = WellKnownText; this.srid = srid; - parseWKTForSerialization(currentWktPos, -1, false); + parseWKTForSerialization(this, currentWktPos, -1, false); serializeToWkb(false); isNull = false; } - public Geography(byte[] wkb) { + /** + * Private constructor used for creating a Geography object from WKB. + */ + private Geography(byte[] wkb) { this.wkb = wkb; buffer = ByteBuffer.wrap(wkb); buffer.order(ByteOrder.LITTLE_ENDIAN); @@ -24,7 +39,7 @@ public Geography(byte[] wkb) { WKTsb = new StringBuffer(); WKTsbNoZM = new StringBuffer(); - constructWKT(internalType, numberOfPoints, numberOfFigures, numberOfSegments, numberOfShapes); + constructWKT(this, internalType, numberOfPoints, numberOfFigures, numberOfSegments, numberOfShapes); wkt = WKTsb.toString(); wktNoZM = WKTsbNoZM.toString(); @@ -35,26 +50,67 @@ public Geography() { // TODO Auto-generated constructor stub } + /** + * 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. + * + * @param wkt + * @param srid + * @return + */ public static Geography STGeomFromText(String wkt, int srid) { return new Geography(wkt, srid); } + /** + * Returns a Geography instance from an Open Geospatial Consortium (OGC) + * Well-Known Binary (WKB) representation. + * + * @param wkb + * @return + */ public static Geography STGeomFromWKB(byte[] wkb) { return new Geography(wkb); } + /** + * Returns a constructed Geography from an internal SQL Server format for spatial data. + * + * @param wkb + * @return + */ public static Geography deserialize(byte[] wkb) { return new Geography(wkb); } + /** + * Returns a Geography instance from an Open Geospatial Consortium (OGC) Well-Known Text (WKT) representation. + * + * @param wkt + * @return + */ public static Geography parse(String wkt) { return new Geography(wkt, 0); } + /** + * Constructs a Geography instance that represents a Point instance from its X and Y values and an SRID. + * + * @param x + * @param y + * @param srid + * @return + */ public static Geography point(double x, double y, int srid) { return new Geography("POINT (" + x + " " + y + ")", srid); } - + + /** + * Returns the Open Geospatial Consortium (OGC) Well-Known Text (WKT) representation of a + * Geography instance. This text will not contain any Z (elevation) or M (measure) values carried by the instance. + * + * @return the WKT representation without the Z and M values. + */ public String STAsText() { if (null == wktNoZM) { buffer = ByteBuffer.wrap(wkb); @@ -64,12 +120,17 @@ public String STAsText() { WKTsb = new StringBuffer(); WKTsbNoZM = new StringBuffer(); - constructWKT(internalType, numberOfPoints, numberOfFigures, numberOfSegments, numberOfShapes); + constructWKT(this, internalType, numberOfPoints, numberOfFigures, numberOfSegments, numberOfShapes); wktNoZM = WKTsbNoZM.toString(); } return wktNoZM; } + /** + * Returns the Open Geospatial Consortium (OGC) Well-Known Binary (WKB) representation of a + * Geography instance. This value will not contain any Z or M values carried by the instance. + * @return + */ public byte[] STAsBinary() { if (null == wkbNoZM) { serializeToWkb(true); @@ -77,6 +138,11 @@ public byte[] STAsBinary() { return wkbNoZM; } + /** + * Returns the bytes that represent an internal SQL Server format of Geography type. + * + * @return + */ public byte[] serialize() { return wkb; } @@ -129,6 +195,11 @@ public int STNumPoints() { return numberOfPoints; } + /** + * Returns the Open Geospatial Consortium (OGC) type name represented by a Geography instance. + * + * @return + */ public String STGeographyType() { if (null != internalType) { return internalType.getTypeName(); @@ -143,158 +214,119 @@ public String asTextZM() { public String toString() { return wkt; } - - protected void constructWKT(InternalSpatialDatatype isd, int pointIndexEnd, int figureIndexEnd, int segmentIndexEnd, int shapeIndexEnd) { - if (null == points || numberOfPoints == 0) { - if (isd.getTypeCode() == 11) { // FULLGLOBE - appendToWKTBuffers("FULLGLOBE"); - return; - } - appendToWKTBuffers(internalType + " EMPTY"); - return; - } - - appendToWKTBuffers(isd.getTypeName()); - appendToWKTBuffers("("); - switch (isd) { - case POINT: - constructPointWKT(currentPointIndex); - break; - case LINESTRING: - case CIRCULARSTRING: - constructLineWKT(currentPointIndex, pointIndexEnd); - break; - case POLYGON: - case MULTIPOINT: - case MULTILINESTRING: - constructShapeWKT(currentFigureIndex, figureIndexEnd); - break; - case COMPOUNDCURVE: - constructCompoundcurveWKT(currentSegmentIndex, segmentIndexEnd, pointIndexEnd); - break; - case MULTIPOLYGON: - constructMultipolygonWKT(currentFigureIndex, figureIndexEnd); - break; - case GEOMETRYCOLLECTION: - constructGeometryCollectionWKT(shapeIndexEnd); - break; - case CURVEPOLYGON: - constructCurvepolygonWKT(currentFigureIndex, figureIndexEnd, currentSegmentIndex, segmentIndexEnd); - break; - default: - break; + 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); } - appendToWKTBuffers(")"); - } - - protected void parseWKTForSerialization(int startPos, int parentShapeIndex, boolean isGeoCollection) { - //after every iteration of this while loop, the currentWktPosition will be set to the - //end of the geometry/geography shape, except for the very first iteration of it. - //This means that there has to be comma (that separates the previous shape with the next shape), - //or we expect a ')' that will close the entire shape and exit the method. + for (int i = 0; i < numberOfPoints; i++) { + buf.putDouble(points[2 * i + 1]); + buf.putDouble(points[2 * i]); + } - parse: while (hasMoreToken()) { - if (startPos != 0) { - if (wkt.charAt(currentWktPos) == ')') { - return; - } else if (wkt.charAt(currentWktPos) == ',') { - currentWktPos++; + if (!noZM) { + if (hasZvalues) { + for (int i = 0; i < numberOfPoints; i++) { + buf.putDouble(zValues[i]); } } - - String nextToken = getNextStringToken().toUpperCase(Locale.US); - int thisShapeIndex; - InternalSpatialDatatype isd = InternalSpatialDatatype.valueOf(nextToken); - byte fa = 0; - // check for FULLGLOBE before reading the first open bracket, since FULLGLOBE doesn't have one. - if (nextToken.equals("FULLGLOBE")) { - if (startPos != 0) { - throw new IllegalArgumentException("Illegal character at wkt position " + currentWktPos); + if (hasMvalues) { + for (int i = 0; i < numberOfPoints; i++) { + buf.putDouble(mValues[i]); } - - shapeList.add(new Shape(parentShapeIndex, -1, isd.getTypeCode())); - isLargerThanHemisphere = true; - version = 2; - break parse; } - - readOpenBracket(); - - if (version == 1 && (nextToken.equals("CIRCULARSTRING") || nextToken.equals("COMPOUNDCURVE") || - nextToken.equals("CURVEPOLYGON"))) { - version = 2; + } + + 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(); - switch (nextToken) { - case "POINT": - if (startPos == 0 && nextToken.toUpperCase().equals("POINT")) { - isSinglePoint = true; - } - - if (isGeoCollection) { - shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); - figureList.add(new Figure(FA_LINE, pointList.size())); - } - - readPointWkt(); - break; - case "LINESTRING": - case "CIRCULARSTRING": - shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); - fa = isd.getTypeCode() == InternalSpatialDatatype.LINESTRING.getTypeCode() ? FA_STROKE : FA_EXTERIOR_RING; - figureList.add(new Figure(fa, pointList.size())); - - readLineWkt(); - - if (startPos == 0 && nextToken.toUpperCase().equals("LINESTRING") && pointList.size() == 2) { - isSingleLineSegment = true; - } - break; - case "POLYGON": - case "MULTIPOINT": - case "MULTILINESTRING": - thisShapeIndex = shapeList.size(); - shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); - - readShapeWkt(thisShapeIndex, nextToken); - - break; - case "MULTIPOLYGON": - thisShapeIndex = shapeList.size(); - shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); - - readMultiPolygonWkt(thisShapeIndex, nextToken); - - break; - case "COMPOUNDCURVE": - shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); - figureList.add(new Figure(FA_COMPOSITE_CURVE, pointList.size())); - - readCompoundCurveWkt(true); - - break; - case "CURVEPOLYGON": - shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); + } + return; + } + + protected void parseWkb() { + srid = buffer.getInt(); + version = buffer.get(); + serializationProperties = buffer.get(); + + interpretSerializationPropBytes(); + + readNumberOfPoints(); + + readPoints(); + + if (hasZvalues) { + readZvalues(); + } + + if (hasMvalues) { + readMvalues(); + } + + //TODO: do I need to do anything when it's isSinglePoint or isSingleLineSegment? + if (!(isSinglePoint || isSingleLineSegment)) { + readNumberOfFigures(); + + readFigures(); + + readNumberOfShapes(); + + readShapes(); + } + + determineInternalType(); - readCurvePolygon(); - - break; - case "GEOMETRYCOLLECTION": - thisShapeIndex = shapeList.size(); - shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); - - parseWKTForSerialization(currentWktPos, thisShapeIndex, true); - - break; - default: - throw new IllegalArgumentException("Illegal character at wkt position " + currentWktPos); + if (buffer.hasRemaining()) { + if (version == 2 && internalType.getTypeCode() != 8 && internalType.getTypeCode() != 11) { + readNumberOfSegments(); + + readSegments(); } - readCloseBracket(); } - - populateStructures(); + } + + 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(); + } } } \ No newline at end of file diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java b/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java index da18702d0..0b955f07c 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java @@ -1,3 +1,11 @@ +/* + * Microsoft JDBC Driver for SQL Server + * + * Copyright(c) Microsoft Corporation All rights reserved. + * + * This program is made available under the terms of the MIT License. See the LICENSE file in the project root for more information. + */ + package com.microsoft.sqlserver.jdbc; import java.nio.ByteBuffer; @@ -5,15 +13,22 @@ import java.util.Locale; public class Geometry extends SQLServerSpatialDatatype { + + /** + * Private constructor used for creating a Geometry object from WKT and srid. + */ private Geometry(String WellKnownText, int srid) { this.wkt = WellKnownText; this.srid = srid; - parseWKTForSerialization(currentWktPos, -1, false); + parseWKTForSerialization(this, currentWktPos, -1, false); serializeToWkb(false); isNull = false; } + /** + * Private constructor used for creating a Geometry object from WKB. + */ private Geometry(byte[] wkb) { this.wkb = wkb; buffer = ByteBuffer.wrap(wkb); @@ -24,7 +39,7 @@ private Geometry(byte[] wkb) { WKTsb = new StringBuffer(); WKTsbNoZM = new StringBuffer(); - constructWKT(internalType, numberOfPoints, numberOfFigures, numberOfSegments, numberOfShapes); + constructWKT(this, internalType, numberOfPoints, numberOfFigures, numberOfSegments, numberOfShapes); wkt = WKTsb.toString(); wktNoZM = WKTsbNoZM.toString(); @@ -35,26 +50,67 @@ public Geometry() { // TODO Auto-generated constructor stub } + /** + * Returns 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 + * @param srid + * @return + */ public static Geometry STGeomFromText(String wkt, int srid) { return new Geometry(wkt, srid); } + /** + * Returns a Geometry instance from an Open Geospatial Consortium (OGC) + * Well-Known Binary (WKB) representation. + * + * @param wkb + * @return + */ public static Geometry STGeomFromWKB(byte[] wkb) { return new Geometry(wkb); } + /** + * Returns a constructed Geometry from an internal SQL Server format for spatial data. + * + * @param wkb + * @return + */ public static Geometry deserialize(byte[] wkb) { return new Geometry(wkb); } + /** + * Returns a Geometry instance from an Open Geospatial Consortium (OGC) Well-Known Text (WKT) representation. + * + * @param wkt + * @return + */ public static Geometry parse(String wkt) { return new Geometry(wkt, 0); } + /** + * Constructs a Geometry instance that represents a Point instance from its X and Y values and an SRID. + * + * @param x + * @param y + * @param srid + * @return + */ public static Geometry point(double x, double y, int srid) { return new Geometry("POINT (" + x + " " + y + ")", srid); } + /** + * Returns the Open Geospatial Consortium (OGC) Well-Known Text (WKT) representation of a + * Geometry instance. This text will not contain any Z (elevation) or M (measure) values carried by the instance. + * + * @return the WKT representation without the Z and M values. + */ public String STAsText() { if (null == wktNoZM) { buffer = ByteBuffer.wrap(wkb); @@ -64,12 +120,17 @@ public String STAsText() { WKTsb = new StringBuffer(); WKTsbNoZM = new StringBuffer(); - constructWKT(internalType, numberOfPoints, numberOfFigures, numberOfSegments, numberOfShapes); + constructWKT(this, internalType, numberOfPoints, numberOfFigures, numberOfSegments, numberOfShapes); wktNoZM = WKTsbNoZM.toString(); } return wktNoZM; } + /** + * Returns the Open Geospatial Consortium (OGC) Well-Known Binary (WKB) representation of a + * Geometry instance. This value will not contain any Z or M values carried by the instance. + * @return + */ public byte[] STAsBinary() { if (null == wkbNoZM) { serializeToWkb(true); @@ -77,6 +138,11 @@ public byte[] STAsBinary() { return wkbNoZM; } + /** + * Returns the bytes that represent an internal SQL Server format of Geometry type. + * + * @return + */ public byte[] serialize() { return wkb; } @@ -129,6 +195,11 @@ public int STNumPoints() { return numberOfPoints; } + /** + * Returns the Open Geospatial Consortium (OGC) type name represented by a geometry instance. + * + * @return + */ public String STGeometryType() { if (null != internalType) { return internalType.getTypeName(); @@ -144,146 +215,118 @@ public String toString() { return wkt; } - protected void constructWKT(InternalSpatialDatatype isd, int pointIndexEnd, int figureIndexEnd, int segmentIndexEnd, int shapeIndexEnd) { - if (null == points || numberOfPoints == 0) { - if (isd.getTypeCode() == 11) { // FULLGLOBE - throw new IllegalArgumentException("Fullglobe is not supported for Geometry."); + 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]); + } } - appendToWKTBuffers(internalType + " EMPTY"); + + if (hasMvalues) { + for (int i = 0; i < numberOfPoints; i++) { + buf.putDouble(mValues[i]); + } + } + } + + if (isSinglePoint || isSingleLineSegment) { + wkb = buf.array(); return; } - appendToWKTBuffers(isd.getTypeName()); - appendToWKTBuffers("("); - - switch (isd) { - case POINT: - constructPointWKT(currentPointIndex); - break; - case LINESTRING: - case CIRCULARSTRING: - constructLineWKT(currentPointIndex, pointIndexEnd); - break; - case POLYGON: - case MULTIPOINT: - case MULTILINESTRING: - constructShapeWKT(currentFigureIndex, figureIndexEnd); - break; - case COMPOUNDCURVE: - constructCompoundcurveWKT(currentSegmentIndex, segmentIndexEnd, pointIndexEnd); - break; - case MULTIPOLYGON: - constructMultipolygonWKT(currentFigureIndex, figureIndexEnd); - break; - case GEOMETRYCOLLECTION: - constructGeometryCollectionWKT(shapeIndexEnd); - break; - case CURVEPOLYGON: - constructCurvepolygonWKT(currentFigureIndex, figureIndexEnd, currentSegmentIndex, segmentIndexEnd); - break; - default: - throw new IllegalArgumentException(); + buf.putInt(numberOfFigures); + for (int i = 0; i < numberOfFigures; i++) { + buf.put(figures[i].getFiguresAttribute()); + buf.putInt(figures[i].getPointOffset()); } - appendToWKTBuffers(")"); - } - - protected void parseWKTForSerialization(int startPos, int parentShapeIndex, boolean isGeoCollection) { - //after every iteration of this while loop, the currentWktPosition will be set to the - //end of the geometry/geography shape, except for the very first iteration of it. - //This means that there has to be comma (that separates the previous shape with the next shape), - //or we expect a ')' that will close the entire shape and exit the method. + 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()); + } - while (hasMoreToken()) { - if (startPos != 0) { - if (wkt.charAt(currentWktPos) == ')') { - return; - } else if (wkt.charAt(currentWktPos) == ',') { - currentWktPos++; - } + 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(); - String nextToken = getNextStringToken().toUpperCase(Locale.US); - int thisShapeIndex; - InternalSpatialDatatype isd = InternalSpatialDatatype.valueOf(nextToken); - byte fa = 0; + } + return; + } + + protected void parseWkb() { + srid = buffer.getInt(); + version = buffer.get(); + serializationProperties = buffer.get(); + + interpretSerializationPropBytes(); + + readNumberOfPoints(); + + readPoints(); + + if (hasZvalues) { + readZvalues(); + } + + if (hasMvalues) { + readMvalues(); + } + + //TODO: do I need to do anything when it's isSinglePoint or isSingleLineSegment? + if (!(isSinglePoint || isSingleLineSegment)) { + readNumberOfFigures(); - readOpenBracket(); + readFigures(); - if (version == 1 && (nextToken.equals("CIRCULARSTRING") || nextToken.equals("COMPOUNDCURVE") || - nextToken.equals("CURVEPOLYGON"))) { - version = 2; - } - - switch (nextToken) { - case "POINT": - if (startPos == 0 && nextToken.toUpperCase().equals("POINT")) { - isSinglePoint = true; - } - - if (isGeoCollection) { - shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); - figureList.add(new Figure(FA_LINE, pointList.size())); - } - - readPointWkt(); - break; - case "LINESTRING": - case "CIRCULARSTRING": - shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); - fa = isd.getTypeCode() == InternalSpatialDatatype.LINESTRING.getTypeCode() ? FA_STROKE : FA_EXTERIOR_RING; - figureList.add(new Figure(fa, pointList.size())); - - readLineWkt(); - - if (startPos == 0 && nextToken.toUpperCase().equals("LINESTRING") && pointList.size() == 2) { - isSingleLineSegment = true; - } - break; - case "POLYGON": - case "MULTIPOINT": - case "MULTILINESTRING": - thisShapeIndex = shapeList.size(); - shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); - - readShapeWkt(thisShapeIndex, nextToken); - - break; - case "MULTIPOLYGON": - thisShapeIndex = shapeList.size(); - shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); - - readMultiPolygonWkt(thisShapeIndex, nextToken); - - break; - case "COMPOUNDCURVE": - shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); - figureList.add(new Figure(FA_COMPOSITE_CURVE, pointList.size())); - - readCompoundCurveWkt(true); - - break; - case "CURVEPOLYGON": - shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); + readNumberOfShapes(); + + readShapes(); + } + + determineInternalType(); - readCurvePolygon(); - - break; - case "GEOMETRYCOLLECTION": - thisShapeIndex = shapeList.size(); - shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); - - parseWKTForSerialization(currentWktPos, thisShapeIndex, true); - - break; - case "FULLGLOBE": - throw new IllegalArgumentException("Fullglobe is not supported for Geometry."); - default: - throw new IllegalArgumentException("Illegal character at wkt position " + currentWktPos); + if (buffer.hasRemaining()) { + if (version == 2 && internalType.getTypeCode() != 8 && internalType.getTypeCode() != 11) { + readNumberOfSegments(); + + readSegments(); } - readCloseBracket(); } - - populateStructures(); + } + + 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/SQLServerSpatialDatatype.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerSpatialDatatype.java index 6dbb7e4cf..3b8ff354a 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerSpatialDatatype.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerSpatialDatatype.java @@ -1,3 +1,11 @@ +/* + * Microsoft JDBC Driver for SQL Server + * + * Copyright(c) Microsoft Corporation All rights reserved. + * + * This program is made available under the terms of the MIT License. See the LICENSE file in the project root for more information. + */ + package com.microsoft.sqlserver.jdbc; import java.math.BigDecimal; @@ -7,7 +15,13 @@ import java.util.List; import java.util.Locale; +import com.sun.mail.imap.protocol.INTERNALDATE; + abstract class SQLServerSpatialDatatype { + + /**WKT = Well-Known-Text, WKB = Well-Knwon-Binary */ + /**As a general rule, the ~IndexEnd variables are non-inclusive (i.e. pointIndexEnd = 8 means the shape using it will + * only go up to the 7th index of the array) */ protected ByteBuffer buffer; protected InternalSpatialDatatype internalType; protected String wkt; @@ -25,6 +39,7 @@ abstract class SQLServerSpatialDatatype { protected int currentPointIndex = 0; protected int currentFigureIndex = 0; protected int currentSegmentIndex = 0; + protected int currentShapeIndex = 0; protected double points[]; protected double zValues[]; protected double mValues[]; @@ -56,9 +71,7 @@ abstract class SQLServerSpatialDatatype { protected List
figureList = new ArrayList
(); protected List shapeList = new ArrayList(); protected List segmentList = new ArrayList(); - - private int currentShapeIndex = 0; - private byte serializationProperties = 0; + protected byte serializationProperties = 0; private final byte SEGMENT_LINE = 0; private final byte SEGMENT_ARC = 1; @@ -73,117 +86,226 @@ abstract class SQLServerSpatialDatatype { private final byte isLargerThanHemisphereMask = 0b00100000; private List version_one_shape_indexes = new ArrayList(); - - protected abstract void constructWKT(InternalSpatialDatatype isd, int pointIndexEnd, - int figureIndexEnd, int segmentIndexEnd, int shapeIndexEnd); - protected abstract void parseWKTForSerialization(int startPos, int parentShapeIndex, boolean isGeoCollection); + /** + * Serializes the Geogemetry/Geography instance to WKB. + * + * @param noZM flag to indicate if Z and M coordinates should be included + */ + protected abstract void serializeToWkb(boolean noZM); - 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]); + /** + * Deserialize the buffer (that contains WKB representation of Geometry/Geography data), and stores it + * into multiple corresponding data structures. + * + */ + protected abstract void parseWkb(); + + /** + * Create the WKT representation of Geometry/Geography from the deserialized data. + * + * @param sd the Geometry/Geography instance. + * @param isd + * @param pointIndexEnd + * @param figureIndexEnd + * @param segmentIndexEnd + * @param shapeIndexEnd + */ + protected void constructWKT(SQLServerSpatialDatatype sd, InternalSpatialDatatype isd, int pointIndexEnd, int figureIndexEnd, + int segmentIndexEnd, int shapeIndexEnd) { + if (null == points || numberOfPoints == 0) { + if (isd.getTypeCode() == 11) { // FULLGLOBE + if (sd instanceof Geometry) { + throw new IllegalArgumentException("Fullglobe is not supported for Geometry."); + } else { + appendToWKTBuffers("FULLGLOBE"); + return; } } - - if (hasMvalues) { - for (int i = 0; i < numberOfPoints; i++) { - buf.putDouble(mValues[i]); - } + // handle the case of GeometryCollection having empty objects + if (isd.getTypeCode() == 7 && currentShapeIndex != shapeIndexEnd - 1) { + currentShapeIndex++; + appendToWKTBuffers(isd.getTypeName() + "("); + constructWKT(this, InternalSpatialDatatype.valueOf(shapes[currentShapeIndex].getOpenGISType()), + numberOfPoints, numberOfFigures, numberOfSegments, numberOfShapes); + appendToWKTBuffers(")"); + return; } - } - - if (isSinglePoint || isSingleLineSegment) { - wkb = buf.array(); + appendToWKTBuffers(isd.getTypeName() + " EMPTY"); 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(); + appendToWKTBuffers(isd.getTypeName()); + appendToWKTBuffers("("); + switch (isd) { + case POINT: + constructPointWKT(currentPointIndex); + break; + case LINESTRING: + case CIRCULARSTRING: + constructLineWKT(currentPointIndex, pointIndexEnd); + break; + case POLYGON: + constructShapeWKT(currentFigureIndex, figureIndexEnd); + break; + case MULTIPOINT: + case MULTILINESTRING: + constructMultiShapeWKT(currentShapeIndex, shapeIndexEnd); + break; + case COMPOUNDCURVE: + constructCompoundcurveWKT(currentSegmentIndex, segmentIndexEnd, pointIndexEnd); + break; + case MULTIPOLYGON: + constructMultipolygonWKT(currentShapeIndex, shapeIndexEnd); + break; + case GEOMETRYCOLLECTION: + constructGeometryCollectionWKT(shapeIndexEnd); + break; + case CURVEPOLYGON: + constructCurvepolygonWKT(currentFigureIndex, figureIndexEnd, currentSegmentIndex, segmentIndexEnd); + break; + default: + throw new IllegalArgumentException("Illegal character at wkt position " + currentWktPos); } - return; + + appendToWKTBuffers(")"); } - protected void parseWkb() { - srid = buffer.getInt(); - version = buffer.get(); - serializationProperties = buffer.get(); - - interpretSerializationPropBytes(); - - readNumberOfPoints(); - - readPoints(); + /** + * Parses WKT and populates the data structures of the Geometry/Geography instance. + * + * @param sd the Geometry/Geography instance. + * @param startPos The index to start from from the WKT. + * @param parentShapeIndex The index of the parent's Shape in the shapes array. Used to determine this shape's parent. + * @param isGeoCollection flag to indicate if this is part of a GeometryCollection. + */ + protected void parseWKTForSerialization(SQLServerSpatialDatatype sd, int startPos, int parentShapeIndex, boolean isGeoCollection) { + //after every iteration of this while loop, the currentWktPosition will be set to the + //end of the geometry/geography shape, except for the very first iteration of it. + //This means that there has to be comma (that separates the previous shape with the next shape), + //or we expect a ')' that will close the entire shape and exit the method. - if (hasZvalues) { - readZvalues(); - } - - if (hasMvalues) { - readMvalues(); - } - - //TODO: do I need to do anything when it's isSinglePoint or isSingleLineSegment? - if (!(isSinglePoint || isSingleLineSegment)) { - readNumberOfFigures(); - - readFigures(); + while (hasMoreToken()) { + if (startPos != 0) { + if (wkt.charAt(currentWktPos) == ')') { + return; + } else if (wkt.charAt(currentWktPos) == ',') { + currentWktPos++; + } + } + + String nextToken = getNextStringToken().toUpperCase(Locale.US); + int thisShapeIndex; + InternalSpatialDatatype isd = InternalSpatialDatatype.valueOf(nextToken); + byte fa = 0; - readNumberOfShapes(); + if (version == 1 && (nextToken.equals("CIRCULARSTRING") || nextToken.equals("COMPOUNDCURVE") || + nextToken.equals("CURVEPOLYGON"))) { + version = 2; + } - readShapes(); - } - - determineInternalType(); + // check for FULLGLOBE before reading the first open bracket, since FULLGLOBE doesn't have one. + if (nextToken.equals("FULLGLOBE")) { + if (sd instanceof Geometry) { + throw new IllegalArgumentException("Fullglobe is not supported for Geometry."); + } + + if (startPos != 0) { + throw new IllegalArgumentException("Illegal character at wkt position " + currentWktPos); + } + + shapeList.add(new Shape(parentShapeIndex, -1, isd.getTypeCode())); + isLargerThanHemisphere = true; + version = 2; + break; + } - if (version == 2 && internalType.getTypeCode() != 8 && internalType.getTypeCode() != 11) { - readNumberOfSegments(); + // if next keyword is empty, continue the loop. + if (checkEmptyKeyword(parentShapeIndex, isd, false)) { + continue; + } + + readOpenBracket(); - readSegments(); + switch (nextToken) { + case "POINT": + if (startPos == 0 && nextToken.toUpperCase().equals("POINT")) { + isSinglePoint = true; + } + + if (isGeoCollection) { + shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); + figureList.add(new Figure(FA_LINE, pointList.size())); + } + + readPointWkt(); + break; + case "LINESTRING": + case "CIRCULARSTRING": + shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); + fa = isd.getTypeCode() == InternalSpatialDatatype.LINESTRING.getTypeCode() ? FA_STROKE : FA_EXTERIOR_RING; + figureList.add(new Figure(fa, pointList.size())); + + readLineWkt(); + + if (startPos == 0 && nextToken.toUpperCase().equals("LINESTRING") && pointList.size() == 2) { + isSingleLineSegment = true; + } + break; + case "POLYGON": + case "MULTIPOINT": + case "MULTILINESTRING": + thisShapeIndex = shapeList.size(); + shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); + + readShapeWkt(thisShapeIndex, nextToken); + + break; + case "MULTIPOLYGON": + thisShapeIndex = shapeList.size(); + shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); + + readMultiPolygonWkt(thisShapeIndex, nextToken); + + break; + case "COMPOUNDCURVE": + shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); + figureList.add(new Figure(FA_COMPOSITE_CURVE, pointList.size())); + + readCompoundCurveWkt(true); + + break; + case "CURVEPOLYGON": + shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); + + readCurvePolygon(); + + break; + case "GEOMETRYCOLLECTION": + thisShapeIndex = shapeList.size(); + shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); + + parseWKTForSerialization(this, currentWktPos, thisShapeIndex, true); + + break; + default: + throw new IllegalArgumentException("Illegal character at wkt position " + currentWktPos); + } + readCloseBracket(); } + + populateStructures(); } + /** + * Constructs and appends a Point type in WKT form to the stringbuffer. + * There are two stringbuffers - WKTsb and WKTsbNoZM. WKTsb contains the X, Y, Z and M coordinates, + * whereas WKTsbNoZM contains only X and Y coordinates. + * + * @param pointIndex indicates which point to append to the stringbuffer. + * + */ protected void constructPointWKT(int pointIndex) { int firstPointIndex = pointIndex * 2; int secondPointIndex = firstPointIndex + 1; @@ -204,7 +326,7 @@ protected void constructPointWKT(int pointIndex) { } appendToWKTBuffers(" "); - if (hasZvalues && !Double.isNaN(zValues[zValueIndex])) { + if (hasZvalues && !Double.isNaN(zValues[zValueIndex]) && !(zValues[zValueIndex] == 0)) { if (zValues[zValueIndex] % 1 == 0) { WKTsb.append((int) zValues[zValueIndex]); } else { @@ -212,7 +334,7 @@ protected void constructPointWKT(int pointIndex) { } WKTsb.append(" "); - if (hasMvalues && !Double.isNaN(mValues[mValueIndex])) { + if (hasMvalues && !Double.isNaN(mValues[mValueIndex]) && !(mValues[mValueIndex] <= 0)) { if (mValues[mValueIndex] % 1 == 0) { WKTsb.append((int) mValues[mValueIndex]); } else { @@ -228,6 +350,12 @@ protected void constructPointWKT(int pointIndex) { WKTsbNoZM.setLength(WKTsbNoZM.length() - 1); } + /** + * Constructs a line in WKT form. + * + * @param pointStartIndex + * @param pointEndIndex + */ protected void constructLineWKT(int pointStartIndex, int pointEndIndex) { for (int i = pointStartIndex; i < pointEndIndex; i++) { constructPointWKT(i); @@ -239,8 +367,13 @@ protected void constructLineWKT(int pointStartIndex, int pointEndIndex) { } } + /** + * Constructs a shape (simple Geometry/Geography entities that are contained within a single bracket) in WKT form. + * + * @param figureStartIndex + * @param figureEndIndex + */ protected void constructShapeWKT(int figureStartIndex, int figureEndIndex) { - // Method for constructing shapes (simple Geometry/Geography entities that are contained within a single bracket) for (int i = figureStartIndex; i < figureEndIndex; i++) { appendToWKTBuffers("("); if (i != numberOfFigures - 1) { //not the last figure @@ -257,6 +390,32 @@ protected void constructShapeWKT(int figureStartIndex, int figureEndIndex) { } } + /** + * Constructs a mutli-shape (MultiPoint / MultiLineString) in WKT form. + * + * @param figureStartIndex + * @param figureEndIndex + */ + protected void constructMultiShapeWKT(int shapeStartIndex, int shapeEndIndex) { + for (int i = shapeStartIndex + 1; i < shapeEndIndex; i++) { + if (shapes[i].getFigureOffset() == -1) { // EMPTY + appendToWKTBuffers("EMPTY"); + } else { + constructShapeWKT(shapes[i].getFigureOffset(), shapes[i].getFigureOffset() + 1); + } + if (i != shapeEndIndex - 1) { + appendToWKTBuffers(", "); + } + } + } + + /** + * Constructs a CompoundCurve in WKT form. + * + * @param segmentStartIndex + * @param segmentEndIndex + * @param pointEndIndex + */ protected void constructCompoundcurveWKT(int segmentStartIndex, int segmentEndIndex, int pointEndIndex) { for (int i = segmentStartIndex; i < segmentEndIndex; i++) { byte segment = segments[i].getSegmentType(); @@ -286,31 +445,78 @@ protected void constructCompoundcurveWKT(int segmentStartIndex, int segmentEndIn } } - protected void constructMultipolygonWKT(int figureStartIndex, int figureEndIndex) { - for (int i = figureStartIndex; i < figureEndIndex; i++) { - if (figures[i].getFiguresAttribute() == 2) { // exterior ring - appendToWKTBuffers("(("); - } else { // interior ring - appendToWKTBuffers("("); + /** + * Constructs a MultiPolygon in WKT form. + * + * @param segmentStartIndex + * @param segmentEndIndex + * @param pointEndIndex + */ + protected void constructMultipolygonWKT(int shapeStartIndex, int shapeEndIndex) { + int figureStartIndex; + int figureEndIndex; + + for (int i = shapeStartIndex + 1; i < shapeEndIndex; i++) { + figureEndIndex = figures.length; + if (shapes[i].getFigureOffset() == -1) { // EMPTY + appendToWKTBuffers("EMPTY"); + if (!(i == shapeEndIndex - 1)) { // not the last exterior polygon of this multipolygon, add a comma + appendToWKTBuffers(", "); + } + continue; } - - if (i == figures.length - 1) { // last figure - constructLineWKT(figures[i].getPointOffset(), numberOfPoints); + figureStartIndex = shapes[i].getFigureOffset(); + if (i == shapes.length - 1) { // last shape + figureEndIndex = figures.length; } else { - constructLineWKT(figures[i].getPointOffset(), figures[i + 1].getPointOffset()); + // look ahead and find the next shape that doesn't have -1 as its figure offset (which signifies EMPTY) + int tempCurrentShapeIndex = i + 1; + // We need to iterate this through until the very end of the shapes list, since if the last shape + // in this MultiPolygon is an EMPTY, it won't know what the correct figureEndIndex would be. + while (tempCurrentShapeIndex < shapes.length) { + if (shapes[tempCurrentShapeIndex].getFigureOffset() == -1) { + tempCurrentShapeIndex++; + continue; + } else { + figureEndIndex = shapes[tempCurrentShapeIndex].getFigureOffset(); + break; + } + } } + + appendToWKTBuffers("("); - if (i == figureEndIndex - 1) { // last polygon of this multipolygon, close off the Multipolygon and return - appendToWKTBuffers("))"); - return; - } else if (figures[i + 1].getFiguresAttribute() == 2) { // not the last polygon, followed by an exterior ring - appendToWKTBuffers(")), "); - } else { // not the last polygon, followed by an interior ring - appendToWKTBuffers("), "); + for (int j = figureStartIndex; j < figureEndIndex; j++) { + appendToWKTBuffers("(");// interior ring + + if (j == figures.length - 1) { // last figure + constructLineWKT(figures[j].getPointOffset(), numberOfPoints); + } else { + constructLineWKT(figures[j].getPointOffset(), figures[j + 1].getPointOffset()); + } + + if (j == figureEndIndex - 1) { // last polygon of this multipolygon, close off the Multipolygon + appendToWKTBuffers(")"); + } else { // not the last polygon, followed by an interior ring + appendToWKTBuffers("), "); + } + } + + appendToWKTBuffers(")"); + + if (!(i == shapeEndIndex - 1)) { // not the last exterior polygon of this multipolygon, add a comma + appendToWKTBuffers(", "); } } } + /** + * Constructs a CurvePolygon in WKT form. + * + * @param segmentStartIndex + * @param segmentEndIndex + * @param pointEndIndex + */ protected void constructCurvepolygonWKT(int figureStartIndex, int figureEndIndex, int segmentStartIndex, int segmentEndIndex) { for (int i = figureStartIndex; i < figureEndIndex; i++) { switch (figures[i].getFiguresAttribute()) { @@ -321,7 +527,6 @@ protected void constructCurvepolygonWKT(int figureStartIndex, int figureEndIndex constructLineWKT(currentPointIndex, numberOfPoints); } else { constructLineWKT(currentPointIndex, figures[i + 1].getPointOffset()); - //currentPointIndex = figures[i + 1].getPointOffset(); } appendToWKTBuffers(")"); @@ -333,7 +538,6 @@ protected void constructCurvepolygonWKT(int figureStartIndex, int figureEndIndex constructLineWKT(currentPointIndex, numberOfPoints); } else { constructLineWKT(currentPointIndex, figures[i + 1].getPointOffset()); - //currentPointIndex = figures[i + 1].getPointOffset(); } appendToWKTBuffers(")"); @@ -395,6 +599,18 @@ protected void constructCurvepolygonWKT(int figureStartIndex, int figureEndIndex } } + /** + * Constructs a Segment in WKT form. + * SQL Server re-uses the last point of a segment if the following segment is of type 3 (first arc) or + * type 2 (first line). This makes sense because the last point of a segment and the first point of the next + * segment have to match for a valid curve. This means that the code has to look ahead and decide to decrement + * the currentPointIndex depending on what segment comes next, since it may have been reused (and it's reflected + * in the array of points) + * + * @param segmentStartIndex + * @param segmentEndIndex + * @param pointEndIndex + */ protected void constructSegmentWKT(int currentSegment, byte segment, int pointEndIndex) { switch (segment) { case 0: @@ -450,11 +666,20 @@ protected void constructSegmentWKT(int currentSegment, byte segment, int pointEn } } + /** + * The starting point for constructing a GeometryCollection type in WKT form. + * + * @param shapeEndIndex + */ protected void constructGeometryCollectionWKT(int shapeEndIndex) { currentShapeIndex++; constructGeometryCollectionWKThelper(shapeEndIndex); } + /** + * Reads Point WKT and adds it to the list of points. + * This method will read up until and including the comma that may come at the end of the Point WKT. + */ protected void readPointWkt() { int numOfCoordinates = 0; double sign; @@ -485,7 +710,7 @@ protected void readPointWkt() { coords[numOfCoordinates] = sign * new BigDecimal(wkt.substring(startPos, currentWktPos)).doubleValue(); } catch (Exception e) { //modify to conversion exception - throw new IllegalArgumentException(); + throw new IllegalArgumentException("Illegal character at wkt position " + currentWktPos); } skipWhiteSpaces(); @@ -510,15 +735,32 @@ protected void readPointWkt() { pointList.add(new Point(coords[0], coords[1], coords[2], coords[3])); } + /** + * Reads a series of Point types. + */ protected void readLineWkt() { while (currentWktPos < wkt.length() && wkt.charAt(currentWktPos) != ')') { readPointWkt(); } } + /** + * Reads a shape (simple Geometry/Geography entities that are contained within a single bracket) WKT. + * + * @param parentShapeIndex + * @param nextToken + */ protected void readShapeWkt(int parentShapeIndex, String nextToken) { byte fa = FA_POINT; while (currentWktPos < wkt.length() && wkt.charAt(currentWktPos) != ')') { + + // if next keyword is empty, continue the loop. + // Do not check this for polygon. + if (!nextToken.equals("POLYGON") && + checkEmptyKeyword(parentShapeIndex, InternalSpatialDatatype.valueOf(nextToken), true)) { + continue; + } + if (nextToken.equals("MULTIPOINT")) { shapeList.add(new Shape(parentShapeIndex, figureList.size(), InternalSpatialDatatype.POINT.getTypeCode())); } else if (nextToken.equals("MULTILINESTRING")) { @@ -551,11 +793,14 @@ protected void readShapeWkt(int parentShapeIndex, String nextToken) { } else if (wkt.charAt(currentWktPos) == ')') { // about to exit while loop continue; } else { // unexpected input - throw new IllegalArgumentException(); + throw new IllegalArgumentException("Illegal character at wkt position " + currentWktPos); } } } + /** + * Reads a CurvePolygon WKT + */ protected void readCurvePolygon() { while (currentWktPos < wkt.length() && wkt.charAt(currentWktPos) != ')') { String nextPotentialToken = getNextStringToken().toUpperCase(Locale.US); @@ -575,7 +820,7 @@ protected void readCurvePolygon() { readLineWkt(); readCloseBracket(); } else { - throw new IllegalArgumentException(); + throw new IllegalArgumentException("Illegal character at wkt position " + currentWktPos); } if (wkt.charAt(currentWktPos) == ',') { // more polygons to follow @@ -583,13 +828,19 @@ protected void readCurvePolygon() { } else if (wkt.charAt(currentWktPos) == ')') { // about to exit while loop continue; } else { // unexpected input - throw new IllegalArgumentException(); + throw new IllegalArgumentException("Illegal character at wkt position " + currentWktPos); } } } + /** + * Reads a MultiPolygon WKT + */ protected void readMultiPolygonWkt(int thisShapeIndex, String nextToken) { while (currentWktPos < wkt.length() && wkt.charAt(currentWktPos) != ')') { + if (checkEmptyKeyword(thisShapeIndex, InternalSpatialDatatype.valueOf(nextToken), true)) { + continue; + } shapeList.add(new Shape(thisShapeIndex, figureList.size(), InternalSpatialDatatype.POLYGON.getTypeCode())); //exterior polygon readOpenBracket(); readShapeWkt(thisShapeIndex, nextToken); @@ -600,12 +851,14 @@ protected void readMultiPolygonWkt(int thisShapeIndex, String nextToken) { } else if (wkt.charAt(currentWktPos) == ')') { // about to exit while loop continue; } else { // unexpected input - throw new IllegalArgumentException(); + throw new IllegalArgumentException("Illegal character at wkt position " + currentWktPos); } } } - + /** + * Reads a Segment WKT + */ protected void readSegmentWkt(int segmentType, boolean isFirstIteration) { segmentList.add(new Segment((byte) segmentType)); @@ -635,6 +888,9 @@ protected void readSegmentWkt(int segmentType, boolean isFirstIteration) { } } + /** + * Reads a CompoundCurve WKT + */ protected void readCompoundCurveWkt(boolean isFirstIteration) { while (currentWktPos < wkt.length() && wkt.charAt(currentWktPos) != ')') { String nextPotentialToken = getNextStringToken().toUpperCase(Locale.US); @@ -647,7 +903,7 @@ protected void readCompoundCurveWkt(boolean isFirstIteration) { readSegmentWkt(SEGMENT_FIRST_LINE, isFirstIteration); readCloseBracket(); } else { - throw new IllegalArgumentException(); + throw new IllegalArgumentException("Illegal character at wkt position " + currentWktPos); } isFirstIteration = false; @@ -657,11 +913,17 @@ protected void readCompoundCurveWkt(boolean isFirstIteration) { } else if (wkt.charAt(currentWktPos) == ')') { // about to exit while loop continue; } else { // unexpected input - throw new IllegalArgumentException(); + throw new IllegalArgumentException("Illegal character at wkt position " + currentWktPos); } } } + /** + * Reads the next string token (usually POINT, LINESTRING, etc.). + * Then increments currentWktPos to the end of the string token. + * + * @return the next string token + */ protected String getNextStringToken() { skipWhiteSpaces(); int endIndex = currentWktPos; @@ -675,6 +937,9 @@ protected String getNextStringToken() { return wkt.substring(temp, endIndex); } + /** + * Populates the various data structures contained within the Geometry/Geography instace. + */ protected void populateStructures() { if (pointList.size() > 0) { points = new double[pointList.size() * 2]; @@ -716,6 +981,14 @@ protected void populateStructures() { } } + // There is an edge case of empty GeometryCollections being inside other GeometryCollections. In this case, + // the figure offset of the very first shape (GeometryCollections) has to be -1, but this is not possible to know until + // We've parsed through the entire WKT and confirmed that there are 0 points. + // Therefore, if so, we make the figure offset of the first shape to be -1. + if (pointList.size() == 0 && shapeList.size() > 0 && shapeList.get(0).getOpenGISType() == 7) { + shapeList.get(0).setFigureOffset(-1); + } + if (shapeList.size() > 0) { shapes = new Shape[shapeList.size()]; @@ -744,7 +1017,7 @@ protected void readOpenBracket() { currentWktPos++; skipWhiteSpaces(); } else { - throw new IllegalArgumentException(); + throw new IllegalArgumentException("Illegal character at wkt position " + currentWktPos); } } @@ -754,7 +1027,7 @@ protected void readCloseBracket() { currentWktPos++; skipWhiteSpaces(); } else { - throw new IllegalArgumentException(); + throw new IllegalArgumentException("Illegal character at wkt position " + currentWktPos); } } @@ -763,7 +1036,7 @@ protected boolean hasMoreToken() { return currentWktPos < wkt.length(); } - private void createSerializationProperties() { + protected void createSerializationProperties() { serializationProperties = 0; if (hasZvalues) { serializationProperties += hasZvaluesMask; @@ -793,7 +1066,7 @@ private void createSerializationProperties() { } } - private int determineWkbCapacity() { + protected int determineWkbCapacity() { int totalSize = 0; totalSize+=6; // SRID + version + SerializationPropertiesByte @@ -833,8 +1106,17 @@ private int determineWkbCapacity() { return totalSize; } + + /** + * Append the data to both stringbuffers. + * @param o data to append to the stringbuffers. + */ + protected void appendToWKTBuffers(Object o) { + WKTsb.append(o); + WKTsbNoZM.append(o); + } - private void interpretSerializationPropBytes() { + protected void interpretSerializationPropBytes() { hasZvalues = (serializationProperties & hasZvaluesMask) != 0; hasMvalues = (serializationProperties & hasMvaluesMask) != 0; isValid = (serializationProperties & isValidMask) != 0; @@ -843,7 +1125,7 @@ private void interpretSerializationPropBytes() { isLargerThanHemisphere = (serializationProperties & isLargerThanHemisphereMask) != 0; } - private void readNumberOfPoints() { + protected void readNumberOfPoints() { if (isSinglePoint) { numberOfPoints = 1; } else if (isSingleLineSegment) { @@ -852,34 +1134,26 @@ private void readNumberOfPoints() { numberOfPoints = buffer.getInt(); } } - - 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(); - } - } - private void readZvalues() { + protected void readZvalues() { zValues = new double[numberOfPoints]; for (int i = 0; i < numberOfPoints; i++) { zValues[i] = buffer.getDouble(); } } - private void readMvalues() { + protected void readMvalues() { mValues = new double[numberOfPoints]; for (int i = 0; i < numberOfPoints; i++) { mValues[i] = buffer.getDouble(); } } - private void readNumberOfFigures() { + protected void readNumberOfFigures() { numberOfFigures = buffer.getInt(); } - private void readFigures() { + protected void readFigures() { byte fa; int po; figures = new Figure[numberOfFigures]; @@ -890,11 +1164,11 @@ private void readFigures() { } } - private void readNumberOfShapes() { + protected void readNumberOfShapes() { numberOfShapes = buffer.getInt(); } - private void readShapes() { + protected void readShapes() { int po; int fo; byte ogt; @@ -907,11 +1181,11 @@ private void readShapes() { } } - private void readNumberOfSegments() { + protected void readNumberOfSegments() { numberOfSegments = buffer.getInt(); } - private void readSegments() { + protected void readSegments() { byte st; segments = new Segment[numberOfSegments]; for (int i = 0; i < numberOfSegments; i++) { @@ -920,7 +1194,7 @@ private void readSegments() { } } - private void determineInternalType() { + protected void determineInternalType() { if (isSinglePoint) { internalType = InternalSpatialDatatype.POINT; } else if (isSingleLineSegment) { @@ -930,6 +1204,44 @@ private void determineInternalType() { } } + protected boolean checkEmptyKeyword(int parentShapeIndex, InternalSpatialDatatype isd, boolean isInsideAnotherShape) { + String potentialEmptyKeyword = getNextStringToken().toUpperCase(Locale.US); + if (potentialEmptyKeyword.equals("EMPTY")) { + + byte typeCode = 0; + + if (isInsideAnotherShape) { + byte parentTypeCode = isd.getTypeCode(); + if (parentTypeCode == 4) { // MultiPoint + typeCode = InternalSpatialDatatype.POINT.getTypeCode(); + } else if (parentTypeCode == 5) { // MultiLineString + typeCode = InternalSpatialDatatype.LINESTRING.getTypeCode(); + } else if (parentTypeCode == 6) { // MultiPolygon + typeCode = InternalSpatialDatatype.POLYGON.getTypeCode(); + } else if (parentTypeCode == 7) { // GeometryCollection + typeCode = InternalSpatialDatatype.GEOMETRYCOLLECTION.getTypeCode(); + } else { + throw new IllegalArgumentException("Illegal parentTypeCode."); + } + } else { + typeCode = isd.getTypeCode(); + } + + shapeList.add(new Shape(parentShapeIndex, -1, typeCode)); + skipWhiteSpaces(); + if (currentWktPos < wkt.length() && wkt.charAt(currentWktPos) == ',') { + currentWktPos++; + skipWhiteSpaces(); + } + return true; + } + + if (!potentialEmptyKeyword.equals("")) { + throw new IllegalArgumentException("Illegal character at wkt position " + currentWktPos); + } + return 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. @@ -938,6 +1250,10 @@ private void incrementPointNumStartIfPointNotReused(int pointEndIndex) { } } + /** + * Helper used for resurcive iteration for constructing GeometryCollection in WKT form. + * @param shapeEndIndex + */ private void constructGeometryCollectionWKThelper(int shapeEndIndex) { //phase 1: assume that there is no multi - stuff and no geometrycollection while (currentShapeIndex < shapeEndIndex) { @@ -950,6 +1266,7 @@ private void constructGeometryCollectionWKThelper(int shapeEndIndex) { int shapeIndexEnd = numberOfShapes; int figureIndexIncrement = 0; int segmentIndexIncrement = 0; + int shapeIndexIncrement = 0; int localCurrentSegmentIndex = 0; int localCurrentShapeIndex = 0; @@ -1012,19 +1329,37 @@ private void constructGeometryCollectionWKThelper(int shapeEndIndex) { int thisShapesParentOffset = shapes[currentShapeIndex].getParentOffset(); + int tempShapeIndex = currentShapeIndex; + // Increment shapeStartIndex to account for the shape index that either Multipoint, MultiLineString // or MultiPolygon takes up - currentShapeIndex++; - while (currentShapeIndex < shapes.length - 1 && - shapes[currentShapeIndex].getParentOffset() != thisShapesParentOffset) { - figureIndexEnd = shapes[currentShapeIndex + 1].getFigureOffset(); - currentShapeIndex++; + tempShapeIndex++; + while (tempShapeIndex < shapes.length && + shapes[tempShapeIndex].getParentOffset() != thisShapesParentOffset) { + if (!(tempShapeIndex == shapes.length - 1) && // last iteration, don't check for shapes[tempShapeIndex + 1] + !(shapes[tempShapeIndex + 1].getFigureOffset() == -1)) { // disregard EMPTY cases + figureIndexEnd = shapes[tempShapeIndex + 1].getFigureOffset(); + } + tempShapeIndex++; } figureIndexIncrement = figureIndexEnd - currentFigureIndex; + shapeIndexIncrement = tempShapeIndex - currentShapeIndex; + shapeIndexEnd = tempShapeIndex; break; case GEOMETRYCOLLECTION: appendToWKTBuffers(isd.getTypeName()); + + // handle Empty GeometryCollection cases + if (shapes[currentShapeIndex].getFigureOffset() == -1) { + appendToWKTBuffers(" EMPTY"); + currentShapeIndex++; + if (currentShapeIndex < shapeEndIndex) { + appendToWKTBuffers(", "); + } + continue; + } + appendToWKTBuffers("("); int geometryCollectionParentIndex = shapes[currentShapeIndex].getParentOffset(); @@ -1032,7 +1367,6 @@ private void constructGeometryCollectionWKThelper(int shapeEndIndex) { // Needed to keep track of which shape we are at, inside the for loop localCurrentShapeIndex = currentShapeIndex; - while (localCurrentShapeIndex < shapes.length - 1 && shapes[localCurrentShapeIndex + 1].getParentOffset() > geometryCollectionParentIndex) { localCurrentShapeIndex++; @@ -1073,9 +1407,10 @@ private void constructGeometryCollectionWKThelper(int shapeEndIndex) { break; } - constructWKT(isd, pointIndexEnd, figureIndexEnd, segmentIndexEnd, shapeIndexEnd); + constructWKT(this, isd, pointIndexEnd, figureIndexEnd, segmentIndexEnd, shapeIndexEnd); currentFigureIndex = currentFigureIndex + figureIndexIncrement; currentSegmentIndex = currentSegmentIndex + segmentIndexIncrement; + currentShapeIndex = currentShapeIndex + shapeIndexIncrement; if (currentShapeIndex < shapeEndIndex) { appendToWKTBuffers(", "); @@ -1083,7 +1418,14 @@ private void constructGeometryCollectionWKThelper(int shapeEndIndex) { } } - //Calculates how many segments will be used by this CompoundCurve + /** + * Calculates how many segments will be used by this shape. + * Needed to determine when the shape that uses segments (e.g. CompoundCurve) needs to stop reading + * in cases where the CompoundCurve is included as part of GeometryCollection. + * @param segmentStart + * @param pointDifference + * @return the number of segments that will be used by this shape. + */ private int calculateSegmentIncrement(int segmentStart, int pointDifference) { @@ -1176,7 +1518,7 @@ private void readComma() { currentWktPos++; skipWhiteSpaces(); } else { - throw new IllegalArgumentException(); + throw new IllegalArgumentException("Illegal character at wkt position " + currentWktPos); } } @@ -1185,14 +1527,12 @@ private void skipWhiteSpaces() { currentWktPos++; } } - - protected void appendToWKTBuffers(Object o) { - WKTsb.append(o); - WKTsbNoZM.append(o); - } } - +/** + * Class to hold and represent the internal makings of a Figure. + * + */ class Figure { private byte figuresAttribute; private int pointOffset; @@ -1215,6 +1555,10 @@ public void setFiguresAttribute(byte fa) { } } +/** + * Class to hold and represent the internal makings of a Shape. + * + */ class Shape { private int parentOffset; private int figureOffset; @@ -1237,8 +1581,17 @@ public int getFigureOffset() { public byte getOpenGISType() { return openGISType; } + + public void setFigureOffset(int fo) { + figureOffset = fo; + } + } +/** + * Class to hold and represent the internal makings of a Segment. + * + */ class Segment { private byte segmentType; @@ -1251,6 +1604,10 @@ public byte getSegmentType() { } } +/** + * Class to hold and represent the internal makings of a Point. + * + */ class Point { private final double x; private final double y; diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/datatypes/SQLServerSpatialDatatypeTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/datatypes/SQLServerSpatialDatatypeTest.java new file mode 100644 index 000000000..c87b0c230 --- /dev/null +++ b/src/test/java/com/microsoft/sqlserver/jdbc/datatypes/SQLServerSpatialDatatypeTest.java @@ -0,0 +1,458 @@ +/* + * Microsoft JDBC Driver for SQL Server + * + * Copyright(c) Microsoft Corporation All rights reserved. + * + * This program is made available under the terms of the MIT License. See the LICENSE file in the project root for more information. + */ +package com.microsoft.sqlserver.jdbc.datatypes; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.IOException; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.sql.Statement; + +import org.apache.commons.codec.DecoderException; +import org.apache.commons.codec.binary.Hex; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.platform.runner.JUnitPlatform; +import org.junit.runner.RunWith; + +import com.microsoft.sqlserver.jdbc.Geography; +import com.microsoft.sqlserver.jdbc.Geometry; +import com.microsoft.sqlserver.jdbc.SQLServerConnection; +import com.microsoft.sqlserver.jdbc.SQLServerException; +import com.microsoft.sqlserver.jdbc.SQLServerPreparedStatement; +import com.microsoft.sqlserver.jdbc.SQLServerResultSet; +import com.microsoft.sqlserver.testframework.AbstractTest; +import com.microsoft.sqlserver.testframework.Utils; + +/** + * Test Geometry / Geography classes + * + */ +@RunWith(JUnitPlatform.class) +public class SQLServerSpatialDatatypeTest extends AbstractTest { + + static SQLServerConnection con = null; + static Statement stmt = null; + static String geomTableName = "geometryTestTable"; + static String geogTableName = "geographyTestTable"; + static SQLServerPreparedStatement pstmt = null; + static SQLServerResultSet rs = null; + + @Test + public void testPointWkb() throws DecoderException { + String geoWKT = "POINT(3 40 5 6)"; + + byte[] geomWKB = Hex.decodeHex("00000000010F0000000000000840000000000000444000000000000014400000000000001840".toCharArray()); + byte[] geogWKB = Hex.decodeHex("E6100000010F0000000000004440000000000000084000000000000014400000000000001840".toCharArray()); + + Geometry geomWKT = Geometry.deserialize(geomWKB); + Geography geogWKT = Geography.deserialize(geogWKB); + + assertEquals(geomWKT.asTextZM(), geoWKT); + assertEquals(geogWKT.asTextZM(), geoWKT); + } + + @Test + public void testLineStringWkb() throws DecoderException { + String geoWKT = "LINESTRING(1 0, 0 1, -1 0)"; + + byte[] geomWKB = Hex.decodeHex("00000000010403000000000000000000F03F00000000000000000000000000000000000000000000F03F000000000000F0BF000000000000000001000000010000000001000000FFFFFFFF0000000002".toCharArray()); + byte[] geogWKB = Hex.decodeHex("E61000000104030000000000000000000000000000000000F03F000000000000F03F00000000000000000000000000000000000000000000F0BF01000000010000000001000000FFFFFFFF0000000002".toCharArray()); + + Geometry geomWKT = Geometry.deserialize(geomWKB); + Geography geogWKT = Geography.deserialize(geogWKB); + + assertEquals(geomWKT.asTextZM(), geoWKT); + assertEquals(geogWKT.asTextZM(), geoWKT); + } + + @Test + public void testPolygonWkb() throws DecoderException { + String geoWKT = "POLYGON((0 0, 0 3, 3 3, 3 0, 0 0), (1 1, 1 2, 2 1, 1 1))"; + + byte[] geomWKB = Hex.decodeHex("000000000104090000000000000000000000000000000000000000000000000000000000000000000840000000000000084000000000000008400000000000000840000000000000000000000000000000000000000000000000000000000000F03F000000000000F03F000000000000F03F00000000000000400000000000000040000000000000F03F000000000000F03F000000000000F03F020000000200000000000500000001000000FFFFFFFF0000000003".toCharArray()); + byte[] geogWKB = Hex.decodeHex("E61000000200090000000000000000000000000000000000000000000000000008400000000000000000000000000000084000000000000008400000000000000000000000000000084000000000000000000000000000000000000000000000F03F000000000000F03F0000000000000040000000000000F03F000000000000F03F0000000000000040000000000000F03F000000000000F03F020000000100000000010500000001000000FFFFFFFF0000000003".toCharArray()); + + Geometry geomWKT = Geometry.deserialize(geomWKB); + Geography geogWKT = Geography.deserialize(geogWKB); + + assertEquals(geomWKT.asTextZM(), geoWKT); + assertEquals(geogWKT.asTextZM(), geoWKT); + } + + @Test + public void testMultiPointWkb() throws DecoderException { + String geoWKT = "MULTIPOINT((2 3), (7 8 9.5))"; + + byte[] geomWKB = Hex.decodeHex("00000000010502000000000000000000004000000000000008400000000000001C400000000000002040000000000000F8FF0000000000002340020000000100000000010100000003000000FFFFFFFF0000000004000000000000000001000000000100000001".toCharArray()); + byte[] geogWKB = Hex.decodeHex("E61000000105020000000000000000000840000000000000004000000000000020400000000000001C40000000000000F8FF0000000000002340020000000100000000010100000003000000FFFFFFFF0000000004000000000000000001000000000100000001".toCharArray()); + + Geometry geomWKT = Geometry.deserialize(geomWKB); + Geography geogWKT = Geography.deserialize(geogWKB); + + assertEquals(geomWKT.asTextZM(), geoWKT); + assertEquals(geogWKT.asTextZM(), geoWKT); + } + + @Test + public void testMultiLineStringWkb() throws DecoderException { + String geoWKT = "MULTILINESTRING((0 2, 1 1), (1 0, 1 1))"; + + byte[] geomWKB = Hex.decodeHex("0000000001040400000000000000000000000000000000000040000000000000F03F000000000000F03F000000000000F03F0000000000000000000000000000F03F000000000000F03F020000000100000000010200000003000000FFFFFFFF0000000005000000000000000002000000000100000002".toCharArray()); + byte[] geogWKB = Hex.decodeHex("E610000001040400000000000000000000400000000000000000000000000000F03F000000000000F03F0000000000000000000000000000F03F000000000000F03F000000000000F03F020000000100000000010200000003000000FFFFFFFF0000000005000000000000000002000000000100000002".toCharArray()); + + Geometry geomWKT = Geometry.deserialize(geomWKB); + Geography geogWKT = Geography.deserialize(geogWKB); + + assertEquals(geomWKT.asTextZM(), geoWKT); + assertEquals(geogWKT.asTextZM(), geoWKT); + } + + @Test + public void testMultiPolygonWkb() throws DecoderException { + String geoWKT = "MULTIPOLYGON(((1 1, 1 2, 2 1, 1 1), (0 0, 0 3, 3 3, 3 0, 0 0 7)), ((9 9, 9 10, 10 9, 9 9)))"; + + byte[] geomWKB = Hex.decodeHex("0000000001010D000000000000000000F03F000000000000F03F000000000000F03F00000000000000400000000000000040000000000000F03F000000000000F03F000000000000F03F000000000000000000000000000000000000000000000000000000000000084000000000000008400000000000000840000000000000084000000000000000000000000000000000000000000000000000000000000022400000000000002240000000000000224000000000000024400000000000002440000000000000224000000000000022400000000000002240000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF0000000000001C40000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF0300000002000000000004000000020900000003000000FFFFFFFF0000000006000000000000000003000000000200000003".toCharArray()); + byte[] geogWKB = Hex.decodeHex("E610000002010D000000000000000000F03F000000000000F03F0000000000000040000000000000F03F000000000000F03F0000000000000040000000000000F03F000000000000F03F000000000000000000000000000000000000000000000840000000000000000000000000000008400000000000000840000000000000000000000000000008400000000000000000000000000000000000000000000022400000000000002240000000000000244000000000000022400000000000002240000000000000244000000000000022400000000000002240000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF0000000000001C40000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF0300000001000000000104000000010900000003000000FFFFFFFF0000000006000000000000000003000000000200000003".toCharArray()); + + Geometry geomWKT = Geometry.deserialize(geomWKB); + Geography geogWKT = Geography.deserialize(geogWKB); + + assertEquals(geomWKT.asTextZM(), geoWKT); + assertEquals(geogWKT.asTextZM(), geoWKT); + } + + @Test + public void testGeometryCollectionWkb() throws DecoderException { + String geoWKT = "GEOMETRYCOLLECTION(POINT(3 3 1), LINESTRING(1 0, 0 1, -1 0), CIRCULARSTRING(1 3, 3 5, 4 7, 7 3, 1 3), POLYGON((0 0 2, 1 10 3, 1 0 4, 0 0 2), (0 0 2, 1 10 3, 1 0 4, 0 0 2), (0 0 2, 1 10 3, 1 0 4, 0 0 2)), MULTIPOINT((2 3), (7 8 9.5)), MULTILINESTRING((0 2, 1 1), (1 0, 1 1)), 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))), COMPOUNDCURVE(CIRCULARSTRING(1 0 3, 0 1 3, 9 6 3, 8 7 3, -1 0 3), CIRCULARSTRING(-1 0 3, 7 9 3, -10 2 3), (-10 2 3, 77 77 77, 88 88 88, 2 6 4), (2 6 4, 3 3 6, 7 7 1)), CURVEPOLYGON((0 0, 0 0, 0 0, 0 0), 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))), POLYGON((0 0 2, 1 10 3, 1 0 4, 0 0 2)))"; + + byte[] geomWKB = Hex.decodeHex("0100000002014A00000000000000000008400000000000000840000000000000F03F00000000000000000000000000000000000000000000F03F000000000000F0BF0000000000000000000000000000F03F00000000000008400000000000000840000000000000144000000000000010400000000000001C400000000000001C400000000000000840000000000000F03F000000000000084000000000000000000000000000000000000000000000F03F0000000000002440000000000000F03F00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F03F0000000000002440000000000000F03F00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F03F0000000000002440000000000000F03F000000000000000000000000000000000000000000000000000000000000004000000000000008400000000000001C40000000000000204000000000000000000000000000000040000000000000F03F000000000000F03F000000000000F03F0000000000000000000000000000F03F000000000000F03F0000000000000000000000000000000000000000000000000000000000000840000000000000084000000000000008400000000000000840000000000000000000000000000000000000000000000000000000000000F03F000000000000F03F000000000000F03F00000000000000400000000000000040000000000000F03F000000000000F03F000000000000F03F00000000000022400000000000002240000000000000224000000000000024400000000000002440000000000000224000000000000022400000000000002240000000000000F03F00000000000000000000000000000000000000000000F03F0000000000002240000000000000184000000000000020400000000000001C40000000000000F0BF00000000000000000000000000001C40000000000000224000000000000024C00000000000000040000000000040534000000000004053400000000000005640000000000000564000000000000000400000000000001840000000000000084000000000000008400000000000001C400000000000001C40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000C7D79E59127037C00000000000000000C7D79E591270374000000000008046C0C7D79E59127037C00000000000000000C7D79E59127037C00000000000000000C7D79E59127037C00000000000001C400000000000001C400000000000000000C7D79E5912703740000000000000204000000000000020400000000000002040000000000000204000000000008046C0C7D79E591270374000000000008056C0C7D79E591270374000000000008056C0C7D79E59127037C000000000008046C0C7D79E59127037C00000000000000000C7D79E59127037C000000000000000000000000000000000000000000000F03F0000000000002440000000000000F03F000000000000000000000000000000000000000000000000000000000000F03F000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000004000000000000008400000000000001040000000000000004000000000000000400000000000000840000000000000104000000000000000400000000000000040000000000000084000000000000010400000000000000040000000000000F8FF0000000000002340000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF00000000000008400000000000000840000000000000084000000000000008400000000000000840000000000000084000000000000008400000000000405340000000000000564000000000000010400000000000001840000000000000F03F000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF0000000000000040000000000000084000000000000010400000000000000040120000000100000000010100000002040000000109000000010D00000001110000000115000000011600000001170000000119000000011B00000001200000000124000000032800000001340000000338000000033C000000014600000011000000FFFFFFFF0000000007000000000000000001000000000100000002000000000200000008000000000300000003000000000600000004050000000600000001050000000700000001000000000800000005080000000800000002080000000900000002000000000A000000060B0000000A000000030B0000000C00000003000000000D00000009000000000E0000000A0000000011000000031000000003010302000002000203020003010203".toCharArray()); + byte[] geogWKB = Hex.decodeHex("E610000002214A000000000000000000084000000000000008400000000000000000000000000000F03F000000000000F03F00000000000000000000000000000000000000000000F0BF0000000000000840000000000000F03F000000000000144000000000000008400000000000001C40000000000000104000000000000008400000000000001C400000000000000840000000000000F03F000000000000000000000000000000000000000000002440000000000000F03F0000000000000000000000000000F03F00000000000000000000000000000000000000000000000000000000000000000000000000002440000000000000F03F0000000000000000000000000000F03F00000000000000000000000000000000000000000000000000000000000000000000000000002440000000000000F03F0000000000000000000000000000F03F000000000000000000000000000000000000000000000840000000000000004000000000000020400000000000001C4000000000000000400000000000000000000000000000F03F000000000000F03F0000000000000000000000000000F03F000000000000F03F000000000000F03F0000000000000000000000000000000000000000000008400000000000000000000000000000084000000000000008400000000000000000000000000000084000000000000000000000000000000000000000000000F03F000000000000F03F0000000000000040000000000000F03F000000000000F03F0000000000000040000000000000F03F000000000000F03F000000000000224000000000000022400000000000002440000000000000224000000000000022400000000000002440000000000000224000000000000022400000000000000000000000000000F03F000000000000F03F0000000000000000000000000000184000000000000022400000000000001C4000000000000020400000000000000000000000000000F0BF00000000000022400000000000001C40000000000000004000000000000024C0000000000040534000000000004053400000000000005640000000000000564000000000000018400000000000000040000000000000084000000000000008400000000000001C400000000000001C4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000C7D79E59127037C00000000000000000C7D79E59127037400000000000000000C7D79E59127037C000000000008046C0C7D79E59127037C00000000000000000C7D79E59127037C000000000000000000000000000001C400000000000001C40C7D79E591270374000000000000000000000000000002040000000000000204000000000000020400000000000002040C7D79E591270374000000000008046C0C7D79E591270374000000000008056C0C7D79E59127037C000000000008056C0C7D79E59127037C000000000008046C0C7D79E59127037C00000000000000000000000000000000000000000000000000000000000002440000000000000F03F0000000000000000000000000000F03F00000000000000000000000000000000000000000000F03F000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000004000000000000008400000000000001040000000000000004000000000000000400000000000000840000000000000104000000000000000400000000000000040000000000000084000000000000010400000000000000040000000000000F8FF0000000000002340000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF00000000000008400000000000000840000000000000084000000000000008400000000000000840000000000000084000000000000008400000000000405340000000000000564000000000000010400000000000001840000000000000F03F000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF0000000000000040000000000000084000000000000010400000000000000040120000000100000000010100000002040000000109000000010D00000001110000000115000000011600000001170000000119000000011B00000001200000000124000000032800000001340000000338000000033C000000014600000011000000FFFFFFFF0000000007000000000000000001000000000100000002000000000200000008000000000300000003000000000600000004050000000600000001050000000700000001000000000800000005080000000800000002080000000900000002000000000A000000060B0000000A000000030B0000000C00000003000000000D00000009000000000E0000000A0000000011000000031000000003010302000002000203020003010203".toCharArray()); + + Geometry geomWKT = Geometry.deserialize(geomWKB); + Geography geogWKT = Geography.deserialize(geogWKB); + + assertEquals(geomWKT.asTextZM(), geoWKT); + assertEquals(geogWKT.asTextZM(), geoWKT); + } + + @Test + public void testCircularStringWkb() throws DecoderException { + String geoWKT = "CIRCULARSTRING(2 1 3 4, 1 2 3, 0 7 3, 1 0 3, 2 1 3)"; + + byte[] geomWKB = Hex.decodeHex("000000000207050000000000000000000040000000000000F03F000000000000F03F000000000000004000000000000000000000000000001C40000000000000F03F00000000000000000000000000000040000000000000F03F000000000000084000000000000008400000000000000840000000000000084000000000000008400000000000001040000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF01000000020000000001000000FFFFFFFF0000000008".toCharArray()); + byte[] geogWKB = Hex.decodeHex("E6100000020705000000000000000000F03F00000000000000400000000000000040000000000000F03F0000000000001C4000000000000000000000000000000000000000000000F03F000000000000F03F0000000000000040000000000000084000000000000008400000000000000840000000000000084000000000000008400000000000001040000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF01000000020000000001000000FFFFFFFF0000000008".toCharArray()); + + Geometry geomWKT = Geometry.deserialize(geomWKB); + Geography geogWKT = Geography.deserialize(geogWKB); + + assertEquals(geomWKT.asTextZM(), geoWKT); + assertEquals(geogWKT.asTextZM(), geoWKT); + } + + @Test + public void testCompoundCurveWkb() throws DecoderException { + String geoWKT = "COMPOUNDCURVE(CIRCULARSTRING(1 0 3, 0 1 3, 9 6 3, 8 7 3, -1 0 3), CIRCULARSTRING(-1 0 3, 7 9 3, -10 2 3), (-10 2 3, 77 77 77, 88 88 88, 2 6 4), (2 6 4, 3 3 6, 7 7 1))"; + + byte[] geomWKB = Hex.decodeHex("0000000002050C000000000000000000F03F00000000000000000000000000000000000000000000F03F0000000000002240000000000000184000000000000020400000000000001C40000000000000F0BF00000000000000000000000000001C40000000000000224000000000000024C00000000000000040000000000040534000000000004053400000000000005640000000000000564000000000000000400000000000001840000000000000084000000000000008400000000000001C400000000000001C4000000000000008400000000000000840000000000000084000000000000008400000000000000840000000000000084000000000000008400000000000405340000000000000564000000000000010400000000000001840000000000000F03F01000000030000000001000000FFFFFFFF0000000009080000000301030200000200".toCharArray()); + byte[] geogWKB = Hex.decodeHex("E610000002050C0000000000000000000000000000000000F03F000000000000F03F0000000000000000000000000000184000000000000022400000000000001C4000000000000020400000000000000000000000000000F0BF00000000000022400000000000001C40000000000000004000000000000024C0000000000040534000000000004053400000000000005640000000000000564000000000000018400000000000000040000000000000084000000000000008400000000000001C400000000000001C4000000000000008400000000000000840000000000000084000000000000008400000000000000840000000000000084000000000000008400000000000405340000000000000564000000000000010400000000000001840000000000000F03F01000000030000000001000000FFFFFFFF0000000009080000000301030200000200".toCharArray()); + + Geometry geomWKT = Geometry.deserialize(geomWKB); + Geography geogWKT = Geography.deserialize(geogWKB); + + assertEquals(geomWKT.asTextZM(), geoWKT); + assertEquals(geogWKT.asTextZM(), geoWKT); + } + + @Test + public void testCurvePolygonWkb() throws DecoderException { + String geoWKT = "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)))"; + + byte[] geomWKB = Hex.decodeHex("0A00000002001700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F03F00000000000008400000000000000840000000000000144000000000000010400000000000001C400000000000001C400000000000000840000000000000F03F00000000000008400000000000000000C7D79E59127037C00000000000000000C7D79E591270374000000000008046C0C7D79E59127037C00000000000000000C7D79E59127037C00000000000000000C7D79E59127037C00000000000001C400000000000001C400000000000000000C7D79E5912703740000000000000204000000000000020400000000000002040000000000000204000000000008046C0C7D79E591270374000000000008056C0C7D79E591270374000000000008056C0C7D79E59127037C000000000008046C0C7D79E59127037C00000000000000000C7D79E59127037C004000000010000000002040000000309000000030D00000001000000FFFFFFFF000000000A080000000203020003010203".toCharArray()); + byte[] geogWKB = Hex.decodeHex("E6100000020017000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000840000000000000F03F000000000000144000000000000008400000000000001C40000000000000104000000000000008400000000000001C400000000000000840000000000000F03FC7D79E59127037C00000000000000000C7D79E59127037400000000000000000C7D79E59127037C000000000008046C0C7D79E59127037C00000000000000000C7D79E59127037C000000000000000000000000000001C400000000000001C40C7D79E591270374000000000000000000000000000002040000000000000204000000000000020400000000000002040C7D79E591270374000000000008046C0C7D79E591270374000000000008056C0C7D79E59127037C000000000008056C0C7D79E59127037C000000000008046C0C7D79E59127037C0000000000000000004000000010000000002040000000309000000030D00000001000000FFFFFFFF000000000A080000000203020003010203".toCharArray()); + + Geometry geomWKT = Geometry.deserialize(geomWKB); + Geography geogWKT = Geography.deserialize(geogWKB); + + assertEquals(geomWKT.asTextZM(), geoWKT); + assertEquals(geogWKT.asTextZM(), geoWKT); + } + + @Test + public void testFullGlobeWkb() throws DecoderException { + String geoWKT = "FULLGLOBE"; + + byte[] geogWKB = Hex.decodeHex("E61000000224000000000000000001000000FFFFFFFFFFFFFFFF0B".toCharArray()); + + Geography geogWKT = Geography.deserialize(geogWKB); + + assertEquals(geogWKT.asTextZM(), geoWKT); + } + + @Test + public void testPointWkt() throws SQLException { + beforeEachSetup(); + + String geoWKT = "POINT(3 40 5 6)"; + + testWkt(geoWKT); + + beforeEachSetup(); + + geoWKT = "POINT EMPTY"; + + testWkt(geoWKT); + } + + @Test + public void testLineStringWkt() throws SQLException { + beforeEachSetup(); + + String geoWKT = "LINESTRING(1 0, 0 1, -1 0)"; + + testWkt(geoWKT); + + beforeEachSetup(); + + geoWKT = "LINESTRING EMPTY"; + + testWkt(geoWKT); + } + + @Test + public void testPolygonWkt() throws SQLException { + beforeEachSetup(); + + String geoWKT = "POLYGON((0 0, 0 3, 3 3, 3 0, 0 0), (1 1, 1 2, 2 1, 1 1))"; + + testWkt(geoWKT); + + beforeEachSetup(); + + geoWKT = "POLYGON EMPTY"; + + testWkt(geoWKT); + } + + @Test + public void testMultiPointWkt() throws SQLException { + beforeEachSetup(); + + String geoWKT = "MULTIPOINT((2 3), (7 8 9.5))"; + + testWkt(geoWKT); + + beforeEachSetup(); + + geoWKT = "MULTIPOINT EMPTY"; + + testWkt(geoWKT); + } + + @Test + public void testMultiLineStringWkt() throws SQLException { + beforeEachSetup(); + + String geoWKT = "MULTILINESTRING((0 2, 1 1), (1 0, 1 1))"; + + testWkt(geoWKT); + + beforeEachSetup(); + + geoWKT = "MULTILINESTRING EMPTY"; + + testWkt(geoWKT); + } + + @Test + public void testMultiPolygonWkt() throws SQLException { + beforeEachSetup(); + + String geoWKT = "MULTIPOLYGON(((1 1, 1 2, 2 1, 1 1), (0 0, 0 3, 3 3, 3 0, 0 0 7)), ((9 9, 9 10, 10 9, 9 9)))"; + + testWkt(geoWKT); + + beforeEachSetup(); + + geoWKT = "MULTIPOLYGON EMPTY"; + + testWkt(geoWKT); + } + + @Test + public void testGeometryCollectionWkt() throws SQLException { + beforeEachSetup(); + + String geoWKT = "GEOMETRYCOLLECTION(POINT(3 3 1), LINESTRING(1 0, 0 1, -1 0), CIRCULARSTRING(1 3, 3 5, 4 7, 7 3, 1 3), POLYGON((0 0 2, 1 10 3, 1 0 4, 0 0 2), (0 0 2, 1 10 3, 1 0 4, 0 0 2), (0 0 2, 1 10 3, 1 0 4, 0 0 2)), MULTIPOINT((2 3), (7 8 9.5)), MULTILINESTRING((0 2, 1 1), (1 0, 1 1)), 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))), COMPOUNDCURVE(CIRCULARSTRING(1 0 3, 0 1 3, 9 6 3, 8 7 3, -1 0 3), CIRCULARSTRING(-1 0 3, 7 9 3, -10 2 3), (-10 2 3, 77 77 77, 88 88 88, 2 6 4), (2 6 4, 3 3 6, 7 7 1)), CURVEPOLYGON((0 0, 0 0, 0 0, 0 0), 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))), POLYGON((0 0 2, 1 10 3, 1 0 4, 0 0 2)))"; + + testWkt(geoWKT); + + beforeEachSetup(); + + geoWKT = "GEOMETRYCOLLECTION EMPTY"; + + testWkt(geoWKT); + + beforeEachSetup(); + + geoWKT = "GEOMETRYCOLLECTION(GEOMETRYCOLLECTION EMPTY)"; + + testWkt(geoWKT); + + beforeEachSetup(); + + geoWKT = "GEOMETRYCOLLECTION(POINT(3 3 1), GEOMETRYCOLLECTION EMPTY, MULTIPOLYGON(((0 0, 0 3, 3 3, 3 0, 0 0), (1 1, 1 2, 2 1, 1 1)), EMPTY, EMPTY, ((0 0, 1 1, 2 2, 0 0)), EMPTY), GEOMETRYCOLLECTION EMPTY)"; + + testWkt(geoWKT); + } + + @Test + public void testCircularStringWkt() throws SQLException { + beforeEachSetup(); + + String geoWKT = "CIRCULARSTRING(2 1 3 4, 1 2 3, 0 7 3, 1 0 3, 2 1 3)"; + + testWkt(geoWKT); + + beforeEachSetup(); + + geoWKT = "CIRCULARSTRING EMPTY"; + + testWkt(geoWKT); + } + + @Test + public void testCompoundCurveWkt() throws SQLException { + beforeEachSetup(); + + String geoWKT = "COMPOUNDCURVE(CIRCULARSTRING(1 0 3, 0 1 3, 9 6 3, 8 7 3, -1 0 3), CIRCULARSTRING(-1 0 3, 7 9 3, -10 2 3), (-10 2 3, 77 77 77, 88 88 88, 2 6 4), (2 6 4, 3 3 6, 7 7 1))"; + + testWkt(geoWKT); + + beforeEachSetup(); + + geoWKT = "COMPOUNDCURVE EMPTY"; + + testWkt(geoWKT); + } + + @Test + public void testCurvePolygonWkt() throws SQLException { + beforeEachSetup(); + + String geoWKT = "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)))"; + + testWkt(geoWKT); + + beforeEachSetup(); + + geoWKT = "CURVEPOLYGON EMPTY"; + + testWkt(geoWKT); + } + + @Test + public void testFullGlobeWkt() throws SQLException { + beforeEachSetup(); + + String geoWKT = "FULLGLOBE"; + + Geography geogWKT = Geography.STGeomFromText(geoWKT, 4326); + + try { + Geometry.STGeomFromText(geoWKT, 0); + } + catch (IllegalArgumentException e) { + assertEquals(e.getMessage(), "Fullglobe is not supported for Geometry."); + } + + 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); + } + + private void beforeEachSetup() throws SQLException { + Utils.dropTableIfExists(geomTableName, stmt); + Utils.dropTableIfExists(geogTableName, stmt); + stmt.executeUpdate("Create table " + geomTableName + " (c1 geometry)"); + stmt.executeUpdate("Create table " + geogTableName + " (c1 geography)"); + } + + private void testWkt(String geoWKT) 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(); + + rs = (SQLServerResultSet) stmt.executeQuery("select c1 from " + geomTableName); + rs.next(); + assertEquals(rs.getGeometry(1).asTextZM(), geoWKT); + + 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); + } + + /** + * Prepare test + * + * @throws SQLException + * @throws SecurityException + * @throws IOException + */ + @BeforeAll + public static void setupHere() throws SQLException, SecurityException, IOException { + con = (SQLServerConnection) DriverManager.getConnection(connectionString); + stmt = con.createStatement(); + } + + /** + * drop the tables + * + * @throws SQLException + */ + @AfterAll + public static void afterAll() throws SQLException { + Utils.dropTableIfExists(geomTableName, stmt); + Utils.dropTableIfExists(geogTableName, stmt); + + if (null != stmt) { + stmt.close(); + } + + if (null != pstmt) { + pstmt.close(); + } + + if (null != rs) { + rs.close(); + } + + if (null != con) { + con.close(); + } + } +} From a747361423aa02f26518902bf80040c2eb3d7ea1 Mon Sep 17 00:00:00 2001 From: Peter Bae Date: Tue, 21 Nov 2017 09:55:22 -0800 Subject: [PATCH 09/41] more testing --- .../microsoft/sqlserver/jdbc/Geography.java | 12 +- .../microsoft/sqlserver/jdbc/Geometry.java | 10 +- .../jdbc/SQLServerSpatialDatatype.java | 38 +- .../SQLServerSpatialDatatypeTest.java | 554 +++++++++++++++++- 4 files changed, 594 insertions(+), 20 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/Geography.java b/src/main/java/com/microsoft/sqlserver/jdbc/Geography.java index 2c79a0a0d..aab2715b5 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/Geography.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/Geography.java @@ -21,7 +21,13 @@ private Geography(String WellKnownText, int srid) { this.wkt = WellKnownText; this.srid = srid; - parseWKTForSerialization(this, currentWktPos, -1, false); + try { + parseWKTForSerialization(this, currentWktPos, -1, false); + } + catch (StringIndexOutOfBoundsException e) { + throw new IllegalArgumentException("Reached unexpected end of WKT. Please make sure WKT is valid."); + } + serializeToWkb(false); isNull = false; } @@ -85,12 +91,13 @@ public static Geography deserialize(byte[] wkb) { /** * Returns a Geography instance from an Open Geospatial Consortium (OGC) Well-Known Text (WKT) representation. + * SRID is defaulted to 4326. * * @param wkt * @return */ public static Geography parse(String wkt) { - return new Geography(wkt, 0); + return new Geography(wkt, 4326); } /** @@ -300,7 +307,6 @@ protected void parseWkb() { readMvalues(); } - //TODO: do I need to do anything when it's isSinglePoint or isSingleLineSegment? if (!(isSinglePoint || isSingleLineSegment)) { readNumberOfFigures(); diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java b/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java index 0b955f07c..fef747efd 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java @@ -21,7 +21,13 @@ private Geometry(String WellKnownText, int srid) { this.wkt = WellKnownText; this.srid = srid; - parseWKTForSerialization(this, currentWktPos, -1, false); + try { + parseWKTForSerialization(this, currentWktPos, -1, false); + } + catch (StringIndexOutOfBoundsException e) { + throw new IllegalArgumentException("Reached unexpected end of WKT. Please make sure WKT is valid."); + } + serializeToWkb(false); isNull = false; } @@ -85,6 +91,7 @@ public static Geometry deserialize(byte[] wkb) { /** * Returns a Geometry instance from an Open Geospatial Consortium (OGC) Well-Known Text (WKT) representation. + * SRID is defaulted to 0. * * @param wkt * @return @@ -300,7 +307,6 @@ protected void parseWkb() { readMvalues(); } - //TODO: do I need to do anything when it's isSinglePoint or isSingleLineSegment? if (!(isSinglePoint || isSingleLineSegment)) { readNumberOfFigures(); diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerSpatialDatatype.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerSpatialDatatype.java index 3b8ff354a..d26eacc13 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerSpatialDatatype.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerSpatialDatatype.java @@ -197,7 +197,13 @@ protected void parseWKTForSerialization(SQLServerSpatialDatatype sd, int startPo String nextToken = getNextStringToken().toUpperCase(Locale.US); int thisShapeIndex; - InternalSpatialDatatype isd = InternalSpatialDatatype.valueOf(nextToken); + InternalSpatialDatatype isd = InternalSpatialDatatype.INVALID_TYPE; + try { + isd = InternalSpatialDatatype.valueOf(nextToken); + } + catch (Exception e) { + throw new IllegalArgumentException("Illegal character at wkt position " + currentWktPos); + } byte fa = 0; if (version == 1 && (nextToken.equals("CIRCULARSTRING") || nextToken.equals("COMPOUNDCURVE") || @@ -558,10 +564,7 @@ protected void constructCurvepolygonWKT(int figureStartIndex, int figureEndIndex byte segment = segments[segmentStartIndex].getSegmentType(); constructSegmentWKT(segmentStartIndex, segment, pointEndIndex); - if (segmentStartIndex >= segmentEndIndex - 1) { - appendToWKTBuffers(")"); - // about to exit while loop, but not the last segment = we are closing Compoundcurve. - } else if (!(currentPointIndex < pointEndIndex)) { + if (!(currentPointIndex < pointEndIndex)) { appendToWKTBuffers("))"); } else { switch (segment) { @@ -590,12 +593,10 @@ protected void constructCurvepolygonWKT(int figureStartIndex, int figureEndIndex return; } - if (i == figureEndIndex - 1) { - appendToWKTBuffers(")"); - } else { + //Append a comma if this is not the last figure of the shape. + if (i != figureEndIndex - 1) { appendToWKTBuffers(", "); } - } } @@ -713,16 +714,28 @@ protected void readPointWkt() { throw new IllegalArgumentException("Illegal character at wkt position " + currentWktPos); } + numOfCoordinates++; + skipWhiteSpaces(); + + // 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) != ')') { + throw new IllegalArgumentException("Illegal character at wkt position " + currentWktPos); + } + } + if (wkt.charAt(currentWktPos) == ',') { + // need at least 2 coordinates + if (numOfCoordinates == 1) { + throw new IllegalArgumentException("Illegal character at wkt position " + currentWktPos); + } currentWktPos++; skipWhiteSpaces(); - numOfCoordinates++; break; } skipWhiteSpaces(); - - numOfCoordinates++; } if (numOfCoordinates == 4) { @@ -1058,7 +1071,6 @@ protected void createSerializationProperties() { serializationProperties += isSingleLineSegmentMask; } - //TODO look into how the isLargerThanHemisphere is created if (version == 2) { if (isLargerThanHemisphere) { serializationProperties += isLargerThanHemisphereMask; 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 c87b0c230..cdbaca8c0 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/datatypes/SQLServerSpatialDatatypeTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/datatypes/SQLServerSpatialDatatypeTest.java @@ -13,6 +13,8 @@ import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; +import java.util.ArrayList; +import java.util.List; import org.apache.commons.codec.DecoderException; import org.apache.commons.codec.binary.Hex; @@ -42,6 +44,7 @@ public class SQLServerSpatialDatatypeTest extends AbstractTest { static Statement stmt = null; static String geomTableName = "geometryTestTable"; static String geogTableName = "geographyTestTable"; + static String spatialDatatypeTableName = "spatialDatatypeTestTable"; static SQLServerPreparedStatement pstmt = null; static SQLServerResultSet rs = null; @@ -384,6 +387,539 @@ public void testFullGlobeWkt() throws SQLException { assertEquals(rs.getGeography(1).asTextZM(), geoWKT); } + @Test + public void testIrregularCases() throws SQLException { + beforeEachSetup(); + + String geoWKT = " GeOMETRyCOLlECTION(POINT( 3e2 2E1 1 ), GEOMETRYCOLLECTION EmPTy , GeometryCollection(GEometryCOLLEction(GEometryCOLLEction Empty)), " + + "POLYGON( (0 0 2, 1 10 3, 1 0 4, 0 0 2)) )"; + + String geoWKTSS = "GEOMETRYCOLLECTION(POINT(300 20 1), GEOMETRYCOLLECTION EMPTY, GEOMETRYCOLLECTION(GEOMETRYCOLLECTION(GEOMETRYCOLLECTION EMPTY)), POLYGON((0 0 2, 1 10 3, 1 0 4, 0 0 2)))"; + + testWkt(geoWKT, geoWKTSS); + } + + @Test + public void testIllegalCases() throws SQLException { + //Not enough closing bracket case + String geoWKT = "MULTIPOLYGON(((1 1, 1 2, 2 1, 1 1), (0 0, 0 3, 3 3, 3 0, 0 0 7)), ((9 9, 9 10, 10 9, 9 9))"; + + try { + testWkt(geoWKT); + } + catch (IllegalArgumentException e) { + assertEquals(e.getMessage(), "Reached unexpected end of WKT. Please make sure WKT is valid."); + } + + //Not enough closing and opening bracket case + geoWKT = "MULTIPOLYGON((1 1, 1 2, 2 1, 1 1), (0 0, 0 3, 3 3, 3 0, 0 0 7)), ((9 9, 9 10, 10 9, 9 9))"; + + try { + testWkt(geoWKT); + } + catch (IllegalArgumentException e) { + assertEquals(e.getMessage(), "Illegal character at wkt position 14"); + } + + //Too many closing bracket + geoWKT = "MULTIPOLYGON(((1 1, 1 2, 2 1, 1 1), (0 0, 0 3, 3 3, 3 0, 0 0 7)), ((9 9, 9 10, 10 9, 9 9))))"; + + try { + testWkt(geoWKT); + } + catch (IllegalArgumentException e) { + assertEquals(e.getMessage(), "Illegal character at wkt position 91"); + } + + //Too many opening bracket + geoWKT = "MULTIPOLYGON((((1 1, 1 2, 2 1, 1 1), (0 0, 0 3, 3 3, 3 0, 0 0 7)), ((9 9, 9 10, 10 9, 9 9)))"; + + try { + testWkt(geoWKT); + } + catch (IllegalArgumentException e) { + assertEquals(e.getMessage(), "Illegal character at wkt position 15"); + } + + //Too many coordinates + geoWKT = "MULTIPOLYGON(((1 1 3 4 5, 1 2, 2 1, 1 1), (0 0, 0 3, 3 3, 3 0, 0 0 7)), ((9 9, 9 10, 10 9, 9 9)))"; + + try { + testWkt(geoWKT); + } + catch (IllegalArgumentException e) { + assertEquals(e.getMessage(), "Illegal character at wkt position 23"); + } + + //Too little coordinates + geoWKT = "MULTIPOLYGON(((1 , 1 2, 2 1, 1 1), (0 0, 0 3, 3 3, 3 0, 0 0 7)), ((9 9, 9 10, 10 9, 9 9)))"; + + try { + testWkt(geoWKT); + } + catch (IllegalArgumentException e) { + assertEquals(e.getMessage(), "Illegal character at wkt position 17"); + } + + //Incorrect data type + geoWKT = "IvnalidPolygon(((1 , 1 2, 2 1, 1 1), (0 0, 0 3, 3 3, 3 0, 0 0 7)), ((9 9, 9 10, 10 9, 9 9)))"; + + try { + testWkt(geoWKT); + } + catch (IllegalArgumentException e) { + assertEquals(e.getMessage(), "Illegal character at wkt position 14"); + } + + // too many commas + geoWKT = "MULTIPOLYGON(((1 1, 1 2, 2 1, 1 1),, (0 0, 0 3, 3 3, 3 0, 0 0 7)), ((9 9, 9 10, 10 9, 9 9)))"; + + try { + testWkt(geoWKT); + } + catch (IllegalArgumentException e) { + assertEquals(e.getMessage(), "Illegal character at wkt position 35"); + } + + // too little commas + geoWKT = "MULTIPOLYGON(((1 1, 1 2, 2 1, 1 1) (0 0, 0 3, 3 3, 3 0, 0 0 7)), ((9 9, 9 10, 10 9, 9 9)))"; + + try { + testWkt(geoWKT); + } + catch (IllegalArgumentException e) { + assertEquals(e.getMessage(), "Illegal character at wkt position 35"); + } + } + + @Test + 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 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 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))))"; + + List geoWKTList = new ArrayList(); + + geoWKTList.add(geoWKTPoint); + geoWKTList.add(geoWKTLineString); + geoWKTList.add(geoWKTCircularString); + geoWKTList.add(geoWKTCompoundCurve); + geoWKTList.add(geoWKTCurvePolygon); + geoWKTList.add(geoWKTPolygon); + geoWKTList.add(geoWKTMultiPoint); + geoWKTList.add(geoWKTMultiLineString); + geoWKTList.add(geoWKTMultiPolygon); + geoWKTList.add(geoWKTGeometryCollection); + + Geometry geomWKT; + 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)); + } + + //Geography + pstmt = (SQLServerPreparedStatement) con.prepareStatement("insert into "+ geogTableName + " values (?)"); + + geogWKT = Geography.STGeomFromText(geoWKTPoint, 4326); + pstmt.setGeography(1, geogWKT); + + pstmt.executeUpdate(); + + geogWKT = Geography.STGeomFromText(geoWKTLineString, 4326); + pstmt.setGeography(1, geogWKT); + + pstmt.executeUpdate(); + + geogWKT = Geography.STGeomFromText(geoWKTCircularString, 4326); + pstmt.setGeography(1, geogWKT); + + pstmt.executeUpdate(); + + geogWKT = Geography.STGeomFromText(geoWKTCompoundCurve, 4326); + pstmt.setGeography(1, geogWKT); + + pstmt.executeUpdate(); + + geogWKT = Geography.STGeomFromText(geoWKTCurvePolygon, 4326); + pstmt.setGeography(1, geogWKT); + + pstmt.executeUpdate(); + + geogWKT = Geography.STGeomFromText(geoWKTPolygon, 4326); + pstmt.setGeography(1, geogWKT); + + pstmt.executeUpdate(); + + geogWKT = Geography.STGeomFromText(geoWKTMultiPoint, 4326); + pstmt.setGeography(1, geogWKT); + + pstmt.executeUpdate(); + + geogWKT = Geography.STGeomFromText(geoWKTMultiLineString, 4326); + pstmt.setGeography(1, geogWKT); + + pstmt.executeUpdate(); + + geogWKT = Geography.STGeomFromText(geoWKTMultiPolygon, 4326); + pstmt.setGeography(1, geogWKT); + + pstmt.executeUpdate(); + + geogWKT = Geography.STGeomFromText(geoWKTGeometryCollection, 4326); + pstmt.setGeography(1, geogWKT); + + 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)); + } + } + + @Test + public void testMixedAllTypes() throws SQLException { + beforeEachSetupSpatialDatatype(); + + String geoWKTPoint = "POINT(30 12.12312312 5 6)"; + 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))"; + 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 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))))"; + + String s = "some string"; + Double d = 31.34; + int i2 = 5; + + List geoWKTList = new ArrayList(); + + geoWKTList.add(geoWKTPoint); + geoWKTList.add(geoWKTLineString); + geoWKTList.add(geoWKTCircularString); + geoWKTList.add(geoWKTCompoundCurve); + geoWKTList.add(geoWKTCurvePolygon); + geoWKTList.add(geoWKTPolygon); + geoWKTList.add(geoWKTMultiPoint); + geoWKTList.add(geoWKTMultiLineString); + geoWKTList.add(geoWKTMultiPolygon); + geoWKTList.add(geoWKTGeometryCollection); + + 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); + } + } + + @Test + public void testDecimalRounding() throws SQLException { + beforeEachSetup(); + + String geoWKT = "POINT(3 40.7777777777777777777 5 6)"; + + String geoWKTSS = "POINT(3 40.77777777777778 5 6)"; + + testWkt(geoWKT, geoWKTSS); + } + + @Test + public void testParse() throws SQLException { + beforeEachSetup(); + + String geoWKT = "GEOMETRYCOLLECTION(POINT(300 20 1), GEOMETRYCOLLECTION EMPTY, GEOMETRYCOLLECTION(GEOMETRYCOLLECTION(GEOMETRYCOLLECTION EMPTY)), POLYGON((0 0 2, 1 10 3, 1 0 4, 0 0 2)))"; + + Geometry geomWKT = Geometry.parse(geoWKT); + Geography geogWKT = Geography.parse(geoWKT); + + 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); + + 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); + } + + @Test + public void testPoint() throws SQLException { + beforeEachSetup(); + + String geoWKT = "POINT(1 2)"; + + 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(); + + 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(); + + rs = (SQLServerResultSet) stmt.executeQuery("select c1 from " + geogTableName); + rs.next(); + assertEquals(rs.getGeography(1).asTextZM(), geoWKT); + assertEquals(rs.getGeography(1).getSrid(), 4326); + } + + @Test + public void testSTAsText() throws SQLException { + beforeEachSetup(); + + String geoWKT = "GEOMETRYCOLLECTION(POINT(300 20 1), GEOMETRYCOLLECTION EMPTY, GEOMETRYCOLLECTION(GEOMETRYCOLLECTION(GEOMETRYCOLLECTION EMPTY)), POLYGON((0 0 2, 1 10 3, 1 0 4, 0 0 2)))"; + + String geoWKTSS = "GEOMETRYCOLLECTION(POINT(300 20), GEOMETRYCOLLECTION EMPTY, GEOMETRYCOLLECTION(GEOMETRYCOLLECTION(GEOMETRYCOLLECTION EMPTY)), POLYGON((0 0, 1 10, 1 0, 0 0)))"; + + 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(); + + 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(); + + rs = (SQLServerResultSet) stmt.executeQuery("select c1 from " + geogTableName); + rs.next(); + assertEquals(rs.getGeography(1).STAsText(), geoWKTSS); + } + + @Test + public void testSTAsBinary() throws SQLException { + beforeEachSetup(); + + String geoWKT = "POINT(3 40 5 6)"; + String geoWKT2 = "POINT(3 40)"; + + Geometry geomWKT = Geometry.STGeomFromText(geoWKT, 0); + Geography geogWKT = Geography.STGeomFromText(geoWKT, 4326); + + byte[] geomWKB = geomWKT.STAsBinary(); + byte[] geogWKB = geogWKT.STAsBinary(); + + Geometry geomWKT2 = Geometry.STGeomFromText(geoWKT2, 0); + Geography geogWKT2 = Geography.STGeomFromText(geoWKT2, 4326); + + byte[] geomWKB2 = geomWKT2.STAsBinary(); + byte[] geogWKB2 = geogWKT2.STAsBinary(); + + assertEquals(geomWKB, geomWKB2); + assertEquals(geogWKB, geogWKB2); + } + private void beforeEachSetup() throws SQLException { Utils.dropTableIfExists(geomTableName, stmt); Utils.dropTableIfExists(geogTableName, stmt); @@ -391,7 +927,21 @@ private void beforeEachSetup() throws SQLException { stmt.executeUpdate("Create table " + geogTableName + " (c1 geography)"); } + private void beforeEachSetupSpatialDatatype() throws SQLException { + Utils.dropTableIfExists(spatialDatatypeTableName, stmt); + stmt.executeUpdate("Create table " + spatialDatatypeTableName + + " (c1 geometry," + + "c2 geography," + + "c3 nvarchar(512)," + + "c4 decimal(28,4)," + + "c5 int)"); + } + private void testWkt(String geoWKT) throws SQLException { + testWkt(geoWKT, geoWKT); + } + + private void testWkt(String geoWKT, String geoWKTSS) throws SQLException { Geometry geomWKT = Geometry.STGeomFromText(geoWKT, 0); Geography geogWKT = Geography.STGeomFromText(geoWKT, 4326); @@ -403,7 +953,7 @@ private void testWkt(String geoWKT) throws SQLException { rs = (SQLServerResultSet) stmt.executeQuery("select c1 from " + geomTableName); rs.next(); - assertEquals(rs.getGeometry(1).asTextZM(), geoWKT); + assertEquals(rs.getGeometry(1).asTextZM(), geoWKTSS); pstmt = (SQLServerPreparedStatement) con.prepareStatement("insert into "+ geogTableName + " values (?)"); @@ -413,7 +963,7 @@ private void testWkt(String geoWKT) throws SQLException { rs = (SQLServerResultSet) stmt.executeQuery("select c1 from " + geogTableName); rs.next(); - assertEquals(rs.getGeography(1).asTextZM(), geoWKT); + assertEquals(rs.getGeography(1).asTextZM(), geoWKTSS); } /** From 4ae99af236f1b0085e5cae7e5c1a3c055cc5b275 Mon Sep 17 00:00:00 2001 From: Peter Bae Date: Mon, 27 Nov 2017 16:40:22 -0800 Subject: [PATCH 10/41] javadoc updates --- .../microsoft/sqlserver/jdbc/Geography.java | 32 ++++----- .../microsoft/sqlserver/jdbc/Geometry.java | 32 ++++----- .../jdbc/SQLServerSpatialDatatype.java | 68 +++++++++++-------- 3 files changed, 71 insertions(+), 61 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/Geography.java b/src/main/java/com/microsoft/sqlserver/jdbc/Geography.java index aab2715b5..0c0f94c2a 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/Geography.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/Geography.java @@ -60,9 +60,9 @@ public Geography() { * 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. * - * @param wkt - * @param srid - * @return + * @param wkt WKT + * @param srid SRID + * @return Geography instance */ public static Geography STGeomFromText(String wkt, int srid) { return new Geography(wkt, srid); @@ -72,8 +72,8 @@ public static Geography STGeomFromText(String wkt, int srid) { * Returns a Geography instance from an Open Geospatial Consortium (OGC) * Well-Known Binary (WKB) representation. * - * @param wkb - * @return + * @param wkb WKB + * @return Geography instance */ public static Geography STGeomFromWKB(byte[] wkb) { return new Geography(wkb); @@ -82,8 +82,8 @@ public static Geography STGeomFromWKB(byte[] wkb) { /** * Returns a constructed Geography from an internal SQL Server format for spatial data. * - * @param wkb - * @return + * @param wkb WKB + * @return Geography instance */ public static Geography deserialize(byte[] wkb) { return new Geography(wkb); @@ -93,8 +93,8 @@ public static Geography deserialize(byte[] wkb) { * Returns a Geography instance from an Open Geospatial Consortium (OGC) Well-Known Text (WKT) representation. * SRID is defaulted to 4326. * - * @param wkt - * @return + * @param wkt WKt + * @return Geography instance */ public static Geography parse(String wkt) { return new Geography(wkt, 4326); @@ -103,10 +103,10 @@ public static Geography parse(String wkt) { /** * Constructs a Geography instance that represents a Point instance from its X and Y values and an SRID. * - * @param x - * @param y - * @param srid - * @return + * @param x x coordinate + * @param y y coordinate + * @param srid SRID + * @return Geography instance */ public static Geography point(double x, double y, int srid) { return new Geography("POINT (" + x + " " + y + ")", srid); @@ -136,7 +136,7 @@ public String STAsText() { /** * Returns the Open Geospatial Consortium (OGC) Well-Known Binary (WKB) representation of a * Geography instance. This value will not contain any Z or M values carried by the instance. - * @return + * @return WKB */ public byte[] STAsBinary() { if (null == wkbNoZM) { @@ -148,7 +148,7 @@ public byte[] STAsBinary() { /** * Returns the bytes that represent an internal SQL Server format of Geography type. * - * @return + * @return WKB */ public byte[] serialize() { return wkb; @@ -205,7 +205,7 @@ public int STNumPoints() { /** * Returns the Open Geospatial Consortium (OGC) type name represented by a Geography instance. * - * @return + * @return type name */ public String STGeographyType() { if (null != internalType) { diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java b/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java index fef747efd..aff52f1a0 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java @@ -60,9 +60,9 @@ public Geometry() { * Returns 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 - * @param srid - * @return + * @param wkt WKT + * @param srid SRID + * @return Geometry instance */ public static Geometry STGeomFromText(String wkt, int srid) { return new Geometry(wkt, srid); @@ -72,8 +72,8 @@ public static Geometry STGeomFromText(String wkt, int srid) { * Returns a Geometry instance from an Open Geospatial Consortium (OGC) * Well-Known Binary (WKB) representation. * - * @param wkb - * @return + * @param wkb WKB + * @return Geometry instance */ public static Geometry STGeomFromWKB(byte[] wkb) { return new Geometry(wkb); @@ -82,8 +82,8 @@ public static Geometry STGeomFromWKB(byte[] wkb) { /** * Returns a constructed Geometry from an internal SQL Server format for spatial data. * - * @param wkb - * @return + * @param wkb WKB + * @return Geometry instance */ public static Geometry deserialize(byte[] wkb) { return new Geometry(wkb); @@ -93,8 +93,8 @@ public static Geometry deserialize(byte[] wkb) { * Returns a Geometry instance from an Open Geospatial Consortium (OGC) Well-Known Text (WKT) representation. * SRID is defaulted to 0. * - * @param wkt - * @return + * @param wkt WKT + * @return Geometry instance */ public static Geometry parse(String wkt) { return new Geometry(wkt, 0); @@ -103,10 +103,10 @@ public static Geometry parse(String wkt) { /** * Constructs a Geometry instance that represents a Point instance from its X and Y values and an SRID. * - * @param x - * @param y - * @param srid - * @return + * @param x x coordinate + * @param y y coordinate + * @param srid SRID + * @return Geometry instance */ public static Geometry point(double x, double y, int srid) { return new Geometry("POINT (" + x + " " + y + ")", srid); @@ -136,7 +136,7 @@ public String STAsText() { /** * Returns the Open Geospatial Consortium (OGC) Well-Known Binary (WKB) representation of a * Geometry instance. This value will not contain any Z or M values carried by the instance. - * @return + * @return WKB */ public byte[] STAsBinary() { if (null == wkbNoZM) { @@ -148,7 +148,7 @@ public byte[] STAsBinary() { /** * Returns the bytes that represent an internal SQL Server format of Geometry type. * - * @return + * @return WKB */ public byte[] serialize() { return wkb; @@ -205,7 +205,7 @@ public int STNumPoints() { /** * Returns the Open Geospatial Consortium (OGC) type name represented by a geometry instance. * - * @return + * @return type name */ public String STGeometryType() { if (null != internalType) { diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerSpatialDatatype.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerSpatialDatatype.java index d26eacc13..f290959f3 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerSpatialDatatype.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerSpatialDatatype.java @@ -105,11 +105,11 @@ abstract class SQLServerSpatialDatatype { * Create the WKT representation of Geometry/Geography from the deserialized data. * * @param sd the Geometry/Geography instance. - * @param isd - * @param pointIndexEnd - * @param figureIndexEnd - * @param segmentIndexEnd - * @param shapeIndexEnd + * @param isd internal spatial datatype object + * @param pointIndexEnd upper bound for reading points + * @param figureIndexEnd upper bound for reading figures + * @param segmentIndexEnd upper bound for reading segments + * @param shapeIndexEnd upper bound for reading shapes */ protected void constructWKT(SQLServerSpatialDatatype sd, InternalSpatialDatatype isd, int pointIndexEnd, int figureIndexEnd, int segmentIndexEnd, int shapeIndexEnd) { @@ -359,8 +359,8 @@ protected void constructPointWKT(int pointIndex) { /** * Constructs a line in WKT form. * - * @param pointStartIndex - * @param pointEndIndex + * @param pointStartIndex . + * @param pointEndIndex . */ protected void constructLineWKT(int pointStartIndex, int pointEndIndex) { for (int i = pointStartIndex; i < pointEndIndex; i++) { @@ -376,8 +376,8 @@ protected void constructLineWKT(int pointStartIndex, int pointEndIndex) { /** * Constructs a shape (simple Geometry/Geography entities that are contained within a single bracket) in WKT form. * - * @param figureStartIndex - * @param figureEndIndex + * @param figureStartIndex . + * @param figureEndIndex . */ protected void constructShapeWKT(int figureStartIndex, int figureEndIndex) { for (int i = figureStartIndex; i < figureEndIndex; i++) { @@ -399,8 +399,8 @@ protected void constructShapeWKT(int figureStartIndex, int figureEndIndex) { /** * Constructs a mutli-shape (MultiPoint / MultiLineString) in WKT form. * - * @param figureStartIndex - * @param figureEndIndex + * @param shapeStartIndex . + * @param shapeEndIndex . */ protected void constructMultiShapeWKT(int shapeStartIndex, int shapeEndIndex) { for (int i = shapeStartIndex + 1; i < shapeEndIndex; i++) { @@ -418,9 +418,9 @@ protected void constructMultiShapeWKT(int shapeStartIndex, int shapeEndIndex) { /** * Constructs a CompoundCurve in WKT form. * - * @param segmentStartIndex - * @param segmentEndIndex - * @param pointEndIndex + * @param segmentStartIndex . + * @param segmentEndIndex . + * @param pointEndIndex . */ protected void constructCompoundcurveWKT(int segmentStartIndex, int segmentEndIndex, int pointEndIndex) { for (int i = segmentStartIndex; i < segmentEndIndex; i++) { @@ -454,9 +454,8 @@ protected void constructCompoundcurveWKT(int segmentStartIndex, int segmentEndIn /** * Constructs a MultiPolygon in WKT form. * - * @param segmentStartIndex - * @param segmentEndIndex - * @param pointEndIndex + * @param shapeStartIndex . + * @param shapeEndIndex . */ protected void constructMultipolygonWKT(int shapeStartIndex, int shapeEndIndex) { int figureStartIndex; @@ -519,9 +518,10 @@ protected void constructMultipolygonWKT(int shapeStartIndex, int shapeEndIndex) /** * Constructs a CurvePolygon in WKT form. * - * @param segmentStartIndex - * @param segmentEndIndex - * @param pointEndIndex + * @param figureStartIndex . + * @param figureEndIndex . + * @param segmentStartIndex . + * @param segmentEndIndex . */ protected void constructCurvepolygonWKT(int figureStartIndex, int figureEndIndex, int segmentStartIndex, int segmentEndIndex) { for (int i = figureStartIndex; i < figureEndIndex; i++) { @@ -608,9 +608,9 @@ protected void constructCurvepolygonWKT(int figureStartIndex, int figureEndIndex * the currentPointIndex depending on what segment comes next, since it may have been reused (and it's reflected * in the array of points) * - * @param segmentStartIndex - * @param segmentEndIndex - * @param pointEndIndex + * @param currentSegment . + * @param segment . + * @param pointEndIndex . */ protected void constructSegmentWKT(int currentSegment, byte segment, int pointEndIndex) { switch (segment) { @@ -670,7 +670,7 @@ protected void constructSegmentWKT(int currentSegment, byte segment, int pointEn /** * The starting point for constructing a GeometryCollection type in WKT form. * - * @param shapeEndIndex + * @param shapeEndIndex . */ protected void constructGeometryCollectionWKT(int shapeEndIndex) { currentShapeIndex++; @@ -760,8 +760,8 @@ protected void readLineWkt() { /** * Reads a shape (simple Geometry/Geography entities that are contained within a single bracket) WKT. * - * @param parentShapeIndex - * @param nextToken + * @param parentShapeIndex shape index of the parent shape that called this method + * @param nextToken next string token */ protected void readShapeWkt(int parentShapeIndex, String nextToken) { byte fa = FA_POINT; @@ -848,6 +848,9 @@ protected void readCurvePolygon() { /** * Reads a MultiPolygon WKT + * + * @param thisShapeIndex shape index of current shape + * @param nextToken next string token */ protected void readMultiPolygonWkt(int thisShapeIndex, String nextToken) { while (currentWktPos < wkt.length() && wkt.charAt(currentWktPos) != ')') { @@ -871,6 +874,9 @@ protected void readMultiPolygonWkt(int thisShapeIndex, String nextToken) { /** * Reads a Segment WKT + * + * @param segmentType segment type + * @param isFirstIteration flag that indicates if this is the first iteration from the loop outside */ protected void readSegmentWkt(int segmentType, boolean isFirstIteration) { segmentList.add(new Segment((byte) segmentType)); @@ -903,6 +909,8 @@ protected void readSegmentWkt(int segmentType, boolean isFirstIteration) { /** * Reads a CompoundCurve WKT + * + * @param isFirstIteration flag that indicates if this is the first iteration from the loop outside */ protected void readCompoundCurveWkt(boolean isFirstIteration) { while (currentWktPos < wkt.length() && wkt.charAt(currentWktPos) != ')') { @@ -1264,7 +1272,8 @@ private void incrementPointNumStartIfPointNotReused(int pointEndIndex) { /** * Helper used for resurcive iteration for constructing GeometryCollection in WKT form. - * @param shapeEndIndex + * + * @param shapeEndIndex . */ private void constructGeometryCollectionWKThelper(int shapeEndIndex) { //phase 1: assume that there is no multi - stuff and no geometrycollection @@ -1434,8 +1443,9 @@ private void constructGeometryCollectionWKThelper(int shapeEndIndex) { * Calculates how many segments will be used by this shape. * Needed to determine when the shape that uses segments (e.g. CompoundCurve) needs to stop reading * in cases where the CompoundCurve is included as part of GeometryCollection. - * @param segmentStart - * @param pointDifference + * + * @param segmentStart . + * @param pointDifference number of points that were assigned to this segment to be used. * @return the number of segments that will be used by this shape. */ private int calculateSegmentIncrement(int segmentStart, From 475848596cba6a4172dde02cb214aa6b6202ff3d Mon Sep 17 00:00:00 2001 From: rene-ye Date: Wed, 3 Jan 2018 11:40:16 -0800 Subject: [PATCH 11/41] Blob fix Fills the contents of a blob and makes it availible for use after the RS or statement has been closed. Addresses the TDS issue from #567. --- .../sqlserver/jdbc/SQLServerBlob.java | 9 +++++++ .../sqlserver/jdbc/SQLServerResultSet.java | 27 ++++++++++++++++++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBlob.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBlob.java index 47927d6a6..094c9f863 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBlob.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBlob.java @@ -236,6 +236,15 @@ public long length() throws SQLException { return value.length; } + /** + * Public function for the result set to maintain blobs it has created + * @throws SQLException + */ + public void fillByteArray() throws SQLException { + if(!isClosed) + getBytesFromStream(); + } + /** * Converts stream to byte[] * @throws SQLServerException diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java index 9b7933705..0d958553b 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java @@ -26,7 +26,9 @@ import java.sql.SQLWarning; import java.sql.SQLXML; import java.text.MessageFormat; +import java.util.ArrayList; import java.util.Calendar; +import java.util.List; import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; @@ -126,6 +128,7 @@ final void setCurrentRowType(RowType rowType) { * occurs */ private Closeable activeStream; + private List streamObjects = new ArrayList(); /** * A window of fetchSize quickly accessible rows for scrollable result sets @@ -576,7 +579,10 @@ private void closeInternal() { // Mark this ResultSet as closed, then clean up. isClosed = true; - + + //fill all unclosed objects which rely on the stream + fillBlobs(); + // Discard the current fetch buffer contents. discardFetchBuffer(); @@ -2664,6 +2670,7 @@ public Blob getBlob(int i) throws SQLServerException { checkClosed(); Blob value = (Blob) getValue(i, JDBCType.BLOB); loggerExternal.exiting(getClassNameLogging(), "getBlob", value); + streamObjects.add(value); return value; } @@ -2672,6 +2679,7 @@ public Blob getBlob(String colName) throws SQLServerException { checkClosed(); Blob value = (Blob) getValue(findColumn(colName), JDBCType.BLOB); loggerExternal.exiting(getClassNameLogging(), "getBlob", value); + streamObjects.add(value); return value; } @@ -6514,6 +6522,23 @@ final void doServerFetch(int fetchType, scrollWindow.reset(); } } + + /* + * Iterates through the list of objects which rely on the stream that's about to be closed, filling them with their data + * Will skip over closed blobs, implemented in SQLServerBlob + */ + private void fillBlobs() { + for(Object o : streamObjects) { + if(o instanceof SQLServerBlob) { + try { + ((SQLServerBlob) o).fillByteArray(); + } catch (SQLException e) { + if (logger.isLoggable(java.util.logging.Level.FINER)) + logger.finer(toString() + "Filling blobs before closing: " + e.getMessage()); + } + } + } + } /** * Discards the contents of the current fetch buffer. From 5f96797f8308975b808e27a7fa63000686c65b63 Mon Sep 17 00:00:00 2001 From: rene-ye Date: Thu, 4 Jan 2018 11:05:12 -0800 Subject: [PATCH 12/41] changed function accessability --- src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBlob.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBlob.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBlob.java index 094c9f863..3ddb57aac 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBlob.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBlob.java @@ -237,10 +237,10 @@ public long length() throws SQLException { } /** - * Public function for the result set to maintain blobs it has created + * Function for the result set to maintain blobs it has created * @throws SQLException */ - public void fillByteArray() throws SQLException { + void fillByteArray() throws SQLException { if(!isClosed) getBytesFromStream(); } From 83da731f18e597e8ff812b2e8fa107d8a956979c Mon Sep 17 00:00:00 2001 From: rene-ye Date: Fri, 26 Jan 2018 10:02:33 -0800 Subject: [PATCH 13/41] Length fix Fix to issue #611, where calling length() would close the stream. --- src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBlob.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBlob.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBlob.java index 3ddb57aac..6d23038f9 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBlob.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBlob.java @@ -232,6 +232,8 @@ public byte[] getBytes(long pos, */ public long length() throws SQLException { checkClosed(); + if (value == null) + return (long)((PLPInputStream)activeStreams.get(0)).payloadLength; getBytesFromStream(); return value.length; } From ded790c6cd52a9d63aebb261257b44a2d2caf63f Mon Sep 17 00:00:00 2001 From: rene-ye Date: Mon, 5 Feb 2018 13:45:14 -0800 Subject: [PATCH 14/41] Added information to error message To debug a random mismatch error which can't be reproduced locally. --- .../sqlserver/testframework/util/ComparisonUtil.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/test/java/com/microsoft/sqlserver/testframework/util/ComparisonUtil.java b/src/test/java/com/microsoft/sqlserver/testframework/util/ComparisonUtil.java index c942f8c9a..b297a3924 100644 --- a/src/test/java/com/microsoft/sqlserver/testframework/util/ComparisonUtil.java +++ b/src/test/java/com/microsoft/sqlserver/testframework/util/ComparisonUtil.java @@ -92,16 +92,16 @@ public static void compareExpectedAndActual(int dataType, else switch (dataType) { case java.sql.Types.BIGINT: - assertTrue((((Long) expectedValue).longValue() == ((Long) actualValue).longValue()), "Unexpected bigint value"); + assertTrue((((Long) expectedValue).longValue() == ((Long) actualValue).longValue()), "Unexpected bigint value. Expected:" + ((Long) expectedValue).longValue() + " Actual:" + ((Long) actualValue).longValue()); break; case java.sql.Types.INTEGER: - assertTrue((((Integer) expectedValue).intValue() == ((Integer) actualValue).intValue()), "Unexpected int value"); + assertTrue((((Integer) expectedValue).intValue() == ((Integer) actualValue).intValue()), "Unexpected int value. Expected:" + ((Integer) expectedValue).intValue() + " Actual:" + ((Integer) actualValue).intValue()); break; case java.sql.Types.SMALLINT: case java.sql.Types.TINYINT: - assertTrue((((Short) expectedValue).shortValue() == ((Short) actualValue).shortValue()), "Unexpected smallint/tinyint value"); + assertTrue((((Short) expectedValue).shortValue() == ((Short) actualValue).shortValue()), "Unexpected smallint/tinyint value. Expected:" + ((Short) expectedValue).shortValue() + " Actual:" + ((Short) actualValue).shortValue()); break; case java.sql.Types.BIT: @@ -115,11 +115,11 @@ public static void compareExpectedAndActual(int dataType, break; case java.sql.Types.DOUBLE: - assertTrue((((Double) expectedValue).doubleValue() == ((Double) actualValue).doubleValue()), "Unexpected float value"); + assertTrue((((Double) expectedValue).doubleValue() == ((Double) actualValue).doubleValue()), "Unexpected double value. Expected:" + ((Double) expectedValue).doubleValue() + " Actual:" + ((Double) actualValue).doubleValue()); break; case java.sql.Types.REAL: - assertTrue((((Float) expectedValue).floatValue() == ((Float) actualValue).floatValue()), "Unexpected real value"); + assertTrue((((Float) expectedValue).floatValue() == ((Float) actualValue).floatValue()), "Unexpected real/float value. Expected:" + ((Float) expectedValue).floatValue() + " Actual:" + ((Float) actualValue).floatValue()); break; case java.sql.Types.VARCHAR: From c2d0997b0aa0e36c03b130ce792e68db0bf89c6d Mon Sep 17 00:00:00 2001 From: rene-ye Date: Tue, 6 Feb 2018 11:58:21 -0800 Subject: [PATCH 15/41] unexpected error message don't attempt mismatch logic if there's no columns to look for. fall through to sql server and do a lookup on the first column. --- .../microsoft/sqlserver/jdbc/SQLServerCallableStatement.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerCallableStatement.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerCallableStatement.java index 5ad8689c8..c30eab2fa 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerCallableStatement.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerCallableStatement.java @@ -1450,6 +1450,8 @@ public NClob getNClob(String parameterName) throws SQLException { int l = 0; if (paramNames != null) l = paramNames.size(); + if (l == 0)//Server didn't return anything, user might not have access + return 1;//attempting to look up the first column will return no access exception // handle `@name` as well as `name`, since `@name` is what's returned // by DatabaseMetaData#getProcedureColumns From 923c2207ea1d283f675305f5327fc273eed863b5 Mon Sep 17 00:00:00 2001 From: rene-ye Date: Thu, 8 Feb 2018 11:13:37 -0800 Subject: [PATCH 16/41] Kerberos DC fix for automatic credential discarding --- .../sqlserver/jdbc/SQLServerConnection.java | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java index c8232d71a..6b5fc59ea 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java @@ -3428,19 +3428,24 @@ final boolean doExecute() throws SQLServerException { } } finally { - if (integratedSecurity) { - if (null != authentication) - authentication.ReleaseClientContext(); - authentication = null; - + if (integratedSecurity) { if (null != ImpersonatedUserCred) { try { - ImpersonatedUserCred.dispose(); + if (ImpersonatedUserCred.getRemainingLifetime() <= 0) { + if (null != authentication) + authentication.ReleaseClientContext(); + authentication = null; + ImpersonatedUserCred.dispose(); + } } catch (GSSException e) { if (connectionlogger.isLoggable(Level.FINER)) connectionlogger.finer(toString() + " Release of the credentials failed GSSException: " + e); } + } else { + if (null != authentication) + authentication.ReleaseClientContext(); + authentication = null; } } } From 91bf4d36423cd3ae8f6c88e6de78a79581b712d4 Mon Sep 17 00:00:00 2001 From: rene-ye Date: Thu, 22 Feb 2018 10:07:54 -0800 Subject: [PATCH 17/41] minor stream verification --- src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBlob.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBlob.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBlob.java index 6d23038f9..be5a9e465 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBlob.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBlob.java @@ -232,7 +232,7 @@ public byte[] getBytes(long pos, */ public long length() throws SQLException { checkClosed(); - if (value == null) + if (value == null && activeStreams.get(0) instanceof PLPInputStream) return (long)((PLPInputStream)activeStreams.get(0)).payloadLength; getBytesFromStream(); return value.length; From 3ba5f1be4e54361148915671cbbeaba6a0c7b9c2 Mon Sep 17 00:00:00 2001 From: rene-ye Date: Thu, 22 Feb 2018 13:18:13 -0800 Subject: [PATCH 18/41] Reimplemented Blob fixes removed arraylist to avoid costly traversal. Blobs now fill anytime the resultset moves its cursor. --- .../sqlserver/jdbc/SQLServerResultSet.java | 33 ++++++++++++++----- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java index 7c9c710bb..cb5442511 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java @@ -128,7 +128,7 @@ final void setCurrentRowType(RowType rowType) { * occurs */ private Closeable activeStream; - private List streamObjects = new ArrayList(); + private Blob activeBlob; /** * A window of fetchSize quickly accessible rows for scrollable result sets @@ -436,6 +436,8 @@ public T unwrap(Class iface) throws SQLException { // can be done with it other than closing it. if (null != rowErrorException) throw rowErrorException; + + fillBlobs(); } public boolean isClosed() throws SQLException { @@ -675,6 +677,7 @@ final Column getColumn(int columnIndex) throws SQLServerException { // before moving to another one. if (null != activeStream) { try { + fillBlobs(); activeStream.close(); } catch (IOException e) { @@ -2663,7 +2666,7 @@ public Blob getBlob(int i) throws SQLServerException { checkClosed(); Blob value = (Blob) getValue(i, JDBCType.BLOB); loggerExternal.exiting(getClassNameLogging(), "getBlob", value); - streamObjects.add(value); + activeBlob = value; return value; } @@ -2672,7 +2675,7 @@ public Blob getBlob(String colName) throws SQLServerException { checkClosed(); Blob value = (Blob) getValue(findColumn(colName), JDBCType.BLOB); loggerExternal.exiting(getClassNameLogging(), "getBlob", value); - streamObjects.add(value); + activeBlob = value; return value; } @@ -6517,20 +6520,32 @@ final void doServerFetch(int fetchType, } /* - * Iterates through the list of objects which rely on the stream that's about to be closed, filling them with their data + * Iterates through the list of objects which rely on the stream that's about to be closed, ing them with their data * Will skip over closed blobs, implemented in SQLServerBlob */ private void fillBlobs() { - for(Object o : streamObjects) { - if(o instanceof SQLServerBlob) { - try { - ((SQLServerBlob) o).fillByteArray(); + if (activeBlob != null && activeBlob instanceof SQLServerBlob) { + try { + ((SQLServerBlob)activeBlob).fillByteArray(); + } catch (SQLException e) { + if (logger.isLoggable(java.util.logging.Level.FINER)) + logger.finer(toString() + "Filling blobs before closing: " + e.getMessage()); + } finally { + activeBlob = null; + } + } + + /*for (int i = 0; i < streamObjects.size(); i++) { + if(streamObjects.get(i) instanceof SQLServerBlob) { + try { + ((SQLServerBlob) streamObjects.get(i)).fillByteArray(); + streamObjects.remove(i); } catch (SQLException e) { if (logger.isLoggable(java.util.logging.Level.FINER)) logger.finer(toString() + "Filling blobs before closing: " + e.getMessage()); } } - } + }*/ } /** From 9a1300ed1bc1036771e0f4b9a3f8c07007ee8a67 Mon Sep 17 00:00:00 2001 From: rene-ye Date: Thu, 22 Feb 2018 13:18:47 -0800 Subject: [PATCH 19/41] Additional tests added tests which specifically test blob streams and behavior after the RS is closed. --- .../sqlserver/jdbc/unit/lobs/lobsTest.java | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/unit/lobs/lobsTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/unit/lobs/lobsTest.java index af9499fa9..26a4ec266 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/unit/lobs/lobsTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/unit/lobs/lobsTest.java @@ -413,6 +413,92 @@ public void testUpdatorClob() throws Exception { String types[] = {"varchar(max)"}; testUpdateLobs(types, Clob.class); } + + @Test + @DisplayName("readBlobStreamAfterClosingRS") + public void readBlobStreamAfterClosingRS() throws Exception { + String types[] = {"varbinary(max)"}; + table = createTable(table, types, false); // create empty table + int size = 10000; + + byte[] data = new byte[size]; + ThreadLocalRandom.current().nextBytes(data); + + Blob blob = null; + InputStream stream = null; + PreparedStatement ps = conn.prepareStatement("INSERT INTO " + table.getEscapedTableName() + " VALUES(?)"); + blob = conn.createBlob(); + blob.setBytes(1, data); + ps.setBlob(1, blob); + ps.executeUpdate(); + + byte[] chunk = new byte[size]; + ResultSet rs = stmt.executeQuery("select * from " + table.getEscapedTableName()); + rs.next(); + + blob = rs.getBlob(1); + stream = blob.getBinaryStream(); + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + int read = 0; + while ((read = stream.read(chunk)) > 0) + buffer.write(chunk, 0, read); + assertEquals(chunk.length, size); + rs.close(); + stream = blob.getBinaryStream(); + buffer = new ByteArrayOutputStream(); + read = 0; + while ((read = stream.read(chunk)) > 0) + buffer.write(chunk, 0, read); + assertEquals(chunk.length, size); + + if (null != blob) + blob.free(); + dropTables(table); + } + + @Test + @DisplayName("readMultipleBlobStreamsThenCloseRS") + public void readMultipleBlobStreamsThenCloseRS() throws Exception { + String types[] = {"varbinary(max)"}; + table = createTable(table, types, false); + int size = 10000; + + byte[] data = new byte[size]; + Blob[] blobs = {null, null, null}; + InputStream stream = null; + for (int i = 0; i < 3; i++) + { + PreparedStatement ps = conn.prepareStatement("INSERT INTO " + table.getEscapedTableName() + " VALUES(?)"); + blobs[i] = conn.createBlob(); + ThreadLocalRandom.current().nextBytes(data); + blobs[i].setBytes(1, data); + ps.setBlob(1, blobs[i]); + ps.executeUpdate(); + } + byte[] chunk = new byte[size]; + ResultSet rs = stmt.executeQuery("select * from " + table.getEscapedTableName()); + for (int i = 0; i < 3; i++) + { + rs.next(); + blobs[i] = rs.getBlob(1); + stream = blobs[i].getBinaryStream(); + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + int read = 0; + while ((read = stream.read(chunk)) > 0) + buffer.write(chunk, 0, read); + assertEquals(chunk.length, size); + } + rs.close(); + for (int i = 0; i < 3; i++) + { + stream = blobs[i].getBinaryStream(); + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + int read = 0; + while ((read = stream.read(chunk)) > 0) + buffer.write(chunk, 0, read); + assertEquals(chunk.length, size); + } + } private void testUpdateLobs(String types[], Class lobClass) throws Exception { From 19e833912ff85991285bbdc352d6c02efe5a0dbf Mon Sep 17 00:00:00 2001 From: rene-ye Date: Thu, 22 Feb 2018 13:23:44 -0800 Subject: [PATCH 20/41] merge conflict #1 --- .../microsoft/sqlserver/jdbc/SQLServerResultSet.java | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java index cb5442511..6e8e064a4 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java @@ -6534,18 +6534,6 @@ private void fillBlobs() { activeBlob = null; } } - - /*for (int i = 0; i < streamObjects.size(); i++) { - if(streamObjects.get(i) instanceof SQLServerBlob) { - try { - ((SQLServerBlob) streamObjects.get(i)).fillByteArray(); - streamObjects.remove(i); - } catch (SQLException e) { - if (logger.isLoggable(java.util.logging.Level.FINER)) - logger.finer(toString() + "Filling blobs before closing: " + e.getMessage()); - } - } - }*/ } /** From 09fe7e4ae5021e6d553116b5ecfdcc0615ec096b Mon Sep 17 00:00:00 2001 From: rene-ye Date: Thu, 22 Feb 2018 14:22:11 -0800 Subject: [PATCH 21/41] Revert "Merge branch 'allChangesForTesting' into blobStream" This reverts commit bc074f23860686ce79887b36cb5b64fe23372551, reversing changes made to 19e833912ff85991285bbdc352d6c02efe5a0dbf. --- .../jdbc/SQLServerCallableStatement.java | 2 -- .../sqlserver/jdbc/SQLServerConnection.java | 17 ++++++----------- .../sqlserver/jdbc/SQLServerResultSet.java | 2 +- .../testframework/util/ComparisonUtil.java | 10 +++++----- 4 files changed, 12 insertions(+), 19 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerCallableStatement.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerCallableStatement.java index c30eab2fa..5ad8689c8 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerCallableStatement.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerCallableStatement.java @@ -1450,8 +1450,6 @@ public NClob getNClob(String parameterName) throws SQLException { int l = 0; if (paramNames != null) l = paramNames.size(); - if (l == 0)//Server didn't return anything, user might not have access - return 1;//attempting to look up the first column will return no access exception // handle `@name` as well as `name`, since `@name` is what's returned // by DatabaseMetaData#getProcedureColumns diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java index 587ffb75e..432aa9e32 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java @@ -3420,24 +3420,19 @@ final boolean doExecute() throws SQLServerException { } } finally { - if (integratedSecurity) { + if (integratedSecurity) { + if (null != authentication) + authentication.ReleaseClientContext(); + authentication = null; + if (null != ImpersonatedUserCred) { try { - if (ImpersonatedUserCred.getRemainingLifetime() <= 0) { - if (null != authentication) - authentication.ReleaseClientContext(); - authentication = null; - ImpersonatedUserCred.dispose(); - } + ImpersonatedUserCred.dispose(); } catch (GSSException e) { if (connectionlogger.isLoggable(Level.FINER)) connectionlogger.finer(toString() + " Release of the credentials failed GSSException: " + e); } - } else { - if (null != authentication) - authentication.ReleaseClientContext(); - authentication = null; } } } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java index a89ac419b..6e8e064a4 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java @@ -6520,7 +6520,7 @@ final void doServerFetch(int fetchType, } /* - * Iterates through the list of objects which rely on the stream that's about to be closed, filling them with their data + * Iterates through the list of objects which rely on the stream that's about to be closed, ing them with their data * Will skip over closed blobs, implemented in SQLServerBlob */ private void fillBlobs() { diff --git a/src/test/java/com/microsoft/sqlserver/testframework/util/ComparisonUtil.java b/src/test/java/com/microsoft/sqlserver/testframework/util/ComparisonUtil.java index b297a3924..c942f8c9a 100644 --- a/src/test/java/com/microsoft/sqlserver/testframework/util/ComparisonUtil.java +++ b/src/test/java/com/microsoft/sqlserver/testframework/util/ComparisonUtil.java @@ -92,16 +92,16 @@ public static void compareExpectedAndActual(int dataType, else switch (dataType) { case java.sql.Types.BIGINT: - assertTrue((((Long) expectedValue).longValue() == ((Long) actualValue).longValue()), "Unexpected bigint value. Expected:" + ((Long) expectedValue).longValue() + " Actual:" + ((Long) actualValue).longValue()); + assertTrue((((Long) expectedValue).longValue() == ((Long) actualValue).longValue()), "Unexpected bigint value"); break; case java.sql.Types.INTEGER: - assertTrue((((Integer) expectedValue).intValue() == ((Integer) actualValue).intValue()), "Unexpected int value. Expected:" + ((Integer) expectedValue).intValue() + " Actual:" + ((Integer) actualValue).intValue()); + assertTrue((((Integer) expectedValue).intValue() == ((Integer) actualValue).intValue()), "Unexpected int value"); break; case java.sql.Types.SMALLINT: case java.sql.Types.TINYINT: - assertTrue((((Short) expectedValue).shortValue() == ((Short) actualValue).shortValue()), "Unexpected smallint/tinyint value. Expected:" + ((Short) expectedValue).shortValue() + " Actual:" + ((Short) actualValue).shortValue()); + assertTrue((((Short) expectedValue).shortValue() == ((Short) actualValue).shortValue()), "Unexpected smallint/tinyint value"); break; case java.sql.Types.BIT: @@ -115,11 +115,11 @@ public static void compareExpectedAndActual(int dataType, break; case java.sql.Types.DOUBLE: - assertTrue((((Double) expectedValue).doubleValue() == ((Double) actualValue).doubleValue()), "Unexpected double value. Expected:" + ((Double) expectedValue).doubleValue() + " Actual:" + ((Double) actualValue).doubleValue()); + assertTrue((((Double) expectedValue).doubleValue() == ((Double) actualValue).doubleValue()), "Unexpected float value"); break; case java.sql.Types.REAL: - assertTrue((((Float) expectedValue).floatValue() == ((Float) actualValue).floatValue()), "Unexpected real/float value. Expected:" + ((Float) expectedValue).floatValue() + " Actual:" + ((Float) actualValue).floatValue()); + assertTrue((((Float) expectedValue).floatValue() == ((Float) actualValue).floatValue()), "Unexpected real value"); break; case java.sql.Types.VARCHAR: From b071f239fb1e6f92bdb354021f4721eaab0dd13b Mon Sep 17 00:00:00 2001 From: rene-ye Date: Thu, 22 Feb 2018 14:23:31 -0800 Subject: [PATCH 22/41] comment edit --- .../java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java index 6e8e064a4..a89ac419b 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java @@ -6520,7 +6520,7 @@ final void doServerFetch(int fetchType, } /* - * Iterates through the list of objects which rely on the stream that's about to be closed, ing them with their data + * Iterates through the list of objects which rely on the stream that's about to be closed, filling them with their data * Will skip over closed blobs, implemented in SQLServerBlob */ private void fillBlobs() { From 4d91bc9f14be9a4302523cef5ebf3a3132991187 Mon Sep 17 00:00:00 2001 From: rene-ye Date: Fri, 23 Feb 2018 10:23:23 -0800 Subject: [PATCH 23/41] slight adjustments to implementation No longer fills blobs on every checkClosed() call. Only attempts to fill them in scenarios where the cursor moves or another change of state which causes the RS to lose its active stream. --- .../sqlserver/jdbc/SQLServerResultSet.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java index a89ac419b..e5ae74ea1 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java @@ -436,8 +436,6 @@ public T unwrap(Class iface) throws SQLException { // can be done with it other than closing it. if (null != rowErrorException) throw rowErrorException; - - fillBlobs(); } public boolean isClosed() throws SQLException { @@ -582,9 +580,6 @@ private void closeInternal() { // Mark this ResultSet as closed, then clean up. isClosed = true; - //fill all unclosed objects which rely on the stream - fillBlobs(); - // Discard the current fetch buffer contents. discardFetchBuffer(); @@ -619,7 +614,7 @@ public void close() throws SQLServerException { public int findColumn(String columnName) throws SQLServerException { loggerExternal.entering(getClassNameLogging(), "findColumn", columnName); checkClosed(); - + // In order to be as accurate as possible when locating column name // indexes, as well as be deterministic when running on various client // locales, we search for column names using the following scheme: @@ -756,6 +751,7 @@ private Column loadColumn(int index) throws SQLServerException { /* ----------------- JDBC API methods ------------------ */ private void moverInit() throws SQLServerException { + fillBlobs(); cancelInsert(); cancelUpdates(); } @@ -1901,6 +1897,7 @@ Column getterGetColumn(int index) throws SQLServerException { if (logger.isLoggable(java.util.logging.Level.FINER)) logger.finer(toString() + " Getting Column:" + index); + fillBlobs(); return loadColumn(index); } @@ -6524,7 +6521,7 @@ final void doServerFetch(int fetchType, * Will skip over closed blobs, implemented in SQLServerBlob */ private void fillBlobs() { - if (activeBlob != null && activeBlob instanceof SQLServerBlob) { + if (null != activeBlob && activeBlob instanceof SQLServerBlob) { try { ((SQLServerBlob)activeBlob).fillByteArray(); } catch (SQLException e) { @@ -6547,6 +6544,9 @@ private void fillBlobs() { * fetch buffer is considered to be discarded. */ private void discardFetchBuffer() { + //fills blobs before discarding anything + fillBlobs(); + // Clear the TDSReader mark at the start of the fetch buffer fetchBuffer.clearStartMark(); From 48b9b85254aefe8d24b498910e11940bc2288873 Mon Sep 17 00:00:00 2001 From: rene-ye Date: Fri, 23 Feb 2018 13:18:57 -0800 Subject: [PATCH 24/41] SimpleInputStream fix wasNull() moves the cursor for some reason, need to fill blobs. --- .../java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java index e5ae74ea1..fab346e31 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java @@ -1040,6 +1040,7 @@ public boolean next() throws SQLServerException { public boolean wasNull() throws SQLServerException { loggerExternal.entering(getClassNameLogging(), "wasNull"); checkClosed(); + fillBlobs(); loggerExternal.exiting(getClassNameLogging(), "wasNull", lastValueWasNull); return lastValueWasNull; } @@ -1413,7 +1414,7 @@ public int getRow() throws SQLServerException { logger.finer(toString() + logCursorState()); checkClosed(); - + // DYNAMIC (scrollable) cursors do not support getRow() since they do not have any // concept of absolute position. if (isDynamic() && !isForwardOnly()) From c73f5ba3ec19bee80a02ebf0e9cddb58ae666cad Mon Sep 17 00:00:00 2001 From: Cheena Malhotra Date: Tue, 27 Feb 2018 16:23:58 -0800 Subject: [PATCH 25/41] Update SNAPSHOT for upcoming preview release. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 6c41936d5..fc6f260ef 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.microsoft.sqlserver mssql-jdbc - 6.4.0.${jreVersion} + 6.5.0-SNAPSHOT.${jreVersion}-preview jar Microsoft JDBC Driver for SQL Server From bc1e531a3bca321af3bd67828360d9ed3dbb2667 Mon Sep 17 00:00:00 2001 From: Peter Bae Date: Wed, 28 Feb 2018 13:55:04 -0800 Subject: [PATCH 26/41] test changes to make it compatible with sql server 2008 --- .../SQLServerSpatialDatatypeTest.java | 230 ++++++++++-------- 1 file changed, 130 insertions(+), 100 deletions(-) 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 cdbaca8c0..5b76d0961 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/datatypes/SQLServerSpatialDatatypeTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/datatypes/SQLServerSpatialDatatypeTest.java @@ -27,7 +27,6 @@ import com.microsoft.sqlserver.jdbc.Geography; import com.microsoft.sqlserver.jdbc.Geometry; import com.microsoft.sqlserver.jdbc.SQLServerConnection; -import com.microsoft.sqlserver.jdbc.SQLServerException; import com.microsoft.sqlserver.jdbc.SQLServerPreparedStatement; import com.microsoft.sqlserver.jdbc.SQLServerResultSet; import com.microsoft.sqlserver.testframework.AbstractTest; @@ -47,6 +46,7 @@ public class SQLServerSpatialDatatypeTest extends AbstractTest { static String spatialDatatypeTableName = "spatialDatatypeTestTable"; static SQLServerPreparedStatement pstmt = null; static SQLServerResultSet rs = null; + static boolean isDenaliOrLater = false; @Test public void testPointWkb() throws DecoderException { @@ -176,16 +176,18 @@ public void testCompoundCurveWkb() throws DecoderException { @Test public void testCurvePolygonWkb() throws DecoderException { - String geoWKT = "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)))"; - - byte[] geomWKB = Hex.decodeHex("0A00000002001700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F03F00000000000008400000000000000840000000000000144000000000000010400000000000001C400000000000001C400000000000000840000000000000F03F00000000000008400000000000000000C7D79E59127037C00000000000000000C7D79E591270374000000000008046C0C7D79E59127037C00000000000000000C7D79E59127037C00000000000000000C7D79E59127037C00000000000001C400000000000001C400000000000000000C7D79E5912703740000000000000204000000000000020400000000000002040000000000000204000000000008046C0C7D79E591270374000000000008056C0C7D79E591270374000000000008056C0C7D79E59127037C000000000008046C0C7D79E59127037C00000000000000000C7D79E59127037C004000000010000000002040000000309000000030D00000001000000FFFFFFFF000000000A080000000203020003010203".toCharArray()); - byte[] geogWKB = Hex.decodeHex("E6100000020017000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000840000000000000F03F000000000000144000000000000008400000000000001C40000000000000104000000000000008400000000000001C400000000000000840000000000000F03FC7D79E59127037C00000000000000000C7D79E59127037400000000000000000C7D79E59127037C000000000008046C0C7D79E59127037C00000000000000000C7D79E59127037C000000000000000000000000000001C400000000000001C40C7D79E591270374000000000000000000000000000002040000000000000204000000000000020400000000000002040C7D79E591270374000000000008046C0C7D79E591270374000000000008056C0C7D79E59127037C000000000008056C0C7D79E59127037C000000000008046C0C7D79E59127037C0000000000000000004000000010000000002040000000309000000030D00000001000000FFFFFFFF000000000A080000000203020003010203".toCharArray()); + if (isDenaliOrLater) { + String geoWKT = "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)))"; + + byte[] geomWKB = Hex.decodeHex("0A00000002001700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F03F00000000000008400000000000000840000000000000144000000000000010400000000000001C400000000000001C400000000000000840000000000000F03F00000000000008400000000000000000C7D79E59127037C00000000000000000C7D79E591270374000000000008046C0C7D79E59127037C00000000000000000C7D79E59127037C00000000000000000C7D79E59127037C00000000000001C400000000000001C400000000000000000C7D79E5912703740000000000000204000000000000020400000000000002040000000000000204000000000008046C0C7D79E591270374000000000008056C0C7D79E591270374000000000008056C0C7D79E59127037C000000000008046C0C7D79E59127037C00000000000000000C7D79E59127037C004000000010000000002040000000309000000030D00000001000000FFFFFFFF000000000A080000000203020003010203".toCharArray()); + byte[] geogWKB = Hex.decodeHex("E6100000020017000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000840000000000000F03F000000000000144000000000000008400000000000001C40000000000000104000000000000008400000000000001C400000000000000840000000000000F03FC7D79E59127037C00000000000000000C7D79E59127037400000000000000000C7D79E59127037C000000000008046C0C7D79E59127037C00000000000000000C7D79E59127037C000000000000000000000000000001C400000000000001C40C7D79E591270374000000000000000000000000000002040000000000000204000000000000020400000000000002040C7D79E591270374000000000008046C0C7D79E591270374000000000008056C0C7D79E59127037C000000000008056C0C7D79E59127037C000000000008046C0C7D79E59127037C0000000000000000004000000010000000002040000000309000000030D00000001000000FFFFFFFF000000000A080000000203020003010203".toCharArray()); - Geometry geomWKT = Geometry.deserialize(geomWKB); - Geography geogWKT = Geography.deserialize(geogWKB); - - assertEquals(geomWKT.asTextZM(), geoWKT); - assertEquals(geogWKT.asTextZM(), geoWKT); + Geometry geomWKT = Geometry.deserialize(geomWKB); + Geography geogWKT = Geography.deserialize(geogWKB); + + assertEquals(geomWKT.asTextZM(), geoWKT); + assertEquals(geogWKT.asTextZM(), geoWKT); + } } @Test @@ -291,11 +293,14 @@ public void testMultiPolygonWkt() throws SQLException { @Test public void testGeometryCollectionWkt() throws SQLException { - beforeEachSetup(); - - String geoWKT = "GEOMETRYCOLLECTION(POINT(3 3 1), LINESTRING(1 0, 0 1, -1 0), CIRCULARSTRING(1 3, 3 5, 4 7, 7 3, 1 3), POLYGON((0 0 2, 1 10 3, 1 0 4, 0 0 2), (0 0 2, 1 10 3, 1 0 4, 0 0 2), (0 0 2, 1 10 3, 1 0 4, 0 0 2)), MULTIPOINT((2 3), (7 8 9.5)), MULTILINESTRING((0 2, 1 1), (1 0, 1 1)), 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))), COMPOUNDCURVE(CIRCULARSTRING(1 0 3, 0 1 3, 9 6 3, 8 7 3, -1 0 3), CIRCULARSTRING(-1 0 3, 7 9 3, -10 2 3), (-10 2 3, 77 77 77, 88 88 88, 2 6 4), (2 6 4, 3 3 6, 7 7 1)), CURVEPOLYGON((0 0, 0 0, 0 0, 0 0), 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))), POLYGON((0 0 2, 1 10 3, 1 0 4, 0 0 2)))"; - - testWkt(geoWKT); + String geoWKT; + if (isDenaliOrLater) { + beforeEachSetup(); + + geoWKT = "GEOMETRYCOLLECTION(POINT(3 3 1), LINESTRING(1 0, 0 1, -1 0), CIRCULARSTRING(1 3, 3 5, 4 7, 7 3, 1 3), POLYGON((0 0 2, 1 10 3, 1 0 4, 0 0 2), (0 0 2, 1 10 3, 1 0 4, 0 0 2), (0 0 2, 1 10 3, 1 0 4, 0 0 2)), MULTIPOINT((2 3), (7 8 9.5)), MULTILINESTRING((0 2, 1 1), (1 0, 1 1)), 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))), COMPOUNDCURVE(CIRCULARSTRING(1 0 3, 0 1 3, 9 6 3, 8 7 3, -1 0 3), CIRCULARSTRING(-1 0 3, 7 9 3, -10 2 3), (-10 2 3, 77 77 77, 88 88 88, 2 6 4), (2 6 4, 3 3 6, 7 7 1)), CURVEPOLYGON((0 0, 0 0, 0 0, 0 0), 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))), POLYGON((0 0 2, 1 10 3, 1 0 4, 0 0 2)))"; + + testWkt(geoWKT); + } beforeEachSetup(); @@ -318,32 +323,36 @@ public void testGeometryCollectionWkt() throws SQLException { @Test public void testCircularStringWkt() throws SQLException { - beforeEachSetup(); - - String geoWKT = "CIRCULARSTRING(2 1 3 4, 1 2 3, 0 7 3, 1 0 3, 2 1 3)"; - - testWkt(geoWKT); - - beforeEachSetup(); - - geoWKT = "CIRCULARSTRING EMPTY"; - - testWkt(geoWKT); + if (isDenaliOrLater) { + beforeEachSetup(); + + String geoWKT = "CIRCULARSTRING(2 1 3 4, 1 2 3, 0 7 3, 1 0 3, 2 1 3)"; + + testWkt(geoWKT); + + beforeEachSetup(); + + geoWKT = "CIRCULARSTRING EMPTY"; + + testWkt(geoWKT); + } } @Test public void testCompoundCurveWkt() throws SQLException { - beforeEachSetup(); - - String geoWKT = "COMPOUNDCURVE(CIRCULARSTRING(1 0 3, 0 1 3, 9 6 3, 8 7 3, -1 0 3), CIRCULARSTRING(-1 0 3, 7 9 3, -10 2 3), (-10 2 3, 77 77 77, 88 88 88, 2 6 4), (2 6 4, 3 3 6, 7 7 1))"; - - testWkt(geoWKT); - - beforeEachSetup(); - - geoWKT = "COMPOUNDCURVE EMPTY"; - - testWkt(geoWKT); + if (isDenaliOrLater) { + beforeEachSetup(); + + String geoWKT = "COMPOUNDCURVE(CIRCULARSTRING(1 0 3, 0 1 3, 9 6 3, 8 7 3, -1 0 3), CIRCULARSTRING(-1 0 3, 7 9 3, -10 2 3), (-10 2 3, 77 77 77, 88 88 88, 2 6 4), (2 6 4, 3 3 6, 7 7 1))"; + + testWkt(geoWKT); + + beforeEachSetup(); + + geoWKT = "COMPOUNDCURVE EMPTY"; + + testWkt(geoWKT); + } } @Test @@ -363,28 +372,30 @@ public void testCurvePolygonWkt() throws SQLException { @Test public void testFullGlobeWkt() throws SQLException { - beforeEachSetup(); - - String geoWKT = "FULLGLOBE"; + if (isDenaliOrLater) { + beforeEachSetup(); + + String geoWKT = "FULLGLOBE"; - Geography geogWKT = Geography.STGeomFromText(geoWKT, 4326); + Geography geogWKT = Geography.STGeomFromText(geoWKT, 4326); - try { - Geometry.STGeomFromText(geoWKT, 0); - } - catch (IllegalArgumentException e) { - assertEquals(e.getMessage(), "Fullglobe is not supported for Geometry."); + try { + Geometry.STGeomFromText(geoWKT, 0); + } + catch (IllegalArgumentException e) { + assertEquals(e.getMessage(), "Fullglobe is not supported for Geometry."); + } + + 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); } - - 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); } @Test @@ -536,20 +547,22 @@ public void testAllTypes() throws SQLException { 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(); + if (isDenaliOrLater) { + 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); @@ -699,36 +712,38 @@ public void testMixedAllTypes() throws SQLException { 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(); - + if (isDenaliOrLater) { + 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); @@ -977,6 +992,21 @@ private void testWkt(String geoWKT, String geoWKTSS) throws SQLException { public static void setupHere() throws SQLException, SecurityException, IOException { con = (SQLServerConnection) DriverManager.getConnection(connectionString); stmt = con.createStatement(); + + rs = (SQLServerResultSet) stmt.executeQuery("select SERVERPROPERTY ( 'ProductVersion' )"); + + rs.next(); + + try { + int version = Integer.parseInt(rs.getString(1).substring(0, 2)); + + // if major version is greater than or equal to 11, it's SQL Server 2012 or above. + if (version >= 11) { + isDenaliOrLater = true; + } + } catch (Exception e) { + // Do nothing. + } } /** From ec553a005ef36c0d49b9d0ceb8ebab8ac4edf81f Mon Sep 17 00:00:00 2001 From: Peter Bae Date: Wed, 28 Feb 2018 14:20:59 -0800 Subject: [PATCH 27/41] more test changes --- .../SQLServerSpatialDatatypeTest.java | 518 +++++++++--------- 1 file changed, 259 insertions(+), 259 deletions(-) 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 5b76d0961..81a7dcd1d 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/datatypes/SQLServerSpatialDatatypeTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/datatypes/SQLServerSpatialDatatypeTest.java @@ -176,7 +176,6 @@ public void testCompoundCurveWkb() throws DecoderException { @Test public void testCurvePolygonWkb() throws DecoderException { - if (isDenaliOrLater) { String geoWKT = "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)))"; byte[] geomWKB = Hex.decodeHex("0A00000002001700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F03F00000000000008400000000000000840000000000000144000000000000010400000000000001C400000000000001C400000000000000840000000000000F03F00000000000008400000000000000000C7D79E59127037C00000000000000000C7D79E591270374000000000008046C0C7D79E59127037C00000000000000000C7D79E59127037C00000000000000000C7D79E59127037C00000000000001C400000000000001C400000000000000000C7D79E5912703740000000000000204000000000000020400000000000002040000000000000204000000000008046C0C7D79E591270374000000000008056C0C7D79E591270374000000000008056C0C7D79E59127037C000000000008046C0C7D79E59127037C00000000000000000C7D79E59127037C004000000010000000002040000000309000000030D00000001000000FFFFFFFF000000000A080000000203020003010203".toCharArray()); @@ -187,7 +186,6 @@ public void testCurvePolygonWkb() throws DecoderException { assertEquals(geomWKT.asTextZM(), geoWKT); assertEquals(geogWKT.asTextZM(), geoWKT); - } } @Test @@ -357,17 +355,19 @@ public void testCompoundCurveWkt() throws SQLException { @Test public void testCurvePolygonWkt() throws SQLException { - beforeEachSetup(); - - String geoWKT = "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)))"; - - testWkt(geoWKT); - - beforeEachSetup(); - - geoWKT = "CURVEPOLYGON EMPTY"; - - testWkt(geoWKT); + if (isDenaliOrLater) { + beforeEachSetup(); + + String geoWKT = "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)))"; + + testWkt(geoWKT); + + beforeEachSetup(); + + geoWKT = "CURVEPOLYGON EMPTY"; + + testWkt(geoWKT); + } } @Test @@ -505,49 +505,49 @@ public void testIllegalCases() throws SQLException { @Test 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 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 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))))"; - - List geoWKTList = new ArrayList(); - - geoWKTList.add(geoWKTPoint); - geoWKTList.add(geoWKTLineString); - geoWKTList.add(geoWKTCircularString); - geoWKTList.add(geoWKTCompoundCurve); - geoWKTList.add(geoWKTCurvePolygon); - geoWKTList.add(geoWKTPolygon); - geoWKTList.add(geoWKTMultiPoint); - geoWKTList.add(geoWKTMultiLineString); - geoWKTList.add(geoWKTMultiPolygon); - geoWKTList.add(geoWKTGeometryCollection); - - Geometry geomWKT; - 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(); - if (isDenaliOrLater) { + beforeEachSetup(); + + String geoWKTPoint = "POINT(30 12.12312312 5 6)"; + 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))"; + 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 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))))"; + + List geoWKTList = new ArrayList(); + + geoWKTList.add(geoWKTPoint); + geoWKTList.add(geoWKTLineString); + geoWKTList.add(geoWKTCircularString); + geoWKTList.add(geoWKTCompoundCurve); + geoWKTList.add(geoWKTCurvePolygon); + geoWKTList.add(geoWKTPolygon); + geoWKTList.add(geoWKTMultiPoint); + geoWKTList.add(geoWKTMultiLineString); + geoWKTList.add(geoWKTMultiPolygon); + geoWKTList.add(geoWKTGeometryCollection); + + Geometry geomWKT; + 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); @@ -562,157 +562,157 @@ public void testAllTypes() throws SQLException { 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 (?)"); - - geogWKT = Geography.STGeomFromText(geoWKTPoint, 4326); - pstmt.setGeography(1, geogWKT); - - pstmt.executeUpdate(); - - geogWKT = Geography.STGeomFromText(geoWKTLineString, 4326); - pstmt.setGeography(1, geogWKT); - - pstmt.executeUpdate(); - - geogWKT = Geography.STGeomFromText(geoWKTCircularString, 4326); - pstmt.setGeography(1, geogWKT); - - pstmt.executeUpdate(); - - geogWKT = Geography.STGeomFromText(geoWKTCompoundCurve, 4326); - pstmt.setGeography(1, geogWKT); - - pstmt.executeUpdate(); - - geogWKT = Geography.STGeomFromText(geoWKTCurvePolygon, 4326); - pstmt.setGeography(1, geogWKT); - - pstmt.executeUpdate(); - - geogWKT = Geography.STGeomFromText(geoWKTPolygon, 4326); - pstmt.setGeography(1, geogWKT); - - pstmt.executeUpdate(); - - geogWKT = Geography.STGeomFromText(geoWKTMultiPoint, 4326); - pstmt.setGeography(1, geogWKT); - - pstmt.executeUpdate(); - - geogWKT = Geography.STGeomFromText(geoWKTMultiLineString, 4326); - pstmt.setGeography(1, geogWKT); - - pstmt.executeUpdate(); - - geogWKT = Geography.STGeomFromText(geoWKTMultiPolygon, 4326); - pstmt.setGeography(1, geogWKT); - - pstmt.executeUpdate(); - - geogWKT = Geography.STGeomFromText(geoWKTGeometryCollection, 4326); - pstmt.setGeography(1, geogWKT); - - 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)); + + 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 (?)"); + + geogWKT = Geography.STGeomFromText(geoWKTPoint, 4326); + pstmt.setGeography(1, geogWKT); + + pstmt.executeUpdate(); + + geogWKT = Geography.STGeomFromText(geoWKTLineString, 4326); + pstmt.setGeography(1, geogWKT); + + pstmt.executeUpdate(); + + geogWKT = Geography.STGeomFromText(geoWKTCircularString, 4326); + pstmt.setGeography(1, geogWKT); + + pstmt.executeUpdate(); + + geogWKT = Geography.STGeomFromText(geoWKTCompoundCurve, 4326); + pstmt.setGeography(1, geogWKT); + + pstmt.executeUpdate(); + + geogWKT = Geography.STGeomFromText(geoWKTCurvePolygon, 4326); + pstmt.setGeography(1, geogWKT); + + pstmt.executeUpdate(); + + geogWKT = Geography.STGeomFromText(geoWKTPolygon, 4326); + pstmt.setGeography(1, geogWKT); + + pstmt.executeUpdate(); + + geogWKT = Geography.STGeomFromText(geoWKTMultiPoint, 4326); + pstmt.setGeography(1, geogWKT); + + pstmt.executeUpdate(); + + geogWKT = Geography.STGeomFromText(geoWKTMultiLineString, 4326); + pstmt.setGeography(1, geogWKT); + + pstmt.executeUpdate(); + + geogWKT = Geography.STGeomFromText(geoWKTMultiPolygon, 4326); + pstmt.setGeography(1, geogWKT); + + pstmt.executeUpdate(); + + geogWKT = Geography.STGeomFromText(geoWKTGeometryCollection, 4326); + pstmt.setGeography(1, geogWKT); + + 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)); + } } } @Test public void testMixedAllTypes() throws SQLException { - beforeEachSetupSpatialDatatype(); - - String geoWKTPoint = "POINT(30 12.12312312 5 6)"; - 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))"; - 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 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))))"; - - String s = "some string"; - Double d = 31.34; - int i2 = 5; - - List geoWKTList = new ArrayList(); - - geoWKTList.add(geoWKTPoint); - geoWKTList.add(geoWKTLineString); - geoWKTList.add(geoWKTCircularString); - geoWKTList.add(geoWKTCompoundCurve); - geoWKTList.add(geoWKTCurvePolygon); - geoWKTList.add(geoWKTPolygon); - geoWKTList.add(geoWKTMultiPoint); - geoWKTList.add(geoWKTMultiLineString); - geoWKTList.add(geoWKTMultiPolygon); - geoWKTList.add(geoWKTGeometryCollection); - - 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(); - if (isDenaliOrLater) { + beforeEachSetupSpatialDatatype(); + + String geoWKTPoint = "POINT(30 12.12312312 5 6)"; + 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))"; + 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 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))))"; + + String s = "some string"; + Double d = 31.34; + int i2 = 5; + + List geoWKTList = new ArrayList(); + + geoWKTList.add(geoWKTPoint); + geoWKTList.add(geoWKTLineString); + geoWKTList.add(geoWKTCircularString); + geoWKTList.add(geoWKTCompoundCurve); + geoWKTList.add(geoWKTCurvePolygon); + geoWKTList.add(geoWKTPolygon); + geoWKTList.add(geoWKTMultiPoint); + geoWKTList.add(geoWKTMultiLineString); + geoWKTList.add(geoWKTMultiPolygon); + geoWKTList.add(geoWKTGeometryCollection); + + 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); @@ -742,66 +742,66 @@ public void testMixedAllTypes() throws SQLException { 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); + 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); + } } } From 7a73172efa4870dc92d803b9f4291f9f2c5bb52c Mon Sep 17 00:00:00 2001 From: Cheena Malhotra Date: Wed, 28 Feb 2018 15:17:50 -0800 Subject: [PATCH 28/41] Update Maven dependency versions for Junit Tests --- pom.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index fc6f260ef..d76127649 100644 --- a/pom.xml +++ b/pom.xml @@ -40,8 +40,8 @@ UTF-8 - 1.0.0-M3 - 5.0.0-M3 + 1.1.0 + 5.1.0 @@ -118,13 +118,13 @@ com.zaxxer HikariCP - 2.7.4 + 2.7.8 test org.apache.commons commons-dbcp2 - 2.1.1 + 2.2.0 test