From c77478c838a1cd795870ee8624e2be8fd9166ea4 Mon Sep 17 00:00:00 2001 From: bettdouglas Date: Sat, 30 May 2020 16:17:26 +0300 Subject: [PATCH 01/21] add dart_jts dependecy to parse EWKB format --- pubspec.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/pubspec.yaml b/pubspec.yaml index a09c233..d190b88 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -9,6 +9,7 @@ environment: dependencies: buffer: ^1.0.6 crypto: ^2.0.0 + dart_jts: ^0.0.5+3 dev_dependencies: pedantic: ^1.0.0 From b5b12d8b87fc9a426df835dfac5850dd2b93e2df Mon Sep 17 00:00:00 2001 From: bettdouglas Date: Sun, 7 Jun 2020 17:55:36 +0300 Subject: [PATCH 02/21] Updated all switch statements on the type --- lib/src/binary_codec.dart | 12 ++++++++++++ lib/src/query.dart | 4 +++- lib/src/substituter.dart | 2 ++ lib/src/types.dart | 6 +++++- 4 files changed, 22 insertions(+), 2 deletions(-) diff --git a/lib/src/binary_codec.dart b/lib/src/binary_codec.dart index 89c75ce..94c142c 100644 --- a/lib/src/binary_codec.dart +++ b/lib/src/binary_codec.dart @@ -38,6 +38,8 @@ class PostgresBinaryEncoder extends Converter { if (value == null) { return null; } + + switch (_dataType) { case PostgreSQLDataType.boolean: @@ -199,6 +201,12 @@ class PostgresBinaryEncoder extends Converter { } return outBuffer; } + case PostgreSQLDataType.geometry: + { + if(value is Geometry) { + return castBytes(utf8.encode(value.toText())); //TODO: Convert to Uint8List. Need help here. Using toText() in raw sql inserts it well. + } + } } throw PostgreSQLException('Unsupported datatype'); @@ -277,6 +285,8 @@ class PostgresBinaryDecoder extends Converter { return buf.toString(); } + case PostgreSQLDataType.geometry: + return PostgisEWKTParser().parseGeometry(value); } // We'll try and decode this as a utf8 string and return that @@ -305,5 +315,7 @@ class PostgresBinaryDecoder extends Converter { 1184: PostgreSQLDataType.timestampWithTimezone, 2950: PostgreSQLDataType.uuid, 3802: PostgreSQLDataType.json, + 31683: PostgreSQLDataType.geometry, //TODO: Changes on different databases. Is there a workaround for this? + 32339: PostgreSQLDataType.geometry // Obtained the oid's from SELECT oid, typarray FROM pg_type WHERE typname = 'geometry'; }; } diff --git a/lib/src/query.dart b/lib/src/query.dart index 43e5c58..df8489a 100644 --- a/lib/src/query.dart +++ b/lib/src/query.dart @@ -315,7 +315,9 @@ class PostgreSQLFormatIdentifier { 'jsonb': PostgreSQLDataType.json, 'bytea': PostgreSQLDataType.byteArray, 'name': PostgreSQLDataType.name, - 'uuid': PostgreSQLDataType.uuid + 'uuid': PostgreSQLDataType.uuid, + 'geometry': PostgreSQLDataType.geometry, + 'geography': PostgreSQLDataType.geometry }; factory PostgreSQLFormatIdentifier(String t) { diff --git a/lib/src/substituter.dart b/lib/src/substituter.dart index e1b1a7c..8e087ee 100644 --- a/lib/src/substituter.dart +++ b/lib/src/substituter.dart @@ -47,6 +47,8 @@ class PostgreSQLFormat { return 'name'; case PostgreSQLDataType.uuid: return 'uuid'; + case PostgreSQLDataType.geometry: + return 'geometry'; } return null; diff --git a/lib/src/types.dart b/lib/src/types.dart index ee76281..8469972 100644 --- a/lib/src/types.dart +++ b/lib/src/types.dart @@ -64,5 +64,9 @@ enum PostgreSQLDataType { /// /// Must contain 32 hexadecimal characters. May contain any number of '-' characters. /// When returned from database, format will be xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx. - uuid + uuid, + + /// Must be a [List] of [int] currently in Ewkb(Extended Well-Known Binary) format + /// + geometry } From 191a713612512fcf7a84a7eb75137e278bf91b12 Mon Sep 17 00:00:00 2001 From: bettdouglas Date: Sun, 7 Jun 2020 17:56:30 +0300 Subject: [PATCH 03/21] added the various geometric types in Dart --- lib/postgres.dart | 2 + lib/src/geometry_parser.dart | 124 +++++++++++++++++++++++++++++++++++ 2 files changed, 126 insertions(+) create mode 100644 lib/src/geometry_parser.dart diff --git a/lib/postgres.dart b/lib/postgres.dart index 01c9235..f846537 100644 --- a/lib/postgres.dart +++ b/lib/postgres.dart @@ -1,5 +1,7 @@ library postgres; +export 'package:dart_jts/dart_jts.dart'; +export 'package:postgres/src/geometry_parser.dart'; export 'src/connection.dart'; export 'src/execution_context.dart'; export 'src/substituter.dart'; diff --git a/lib/src/geometry_parser.dart b/lib/src/geometry_parser.dart new file mode 100644 index 0000000..ba09992 --- /dev/null +++ b/lib/src/geometry_parser.dart @@ -0,0 +1,124 @@ +import 'package:dart_jts/dart_jts.dart'; + +abstract class PostgisGeometryParser { + Geometry parseGeometry(List ewkt); + Point parsePoint(List ewkt); + LineString parseLineString(List ewkt); + Polygon parsePolygon(List ewkt); + + MultiPoint parseMultiPoint(List ewkt); + MultiLineString parseMultiLineString(List ewkt); + MultiPolygon parseMultiPolygon(List ewkt); + GeometryCollection parseGeometryCollection(List ewkt); + MultiLineString readMultiCurve(List ewkt); +} + + + +class EwkbFormatException implements Exception { + final String message; + + EwkbFormatException(this.message) : super(); + + @override + String toString() { + return 'FormatConversionException($message)'; + } + +} + + +class PostgisEWKTParser extends PostgisGeometryParser { + + var wkbReader = WKBReader(); + + @override + LineString parseLineString(List ewkt) { + final lineString = wkbReader.read(ewkt); + if(lineString is LineString) { + return lineString; + } else { + throw EwkbFormatException('$ewkt is not valid LineString. Ensure your format is EWKB & is a LineString'); + } + } + + @override + MultiPolygon parseMultiPolygon(List ewkt) { + final multiPolygon = wkbReader.read(ewkt); + if(multiPolygon is MultiPolygon) { + return multiPolygon; + } else { + throw EwkbFormatException('$ewkt is not valid MultiPolygon. Ensure your format is EWKB & is a MultiPolygon'); + } + } + + @override + Point parsePoint(List ewkt) { + final point = wkbReader.read(ewkt); + if(point is Point) { + return point; + } else { + throw EwkbFormatException('$ewkt is not valid Point. Ensure your format is EWKB & is a Point'); + } + } + + @override + Polygon parsePolygon(List ewkt) { + final point = wkbReader.read(ewkt); + if(point is Polygon) { + return point; + } else { + throw EwkbFormatException('$ewkt is not valid Polygon. Ensure your format is EWKB & is a Polygon'); + } + } + + @override + GeometryCollection parseGeometryCollection(List ewkt) { + final geometryCollection = wkbReader.read(ewkt); + if(geometryCollection is GeometryCollection) { + return geometryCollection; + } else { + throw EwkbFormatException('$ewkt is not valid GeometryCollection. Ensure your format is EWKB & is a GeometryCollection'); + } + } + + @override + Geometry parseGeometry(List ewkt) { + final geometry = wkbReader.read(ewkt); + if(geometry is Geometry) { + return geometry; + } else { + throw EwkbFormatException('$ewkt is not valid Geometry. Ensure your format is EWKB & is a Geometry'); + } + } + + @override + MultiLineString parseMultiLineString(List ewkt) { + final multiLineString = wkbReader.read(ewkt); + if(multiLineString is MultiLineString) { + return multiLineString; + } else { + throw EwkbFormatException('$ewkt is not valid MultiLineString. Ensure your format is EWKB & is a MultiLineString'); + } + } + + @override + MultiPoint parseMultiPoint(List ewkt) { + final multiPoint = wkbReader.read(ewkt); + if(multiPoint is MultiPoint) { + return multiPoint; + } else { + throw EwkbFormatException('$ewkt is not valid MultiPoint. Ensure your format is EWKB & is a MultiPoint'); + } + } + + @override + MultiLineString readMultiCurve(List ewkt) { + final multiCurve = wkbReader.read(ewkt); + if(multiCurve is MultiLineString) { + return multiCurve; + } else { + throw EwkbFormatException('$ewkt is not valid MultiPoint. Ensure your format is EWKB & is a MultiPoint'); + } + } +} \ No newline at end of file From 6a016d570344ae327a3fbd4271344343fc9f8236 Mon Sep 17 00:00:00 2001 From: bettdouglas Date: Tue, 9 Jun 2020 01:46:05 +0300 Subject: [PATCH 04/21] Added geometry_tests --- test/geometry_test.dart | 373 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 373 insertions(+) create mode 100644 test/geometry_test.dart diff --git a/test/geometry_test.dart b/test/geometry_test.dart new file mode 100644 index 0000000..8b01ff5 --- /dev/null +++ b/test/geometry_test.dart @@ -0,0 +1,373 @@ +import 'package:postgres/postgres.dart'; +import 'package:test/test.dart'; + +const String WKT_POINT = 'POINT ( 10 10)'; + +const String WKT_LINESTRING = 'LINESTRING (10 10, 20 20, 30 40)'; + +const String WKT_LINEARRING = 'LINEARRING (10 10, 20 20, 30 40, 10 10)'; + +const String WKT_POLY = 'POLYGON ((50 50, 50 150, 150 150, 150 50, 50 50))'; + +const String WKT_MULTIPOINT = 'MULTIPOINT ((10 10), (20 20))'; + +const String WKT_MULTILINESTRING = + 'MULTILINESTRING ((10 10, 20 20), (15 15, 30 15))'; + +const String WKT_MULTIPOLYGON = + 'MULTIPOLYGON (((10 10, 10 20, 20 20, 20 15, 10 10)), ((60 60, 70 70, 80 60, 60 60)))'; + +const String WKT_GC = + 'GEOMETRYCOLLECTION (POLYGON ((100 200, 200 200, 200 100, 100 100, 100 200)), LINESTRING (150 250, 250 250))'; + +const String multiInsert = ''' + INSERT INTO test(geom) values (GeomFromEWKT('SRID=4326;POINT(0 0)')); + INSERT INTO test(geom) values (GeomFromEWKT('SRID=4326;POINT(-2 2)')); + INSERT INTO test(geom) values (GeomFromEWKT('SRID=4326;MULTIPOINT(2 1,1 2)')); + INSERT INTO test(geom) values (GeomFromEWKT('SRID=4326;LINESTRING(0 0,1 1,1 2)')); + INSERT INTO test(geom) values (GeomFromEWKT('SRID=4326;MULTILINESTRING((1 0,0 1,3 2),(3 2,5 4))')); + INSERT INTO test(geom) values (GeomFromEWKT('SRID=4326;POLYGON((0 0,4 0,4 4,0 4,0 0),(1 1, 2 1, 2 2, 1 2,1 1))')); + INSERT INTO test(geom) values (GeomFromEWKT('SRID=4326;MULTIPOLYGON(((1 1,3 1,3 3,1 3,1 1),(1 1,2 1,2 2,1 2,1 1)), ((-1 -1,-1 -2,-2 -2,-2 -1,-1 -1)))')); + INSERT INTO test(geom) values (GeomFromEWKT('SRID=4326;GEOMETRYCOLLECTION(POLYGON((1 1, 2 1, 2 2, 1 2,1 1)),POINT(2 3),LINESTRING(2 3,3 4))')); + '''; + +void main() { + PostgreSQLConnection connection; + + final geomFactory = GeometryFactory.withCoordinateSequenceFactory( + PackedCoordinateSequenceFactory.withType( + PackedCoordinateSequenceFactory.DOUBLE)); + final rdr = WKTReader.withFactory(geomFactory); + + setUp(() async { + connection = PostgreSQLConnection('localhost', 5432, 'alaska', + username: 'dart', password: 'dart'); + await connection.open(); + + await connection.execute(''' + DROP TABLE IF EXISTS test; + CREATE EXTENSION IF NOT EXISTS postgis; + CREATE TABLE IF NOT EXISTS test(gid serial PRIMARY KEY, geom geometry); + '''); + }); + + tearDown(() async { + await connection?.close(); + }); + + group('Storage', () { + test('Can store point and read point as dart_jts.Point', () async { + final result = await connection.query( + 'INSERT into test(geom) VALUES (@point) returning geom', + substitutionValues: {'point': WKT_POINT}, + ); + final geom = result[0][0] as Point; + // print(geom.toString()); + + expect(geom.equalsExactGeom(rdr.read(WKT_POINT)), true); + }); + + test('Can store linestring and read linestring as dart_jts.LineString', + () async { + final result = await connection.query( + 'INSERT into test(geom) VALUES (@linestring) returning geom', + substitutionValues: {'linestring': WKT_LINESTRING}, + ); + + final geom = result[0][0] as LineString; + // print(geom.toString()); + final lineString = rdr.read(WKT_LINESTRING); + + expect(geom.toString(), lineString.toString()); + expect(geom.SRID, lineString.SRID); + expect(geom.envelope, lineString.envelope); + expect(geom.equals(lineString), true); + + expect(geom.toText(), lineString.toText()); + }); + + // test('Can store linearring and read linearring as dart_jts.LinearRing', + // () async { + // final result = await connection.query( + // 'INSERT into test(geom) VALUES (@linearRing) returning ST_IsValid(geom)', + // substitutionValues: {'linearRing': rdr.read(WKT_LINEARRING).toText()}, + // ); + // // final geom = result[0][0] as LinearRing; + // // final linearRing = rdr.read(WKT_LINEARRING); + + // // expect(geom.equalsExactGeom(linearRing), true); + // // expect(geom.SRID, linearRing.SRID); + // // expect(geom.toText(), linearRing.toText()); + // expect(result[0][0], false); + // }); + + test('Can store polygon and read it as dart_jts.Polygon', () async { + final result = await connection.query( + 'INSERT into test(geom) VALUES (@polygon) returning geom', + substitutionValues: {'polygon': WKT_POLY}, + ); + + final geom = result[0][0] as Polygon; + final poly = rdr.read(WKT_POLY); + + expect(geom.equalsExactGeom(poly), true); + expect(geom.SRID, poly.SRID); + expect(geom.toText(), poly.toText()); + }); + + test('Can store MultiPoint and read it as dart_jts.MultiPolygon', () async { + final result = await connection.query( + 'INSERT into test(geom) VALUES (@multiPoint) returning geom', + substitutionValues: {'multiPoint': WKT_MULTIPOINT}, + ); + + final geom = result[0][0] as MultiPoint; + final multiPoint = rdr.read(WKT_MULTIPOINT); + + expect(geom.equalsExactGeom(multiPoint), true); + expect(geom.SRID, multiPoint.SRID); + expect(geom.toText(), multiPoint.toText()); + }); + + test( + 'Can store MultiLineString well and read it as dart_jts.MultiLineString', + () async { + final result = await connection.query( + 'INSERT into test(geom) VALUES (@multiLine),(@multiLine) returning geom,geom', + substitutionValues: {'multiLine': WKT_MULTILINESTRING}, + ); + + final geom = result[0][0] as MultiLineString; + final geom1 = result[0][1] as MultiLineString; + final multiLineString = rdr.read(WKT_MULTILINESTRING); + + expect(geom.equals(geom1), true); + + expect(geom.equalsExactGeom(multiLineString), true); + expect(geom.SRID, multiLineString.SRID); + expect(geom.toText(), multiLineString.toText()); + }); + + test('Can store multipolygon and read it as dart_jts.MultiPolgon', + () async { + final result = await connection.query( + 'INSERT into test(geom) VALUES (@multiPoly) returning geom', + substitutionValues: {'multiPoly': WKT_MULTIPOLYGON}, + ); + final geom = result[0][0] as MultiPolygon; + final actualGeom = rdr.read(WKT_MULTIPOLYGON); + + expect(actualGeom.equals(geom), true); + expect(geom.SRID, actualGeom.SRID); + expect(geom.toText(), actualGeom.toText()); + }); + + test('Can store GeometryCollection well', () async { + final result = await connection.query( + 'INSERT into test(geom) VALUES (@geomColl) returning geom', + substitutionValues: {'geomColl': WKT_GC}, + ); + + final geom = result[0][0] as GeometryCollection; + // final actualGeom = rdr.read(WKT_GC); //TODO: Issue with wkt reading GeometryCollections + + // expect(actualGeom.equals(geom), true); + // expect(geom.SRID, actualGeom.SRID); + expect(geom.toText(), WKT_GC); + }); + + test( + 'MultiInsert should return appropriate inserted geometries when read back', + () async { + final sql = ''' + INSERT INTO test(geom) values + (GeomFromEWKT('SRID=4326;POINT(0 0)')), + (GeomFromEWKT('SRID=4326;POINT(-2 2)')), + (GeomFromEWKT('SRID=4326;MULTIPOINT(2 1,1 2)')), + (GeomFromEWKT('SRID=4326;LINESTRING(0 0,1 1,1 2)')), + (GeomFromEWKT('SRID=4326;MULTILINESTRING((1 0,0 1,3 2),(3 2,5 4))')), + (GeomFromEWKT('SRID=4326;POLYGON((0 0,4 0,4 4,0 4,0 0),(1 1, 2 1, 2 2, 1 2,1 1))')), + (GeomFromEWKT('SRID=4326;MULTIPOLYGON(((1 1,3 1,3 3,1 3,1 1),(1 1,2 1,2 2,1 2,1 1)), ((-1 -1,-1 -2,-2 -2,-2 -1,-1 -1)))')), + (GeomFromEWKT('SRID=4326;GEOMETRYCOLLECTION(POLYGON((1 1, 2 1, 2 2, 1 2,1 1)),POINT(2 3),LINESTRING(2 3,3 4))')) + RETURNING geom,geom,geom,geom,geom,geom,geom,geom + '''; + final results = await connection.query(sql); + final point = results[0][0] as Point; + final point2 = results[1][0] as Point; + final multiPoint = results[2][0] as MultiPoint; + final lineString = results[3][0] as LineString; + final multiLineString = results[4][0] as MultiLineString; + final polygon = results[5][0] as Polygon; + final multiPolygon = results[6][0] as MultiPolygon; + final geomCollection = results[7][0] as GeometryCollection; + + expect(point.SRID, 4326); + expect(point.coordinates.getX(0), 0); + expect(point.coordinates.getY(0), 0); + + expect(point2.SRID, 4326); + expect(point2.coordinates.getX(0), -2); + expect(point2.coordinates.getY(0), 2); + + expect(multiPoint.getCoordinates().first, Coordinate(2, 1)); + expect(multiPoint.getCoordinates().elementAt(1), Coordinate(1, 2)); + + expect(lineString.getCoordinates().length, 3); + expect(lineString.getCoordinates().elementAt(0), Coordinate(0, 0)); + expect(lineString.getCoordinates().elementAt(1), Coordinate(1, 1)); + expect(lineString.getCoordinates().elementAt(2), Coordinate(1, 2)); + expect(lineString.SRID, 4326); + + expect(multiLineString.SRID, 4326); + expect(multiLineString.getNumGeometries(), 2); + expect( + multiLineString.getGeometryN(0).equals( + geomFactory.createLineString( + [ + Coordinate(1, 0), + Coordinate(0, 1), + Coordinate(3, 2), + ], + ), + ), + true, + ); + expect( + multiLineString.getGeometryN(1).equals( + geomFactory.createLineString( + [ + Coordinate(3, 2), + Coordinate(5, 4), + ], + ), + ), + true, + ); + + expect(polygon.SRID, 4326); + expect(polygon.getNumInteriorRing(), 1); + expect( + polygon.getInteriorRingN(0).equals( + geomFactory.createLinearRing( + [ + Coordinate(1, 1), + Coordinate(2, 1), + Coordinate(2, 2), + Coordinate(1, 2), + Coordinate(1, 1), + ], + ), + ), + true, + ); + + expect( + polygon.getExteriorRing().equals( + geomFactory.createLinearRing( + [ + Coordinate(0, 0), + Coordinate(4, 0), + Coordinate(4, 4), + Coordinate(0, 4), + Coordinate(0, 0) + ], + ), + ), + true); + + expect(multiPolygon.getNumGeometries(), 2); + expect(multiPolygon.SRID, 4326); + final polygon1 = multiPolygon.getGeometryN(0) as Polygon; + final polygon2 = multiPolygon.getGeometryN(1) as Polygon; + + expect(polygon1.getGeometryType(), 'Polygon'); + expect(polygon2.getGeometryType(), 'Polygon'); + + expect(polygon1.getNumInteriorRing(), 1); + expect( + polygon1.getInteriorRingN(0).equals( + geomFactory.createLinearRing( + [ + //1 1,2 1,2 2,1 2,1 1 + Coordinate(1, 1), + Coordinate(2, 1), + Coordinate(2, 2), + Coordinate(1, 2), + Coordinate(1, 1) + ], + ), + ), + true, + ); + + expect( + polygon1.getExteriorRing().equals( + geomFactory.createLinearRing( + [ + //1 1,3 1,3 3,1 3,1 1 + Coordinate(1, 1), + Coordinate(3, 1), + Coordinate(3, 3), + Coordinate(1, 3), + Coordinate(1, 1) + ], + ), + ), + true, + ); + + expect( + polygon2.getExteriorRing().equals( + geomFactory.createLinearRing( + [ + //-1 -1,-1 -2,-2 -2,-2 -1,-1 -1 + Coordinate(-1, -1), + Coordinate(-1, -2), + Coordinate(-2, -2), + Coordinate(-2, -1), + Coordinate(-1, -1) + ], + ), + ), + true, + ); + + expect(geomCollection.SRID, 4326); + expect(geomCollection.getNumGeometries(), 3); + + final polygonInGC = geomCollection.getGeometryN(0) as Polygon; + final pointInGC = geomCollection.getGeometryN(1) as Point; + final lineStringInGC = geomCollection.getGeometryN(2) as LineString; + + expect( + polygonInGC.getExteriorRing().equals( + geomFactory.createLinearRing( + [ + Coordinate(1, 1), + Coordinate(2, 1), + Coordinate(2, 2), + Coordinate(1, 2), + Coordinate(1, 1) + ], + ), + ), + true, + ); + + expect(pointInGC.getCoordinate().getX(), 2); + expect(pointInGC.getCoordinate().getY(), 3); + + expect( + lineStringInGC.equals( + geomFactory.createLineString( + [ + Coordinate(2, 3), + Coordinate(3, 4), + ], + ), + ), + true, + ); + }); + }); +} From a9eb4c54b0cd373e495801b68e5bd1d152b65623 Mon Sep 17 00:00:00 2001 From: bettdouglas Date: Tue, 9 Jun 2020 01:46:55 +0300 Subject: [PATCH 05/21] add LinearRing parse to interface --- lib/src/geometry_parser.dart | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/src/geometry_parser.dart b/lib/src/geometry_parser.dart index ba09992..03880b2 100644 --- a/lib/src/geometry_parser.dart +++ b/lib/src/geometry_parser.dart @@ -11,6 +11,8 @@ abstract class PostgisGeometryParser { MultiPolygon parseMultiPolygon(List ewkt); GeometryCollection parseGeometryCollection(List ewkt); MultiLineString readMultiCurve(List ewkt); + + LinearRing parseLinearRing(List ewkt); } @@ -121,4 +123,14 @@ class PostgisEWKTParser extends PostgisGeometryParser { throw EwkbFormatException('$ewkt is not valid MultiPoint. Ensure your format is EWKB & is a MultiPoint'); } } + + @override + LinearRing parseLinearRing(List ewkt) { + final linearRing = wkbReader.read(ewkt); + if (linearRing is LinearRing) { + return linearRing; + } else { + throw EwkbFormatException('$ewkt is not valid LinearRing'); + } + } } \ No newline at end of file From 5260cd31e7b1495f1b4a8c1d9363f1dd46128321 Mon Sep 17 00:00:00 2001 From: bettdouglas Date: Tue, 9 Jun 2020 02:00:02 +0300 Subject: [PATCH 06/21] changed to ungrouped test --- test/geometry_test.dart | 578 ++++++++++++++++++++-------------------- 1 file changed, 289 insertions(+), 289 deletions(-) diff --git a/test/geometry_test.dart b/test/geometry_test.dart index 8b01ff5..1b1f17e 100644 --- a/test/geometry_test.dart +++ b/test/geometry_test.dart @@ -31,6 +31,10 @@ const String multiInsert = ''' INSERT INTO test(geom) values (GeomFromEWKT('SRID=4326;GEOMETRYCOLLECTION(POLYGON((1 1, 2 1, 2 2, 1 2,1 1)),POINT(2 3),LINESTRING(2 3,3 4))')); '''; +/// Before running this tests, RUN `SELECT oid, typname FROM pg_type WHERE typname in ('geometry','geography');` AND change the id of `typeMap` in `PostgresBinaryDecoder` to the returned oid. +/// after running `CREATE EXTENSION posgis` in the database +/// WorkAround for this needed + void main() { PostgreSQLConnection connection; @@ -55,131 +59,128 @@ void main() { await connection?.close(); }); - group('Storage', () { - test('Can store point and read point as dart_jts.Point', () async { - final result = await connection.query( - 'INSERT into test(geom) VALUES (@point) returning geom', - substitutionValues: {'point': WKT_POINT}, - ); - final geom = result[0][0] as Point; - // print(geom.toString()); - - expect(geom.equalsExactGeom(rdr.read(WKT_POINT)), true); - }); - - test('Can store linestring and read linestring as dart_jts.LineString', - () async { - final result = await connection.query( - 'INSERT into test(geom) VALUES (@linestring) returning geom', - substitutionValues: {'linestring': WKT_LINESTRING}, - ); - - final geom = result[0][0] as LineString; - // print(geom.toString()); - final lineString = rdr.read(WKT_LINESTRING); - - expect(geom.toString(), lineString.toString()); - expect(geom.SRID, lineString.SRID); - expect(geom.envelope, lineString.envelope); - expect(geom.equals(lineString), true); - - expect(geom.toText(), lineString.toText()); - }); - - // test('Can store linearring and read linearring as dart_jts.LinearRing', - // () async { - // final result = await connection.query( - // 'INSERT into test(geom) VALUES (@linearRing) returning ST_IsValid(geom)', - // substitutionValues: {'linearRing': rdr.read(WKT_LINEARRING).toText()}, - // ); - // // final geom = result[0][0] as LinearRing; - // // final linearRing = rdr.read(WKT_LINEARRING); - - // // expect(geom.equalsExactGeom(linearRing), true); - // // expect(geom.SRID, linearRing.SRID); - // // expect(geom.toText(), linearRing.toText()); - // expect(result[0][0], false); - // }); - - test('Can store polygon and read it as dart_jts.Polygon', () async { - final result = await connection.query( - 'INSERT into test(geom) VALUES (@polygon) returning geom', - substitutionValues: {'polygon': WKT_POLY}, - ); - - final geom = result[0][0] as Polygon; - final poly = rdr.read(WKT_POLY); - - expect(geom.equalsExactGeom(poly), true); - expect(geom.SRID, poly.SRID); - expect(geom.toText(), poly.toText()); - }); - - test('Can store MultiPoint and read it as dart_jts.MultiPolygon', () async { - final result = await connection.query( - 'INSERT into test(geom) VALUES (@multiPoint) returning geom', - substitutionValues: {'multiPoint': WKT_MULTIPOINT}, - ); - - final geom = result[0][0] as MultiPoint; - final multiPoint = rdr.read(WKT_MULTIPOINT); - - expect(geom.equalsExactGeom(multiPoint), true); - expect(geom.SRID, multiPoint.SRID); - expect(geom.toText(), multiPoint.toText()); - }); - - test( - 'Can store MultiLineString well and read it as dart_jts.MultiLineString', - () async { - final result = await connection.query( - 'INSERT into test(geom) VALUES (@multiLine),(@multiLine) returning geom,geom', - substitutionValues: {'multiLine': WKT_MULTILINESTRING}, - ); - - final geom = result[0][0] as MultiLineString; - final geom1 = result[0][1] as MultiLineString; - final multiLineString = rdr.read(WKT_MULTILINESTRING); - - expect(geom.equals(geom1), true); - - expect(geom.equalsExactGeom(multiLineString), true); - expect(geom.SRID, multiLineString.SRID); - expect(geom.toText(), multiLineString.toText()); - }); - - test('Can store multipolygon and read it as dart_jts.MultiPolgon', - () async { - final result = await connection.query( - 'INSERT into test(geom) VALUES (@multiPoly) returning geom', - substitutionValues: {'multiPoly': WKT_MULTIPOLYGON}, - ); - final geom = result[0][0] as MultiPolygon; - final actualGeom = rdr.read(WKT_MULTIPOLYGON); - - expect(actualGeom.equals(geom), true); - expect(geom.SRID, actualGeom.SRID); - expect(geom.toText(), actualGeom.toText()); - }); - - test('Can store GeometryCollection well', () async { - final result = await connection.query( - 'INSERT into test(geom) VALUES (@geomColl) returning geom', - substitutionValues: {'geomColl': WKT_GC}, - ); - - final geom = result[0][0] as GeometryCollection; - // final actualGeom = rdr.read(WKT_GC); //TODO: Issue with wkt reading GeometryCollections - - // expect(actualGeom.equals(geom), true); - // expect(geom.SRID, actualGeom.SRID); - expect(geom.toText(), WKT_GC); - }); - - test( - 'MultiInsert should return appropriate inserted geometries when read back', - () async { - final sql = ''' + test('Can store point and read point as dart_jts.Point', () async { + final result = await connection.query( + 'INSERT into test(geom) VALUES (@point) returning geom', + substitutionValues: {'point': WKT_POINT}, + ); + final geom = result[0][0] as Point; + // print(geom.toString()); + + expect(geom.equalsExactGeom(rdr.read(WKT_POINT)), true); + }); + + test('Can store linestring and read linestring as dart_jts.LineString', + () async { + final result = await connection.query( + 'INSERT into test(geom) VALUES (@linestring) returning geom', + substitutionValues: {'linestring': WKT_LINESTRING}, + ); + + final geom = result[0][0] as LineString; + // print(geom.toString()); + final lineString = rdr.read(WKT_LINESTRING); + + expect(geom.toString(), lineString.toString()); + expect(geom.SRID, lineString.SRID); + expect(geom.envelope, lineString.envelope); + expect(geom.equals(lineString), true); + + expect(geom.toText(), lineString.toText()); + }); + + // test('Can store linearring and read linearring as dart_jts.LinearRing', + // () async { + // final result = await connection.query( + // 'INSERT into test(geom) VALUES (@linearRing) returning ST_IsValid(geom)', + // substitutionValues: {'linearRing': rdr.read(WKT_LINEARRING).toText()}, + // ); + // // final geom = result[0][0] as LinearRing; + // // final linearRing = rdr.read(WKT_LINEARRING); + + // // expect(geom.equalsExactGeom(linearRing), true); + // // expect(geom.SRID, linearRing.SRID); + // // expect(geom.toText(), linearRing.toText()); + // expect(result[0][0], false); + // }); + + test('Can store polygon and read it as dart_jts.Polygon', () async { + final result = await connection.query( + 'INSERT into test(geom) VALUES (@polygon) returning geom', + substitutionValues: {'polygon': WKT_POLY}, + ); + + final geom = result[0][0] as Polygon; + final poly = rdr.read(WKT_POLY); + + expect(geom.equalsExactGeom(poly), true); + expect(geom.SRID, poly.SRID); + expect(geom.toText(), poly.toText()); + }); + + test('Can store MultiPoint and read it as dart_jts.MultiPolygon', () async { + final result = await connection.query( + 'INSERT into test(geom) VALUES (@multiPoint) returning geom', + substitutionValues: {'multiPoint': WKT_MULTIPOINT}, + ); + + final geom = result[0][0] as MultiPoint; + final multiPoint = rdr.read(WKT_MULTIPOINT); + + expect(geom.equalsExactGeom(multiPoint), true); + expect(geom.SRID, multiPoint.SRID); + expect(geom.toText(), multiPoint.toText()); + }); + + test('Can store MultiLineString well and read it as dart_jts.MultiLineString', + () async { + final result = await connection.query( + 'INSERT into test(geom) VALUES (@multiLine),(@multiLine) returning geom,geom', + substitutionValues: {'multiLine': WKT_MULTILINESTRING}, + ); + + final geom = result[0][0] as MultiLineString; + final geom1 = result[0][1] as MultiLineString; + final multiLineString = rdr.read(WKT_MULTILINESTRING); + + expect(geom.equals(geom1), true); + + expect(geom.equalsExactGeom(multiLineString), true); + expect(geom.SRID, multiLineString.SRID); + expect(geom.toText(), multiLineString.toText()); + }); + + test('Can store multipolygon and read it as dart_jts.MultiPolgon', () async { + final result = await connection.query( + 'INSERT into test(geom) VALUES (@multiPoly) returning geom', + substitutionValues: {'multiPoly': WKT_MULTIPOLYGON}, + ); + final geom = result[0][0] as MultiPolygon; + final actualGeom = rdr.read(WKT_MULTIPOLYGON); + + expect(actualGeom.equals(geom), true); + expect(geom.SRID, actualGeom.SRID); + expect(geom.toText(), actualGeom.toText()); + }); + + test('Can store GeometryCollection well', () async { + final result = await connection.query( + 'INSERT into test(geom) VALUES (@geomColl) returning geom', + substitutionValues: {'geomColl': WKT_GC}, + ); + + final geom = result[0][0] as GeometryCollection; + // final actualGeom = rdr.read(WKT_GC); //TODO: Issue with wkt reading GeometryCollections + + // expect(actualGeom.equals(geom), true); + // expect(geom.SRID, actualGeom.SRID); + expect(geom.toText(), WKT_GC); + }); + + test( + 'MultiInsert should return appropriate inserted geometries when read back', + () async { + final sql = ''' INSERT INTO test(geom) values (GeomFromEWKT('SRID=4326;POINT(0 0)')), (GeomFromEWKT('SRID=4326;POINT(-2 2)')), @@ -191,183 +192,182 @@ void main() { (GeomFromEWKT('SRID=4326;GEOMETRYCOLLECTION(POLYGON((1 1, 2 1, 2 2, 1 2,1 1)),POINT(2 3),LINESTRING(2 3,3 4))')) RETURNING geom,geom,geom,geom,geom,geom,geom,geom '''; - final results = await connection.query(sql); - final point = results[0][0] as Point; - final point2 = results[1][0] as Point; - final multiPoint = results[2][0] as MultiPoint; - final lineString = results[3][0] as LineString; - final multiLineString = results[4][0] as MultiLineString; - final polygon = results[5][0] as Polygon; - final multiPolygon = results[6][0] as MultiPolygon; - final geomCollection = results[7][0] as GeometryCollection; - - expect(point.SRID, 4326); - expect(point.coordinates.getX(0), 0); - expect(point.coordinates.getY(0), 0); - - expect(point2.SRID, 4326); - expect(point2.coordinates.getX(0), -2); - expect(point2.coordinates.getY(0), 2); - - expect(multiPoint.getCoordinates().first, Coordinate(2, 1)); - expect(multiPoint.getCoordinates().elementAt(1), Coordinate(1, 2)); - - expect(lineString.getCoordinates().length, 3); - expect(lineString.getCoordinates().elementAt(0), Coordinate(0, 0)); - expect(lineString.getCoordinates().elementAt(1), Coordinate(1, 1)); - expect(lineString.getCoordinates().elementAt(2), Coordinate(1, 2)); - expect(lineString.SRID, 4326); - - expect(multiLineString.SRID, 4326); - expect(multiLineString.getNumGeometries(), 2); - expect( - multiLineString.getGeometryN(0).equals( - geomFactory.createLineString( - [ - Coordinate(1, 0), - Coordinate(0, 1), - Coordinate(3, 2), - ], - ), + final results = await connection.query(sql); + final point = results[0][0] as Point; + final point2 = results[1][0] as Point; + final multiPoint = results[2][0] as MultiPoint; + final lineString = results[3][0] as LineString; + final multiLineString = results[4][0] as MultiLineString; + final polygon = results[5][0] as Polygon; + final multiPolygon = results[6][0] as MultiPolygon; + final geomCollection = results[7][0] as GeometryCollection; + + expect(point.SRID, 4326); + expect(point.coordinates.getX(0), 0); + expect(point.coordinates.getY(0), 0); + + expect(point2.SRID, 4326); + expect(point2.coordinates.getX(0), -2); + expect(point2.coordinates.getY(0), 2); + + expect(multiPoint.getCoordinates().first, Coordinate(2, 1)); + expect(multiPoint.getCoordinates().elementAt(1), Coordinate(1, 2)); + + expect(lineString.getCoordinates().length, 3); + expect(lineString.getCoordinates().elementAt(0), Coordinate(0, 0)); + expect(lineString.getCoordinates().elementAt(1), Coordinate(1, 1)); + expect(lineString.getCoordinates().elementAt(2), Coordinate(1, 2)); + expect(lineString.SRID, 4326); + + expect(multiLineString.SRID, 4326); + expect(multiLineString.getNumGeometries(), 2); + expect( + multiLineString.getGeometryN(0).equals( + geomFactory.createLineString( + [ + Coordinate(1, 0), + Coordinate(0, 1), + Coordinate(3, 2), + ], ), - true, - ); - expect( - multiLineString.getGeometryN(1).equals( - geomFactory.createLineString( - [ - Coordinate(3, 2), - Coordinate(5, 4), - ], - ), + ), + true, + ); + expect( + multiLineString.getGeometryN(1).equals( + geomFactory.createLineString( + [ + Coordinate(3, 2), + Coordinate(5, 4), + ], ), - true, - ); - - expect(polygon.SRID, 4326); - expect(polygon.getNumInteriorRing(), 1); - expect( - polygon.getInteriorRingN(0).equals( - geomFactory.createLinearRing( - [ - Coordinate(1, 1), - Coordinate(2, 1), - Coordinate(2, 2), - Coordinate(1, 2), - Coordinate(1, 1), - ], - ), + ), + true, + ); + + expect(polygon.SRID, 4326); + expect(polygon.getNumInteriorRing(), 1); + expect( + polygon.getInteriorRingN(0).equals( + geomFactory.createLinearRing( + [ + Coordinate(1, 1), + Coordinate(2, 1), + Coordinate(2, 2), + Coordinate(1, 2), + Coordinate(1, 1), + ], ), - true, - ); - - expect( - polygon.getExteriorRing().equals( - geomFactory.createLinearRing( - [ - Coordinate(0, 0), - Coordinate(4, 0), - Coordinate(4, 4), - Coordinate(0, 4), - Coordinate(0, 0) - ], - ), - ), - true); - - expect(multiPolygon.getNumGeometries(), 2); - expect(multiPolygon.SRID, 4326); - final polygon1 = multiPolygon.getGeometryN(0) as Polygon; - final polygon2 = multiPolygon.getGeometryN(1) as Polygon; - - expect(polygon1.getGeometryType(), 'Polygon'); - expect(polygon2.getGeometryType(), 'Polygon'); + ), + true, + ); - expect(polygon1.getNumInteriorRing(), 1); - expect( - polygon1.getInteriorRingN(0).equals( + expect( + polygon.getExteriorRing().equals( geomFactory.createLinearRing( [ - //1 1,2 1,2 2,1 2,1 1 - Coordinate(1, 1), - Coordinate(2, 1), - Coordinate(2, 2), - Coordinate(1, 2), - Coordinate(1, 1) + Coordinate(0, 0), + Coordinate(4, 0), + Coordinate(4, 4), + Coordinate(0, 4), + Coordinate(0, 0) ], ), ), - true, - ); - - expect( - polygon1.getExteriorRing().equals( - geomFactory.createLinearRing( - [ - //1 1,3 1,3 3,1 3,1 1 - Coordinate(1, 1), - Coordinate(3, 1), - Coordinate(3, 3), - Coordinate(1, 3), - Coordinate(1, 1) - ], - ), + true); + + expect(multiPolygon.getNumGeometries(), 2); + expect(multiPolygon.SRID, 4326); + final polygon1 = multiPolygon.getGeometryN(0) as Polygon; + final polygon2 = multiPolygon.getGeometryN(1) as Polygon; + + expect(polygon1.getGeometryType(), 'Polygon'); + expect(polygon2.getGeometryType(), 'Polygon'); + + expect(polygon1.getNumInteriorRing(), 1); + expect( + polygon1.getInteriorRingN(0).equals( + geomFactory.createLinearRing( + [ + //1 1,2 1,2 2,1 2,1 1 + Coordinate(1, 1), + Coordinate(2, 1), + Coordinate(2, 2), + Coordinate(1, 2), + Coordinate(1, 1) + ], ), - true, - ); - - expect( - polygon2.getExteriorRing().equals( - geomFactory.createLinearRing( - [ - //-1 -1,-1 -2,-2 -2,-2 -1,-1 -1 - Coordinate(-1, -1), - Coordinate(-1, -2), - Coordinate(-2, -2), - Coordinate(-2, -1), - Coordinate(-1, -1) - ], - ), + ), + true, + ); + + expect( + polygon1.getExteriorRing().equals( + geomFactory.createLinearRing( + [ + //1 1,3 1,3 3,1 3,1 1 + Coordinate(1, 1), + Coordinate(3, 1), + Coordinate(3, 3), + Coordinate(1, 3), + Coordinate(1, 1) + ], ), - true, - ); - - expect(geomCollection.SRID, 4326); - expect(geomCollection.getNumGeometries(), 3); - - final polygonInGC = geomCollection.getGeometryN(0) as Polygon; - final pointInGC = geomCollection.getGeometryN(1) as Point; - final lineStringInGC = geomCollection.getGeometryN(2) as LineString; - - expect( - polygonInGC.getExteriorRing().equals( - geomFactory.createLinearRing( - [ - Coordinate(1, 1), - Coordinate(2, 1), - Coordinate(2, 2), - Coordinate(1, 2), - Coordinate(1, 1) - ], - ), + ), + true, + ); + + expect( + polygon2.getExteriorRing().equals( + geomFactory.createLinearRing( + [ + //-1 -1,-1 -2,-2 -2,-2 -1,-1 -1 + Coordinate(-1, -1), + Coordinate(-1, -2), + Coordinate(-2, -2), + Coordinate(-2, -1), + Coordinate(-1, -1) + ], + ), + ), + true, + ); + + expect(geomCollection.SRID, 4326); + expect(geomCollection.getNumGeometries(), 3); + + final polygonInGC = geomCollection.getGeometryN(0) as Polygon; + final pointInGC = geomCollection.getGeometryN(1) as Point; + final lineStringInGC = geomCollection.getGeometryN(2) as LineString; + + expect( + polygonInGC.getExteriorRing().equals( + geomFactory.createLinearRing( + [ + Coordinate(1, 1), + Coordinate(2, 1), + Coordinate(2, 2), + Coordinate(1, 2), + Coordinate(1, 1) + ], ), - true, - ); - - expect(pointInGC.getCoordinate().getX(), 2); - expect(pointInGC.getCoordinate().getY(), 3); - - expect( - lineStringInGC.equals( - geomFactory.createLineString( - [ - Coordinate(2, 3), - Coordinate(3, 4), - ], ), + true, + ); + + expect(pointInGC.getCoordinate().getX(), 2); + expect(pointInGC.getCoordinate().getY(), 3); + + expect( + lineStringInGC.equals( + geomFactory.createLineString( + [ + Coordinate(2, 3), + Coordinate(3, 4), + ], ), - true, - ); - }); + ), + true, + ); }); } From 844c3251bb990e5e2d997029fd230e14d1b1d8de Mon Sep 17 00:00:00 2001 From: bettdouglas Date: Tue, 9 Jun 2020 02:19:06 +0300 Subject: [PATCH 07/21] fix typo postgis and dartfmt --- test/geometry_test.dart | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/test/geometry_test.dart b/test/geometry_test.dart index 1b1f17e..bfe5c1a 100644 --- a/test/geometry_test.dart +++ b/test/geometry_test.dart @@ -32,19 +32,22 @@ const String multiInsert = ''' '''; /// Before running this tests, RUN `SELECT oid, typname FROM pg_type WHERE typname in ('geometry','geography');` AND change the id of `typeMap` in `PostgresBinaryDecoder` to the returned oid. -/// after running `CREATE EXTENSION posgis` in the database +/// after running `CREATE EXTENSION postgis` in the database /// WorkAround for this needed void main() { PostgreSQLConnection connection; final geomFactory = GeometryFactory.withCoordinateSequenceFactory( - PackedCoordinateSequenceFactory.withType( - PackedCoordinateSequenceFactory.DOUBLE)); + PackedCoordinateSequenceFactory.withType( + PackedCoordinateSequenceFactory.DOUBLE, + ), + ); + final rdr = WKTReader.withFactory(geomFactory); setUp(() async { - connection = PostgreSQLConnection('localhost', 5432, 'alaska', + connection = PostgreSQLConnection('localhost', 5432, 'test', username: 'dart', password: 'dart'); await connection.open(); From 9e2084b2304ec00c1e6ae399fdc88043b2770905 Mon Sep 17 00:00:00 2001 From: bettdouglas Date: Tue, 9 Jun 2020 02:19:13 +0300 Subject: [PATCH 08/21] run dartfmt --- lib/src/binary_codec.dart | 20 +++++---- lib/src/geometry_parser.dart | 84 +++++++++++++++++++----------------- 2 files changed, 55 insertions(+), 49 deletions(-) diff --git a/lib/src/binary_codec.dart b/lib/src/binary_codec.dart index 94c142c..37eb84d 100644 --- a/lib/src/binary_codec.dart +++ b/lib/src/binary_codec.dart @@ -38,8 +38,6 @@ class PostgresBinaryEncoder extends Converter { if (value == null) { return null; } - - switch (_dataType) { case PostgreSQLDataType.boolean: @@ -201,12 +199,14 @@ class PostgresBinaryEncoder extends Converter { } return outBuffer; } - case PostgreSQLDataType.geometry: - { - if(value is Geometry) { - return castBytes(utf8.encode(value.toText())); //TODO: Convert to Uint8List. Need help here. Using toText() in raw sql inserts it well. - } + case PostgreSQLDataType.geometry: + { + if (value is Geometry) { + return castBytes(utf8.encode( + value.toText(), + ),); //TODO: Convert to Uint8List. Need help here. Using toText() in raw sql inserts it well. } + } } throw PostgreSQLException('Unsupported datatype'); @@ -315,7 +315,9 @@ class PostgresBinaryDecoder extends Converter { 1184: PostgreSQLDataType.timestampWithTimezone, 2950: PostgreSQLDataType.uuid, 3802: PostgreSQLDataType.json, - 31683: PostgreSQLDataType.geometry, //TODO: Changes on different databases. Is there a workaround for this? - 32339: PostgreSQLDataType.geometry // Obtained the oid's from SELECT oid, typarray FROM pg_type WHERE typname = 'geometry'; + 31683: PostgreSQLDataType + .geometry, //TODO: Changes on different databases. Is there a workaround for this? + 32339: PostgreSQLDataType + .geometry // Obtained the oid's from SELECT oid, typarray FROM pg_type WHERE typname = 'geometry'; }; } diff --git a/lib/src/geometry_parser.dart b/lib/src/geometry_parser.dart index 03880b2..c07eb3f 100644 --- a/lib/src/geometry_parser.dart +++ b/lib/src/geometry_parser.dart @@ -15,8 +15,6 @@ abstract class PostgisGeometryParser { LinearRing parseLinearRing(List ewkt); } - - class EwkbFormatException implements Exception { final String message; @@ -24,103 +22,109 @@ class EwkbFormatException implements Exception { @override String toString() { - return 'FormatConversionException($message)'; + return 'FormatConversionException($message)'; } - } - class PostgisEWKTParser extends PostgisGeometryParser { - var wkbReader = WKBReader(); @override LineString parseLineString(List ewkt) { final lineString = wkbReader.read(ewkt); - if(lineString is LineString) { + if (lineString is LineString) { return lineString; } else { - throw EwkbFormatException('$ewkt is not valid LineString. Ensure your format is EWKB & is a LineString'); + throw EwkbFormatException( + '$ewkt is not valid LineString. Ensure your format is EWKB & is a LineString'); } } @override MultiPolygon parseMultiPolygon(List ewkt) { final multiPolygon = wkbReader.read(ewkt); - if(multiPolygon is MultiPolygon) { + if (multiPolygon is MultiPolygon) { return multiPolygon; } else { - throw EwkbFormatException('$ewkt is not valid MultiPolygon. Ensure your format is EWKB & is a MultiPolygon'); + throw EwkbFormatException( + '$ewkt is not valid MultiPolygon. Ensure your format is EWKB & is a MultiPolygon'); } } @override Point parsePoint(List ewkt) { final point = wkbReader.read(ewkt); - if(point is Point) { + if (point is Point) { return point; } else { - throw EwkbFormatException('$ewkt is not valid Point. Ensure your format is EWKB & is a Point'); + throw EwkbFormatException( + '$ewkt is not valid Point. Ensure your format is EWKB & is a Point'); } } @override Polygon parsePolygon(List ewkt) { final point = wkbReader.read(ewkt); - if(point is Polygon) { + if (point is Polygon) { return point; } else { - throw EwkbFormatException('$ewkt is not valid Polygon. Ensure your format is EWKB & is a Polygon'); + throw EwkbFormatException( + '$ewkt is not valid Polygon. Ensure your format is EWKB & is a Polygon'); } } @override GeometryCollection parseGeometryCollection(List ewkt) { final geometryCollection = wkbReader.read(ewkt); - if(geometryCollection is GeometryCollection) { + if (geometryCollection is GeometryCollection) { return geometryCollection; } else { - throw EwkbFormatException('$ewkt is not valid GeometryCollection. Ensure your format is EWKB & is a GeometryCollection'); + throw EwkbFormatException( + '$ewkt is not valid GeometryCollection. Ensure your format is EWKB & is a GeometryCollection'); } } - + @override Geometry parseGeometry(List ewkt) { final geometry = wkbReader.read(ewkt); - if(geometry is Geometry) { + if (geometry is Geometry) { return geometry; } else { - throw EwkbFormatException('$ewkt is not valid Geometry. Ensure your format is EWKB & is a Geometry'); + throw EwkbFormatException( + '$ewkt is not valid Geometry. Ensure your format is EWKB & is a Geometry'); } } - - @override - MultiLineString parseMultiLineString(List ewkt) { - final multiLineString = wkbReader.read(ewkt); - if(multiLineString is MultiLineString) { - return multiLineString; - } else { - throw EwkbFormatException('$ewkt is not valid MultiLineString. Ensure your format is EWKB & is a MultiLineString'); - } + + @override + MultiLineString parseMultiLineString(List ewkt) { + final multiLineString = wkbReader.read(ewkt); + if (multiLineString is MultiLineString) { + return multiLineString; + } else { + throw EwkbFormatException( + '$ewkt is not valid MultiLineString. Ensure your format is EWKB & is a MultiLineString'); } - - @override - MultiPoint parseMultiPoint(List ewkt) { - final multiPoint = wkbReader.read(ewkt); - if(multiPoint is MultiPoint) { - return multiPoint; - } else { - throw EwkbFormatException('$ewkt is not valid MultiPoint. Ensure your format is EWKB & is a MultiPoint'); - } + } + + @override + MultiPoint parseMultiPoint(List ewkt) { + final multiPoint = wkbReader.read(ewkt); + if (multiPoint is MultiPoint) { + return multiPoint; + } else { + throw EwkbFormatException( + '$ewkt is not valid MultiPoint. Ensure your format is EWKB & is a MultiPoint'); } + } @override MultiLineString readMultiCurve(List ewkt) { final multiCurve = wkbReader.read(ewkt); - if(multiCurve is MultiLineString) { + if (multiCurve is MultiLineString) { return multiCurve; } else { - throw EwkbFormatException('$ewkt is not valid MultiPoint. Ensure your format is EWKB & is a MultiPoint'); + throw EwkbFormatException( + '$ewkt is not valid MultiPoint. Ensure your format is EWKB & is a MultiPoint'); } } @@ -133,4 +137,4 @@ class PostgisEWKTParser extends PostgisGeometryParser { throw EwkbFormatException('$ewkt is not valid LinearRing'); } } -} \ No newline at end of file +} From 876f0399c74cbe48380ea814fb2f737f8d2cf48d Mon Sep 17 00:00:00 2001 From: bettdouglas Date: Tue, 9 Jun 2020 09:12:52 +0300 Subject: [PATCH 09/21] Reduce boilerplate --- lib/postgres.dart | 3 +- lib/src/geometry_parser.dart | 140 ----------------------------------- 2 files changed, 1 insertion(+), 142 deletions(-) delete mode 100644 lib/src/geometry_parser.dart diff --git a/lib/postgres.dart b/lib/postgres.dart index f846537..a3edbe1 100644 --- a/lib/postgres.dart +++ b/lib/postgres.dart @@ -1,7 +1,6 @@ library postgres; -export 'package:dart_jts/dart_jts.dart'; -export 'package:postgres/src/geometry_parser.dart'; +export 'package:dart_jts/dart_jts.dart' show WKBReader,Geometry; export 'src/connection.dart'; export 'src/execution_context.dart'; export 'src/substituter.dart'; diff --git a/lib/src/geometry_parser.dart b/lib/src/geometry_parser.dart deleted file mode 100644 index c07eb3f..0000000 --- a/lib/src/geometry_parser.dart +++ /dev/null @@ -1,140 +0,0 @@ -import 'package:dart_jts/dart_jts.dart'; - -abstract class PostgisGeometryParser { - Geometry parseGeometry(List ewkt); - Point parsePoint(List ewkt); - LineString parseLineString(List ewkt); - Polygon parsePolygon(List ewkt); - - MultiPoint parseMultiPoint(List ewkt); - MultiLineString parseMultiLineString(List ewkt); - MultiPolygon parseMultiPolygon(List ewkt); - GeometryCollection parseGeometryCollection(List ewkt); - MultiLineString readMultiCurve(List ewkt); - - LinearRing parseLinearRing(List ewkt); -} - -class EwkbFormatException implements Exception { - final String message; - - EwkbFormatException(this.message) : super(); - - @override - String toString() { - return 'FormatConversionException($message)'; - } -} - -class PostgisEWKTParser extends PostgisGeometryParser { - var wkbReader = WKBReader(); - - @override - LineString parseLineString(List ewkt) { - final lineString = wkbReader.read(ewkt); - if (lineString is LineString) { - return lineString; - } else { - throw EwkbFormatException( - '$ewkt is not valid LineString. Ensure your format is EWKB & is a LineString'); - } - } - - @override - MultiPolygon parseMultiPolygon(List ewkt) { - final multiPolygon = wkbReader.read(ewkt); - if (multiPolygon is MultiPolygon) { - return multiPolygon; - } else { - throw EwkbFormatException( - '$ewkt is not valid MultiPolygon. Ensure your format is EWKB & is a MultiPolygon'); - } - } - - @override - Point parsePoint(List ewkt) { - final point = wkbReader.read(ewkt); - if (point is Point) { - return point; - } else { - throw EwkbFormatException( - '$ewkt is not valid Point. Ensure your format is EWKB & is a Point'); - } - } - - @override - Polygon parsePolygon(List ewkt) { - final point = wkbReader.read(ewkt); - if (point is Polygon) { - return point; - } else { - throw EwkbFormatException( - '$ewkt is not valid Polygon. Ensure your format is EWKB & is a Polygon'); - } - } - - @override - GeometryCollection parseGeometryCollection(List ewkt) { - final geometryCollection = wkbReader.read(ewkt); - if (geometryCollection is GeometryCollection) { - return geometryCollection; - } else { - throw EwkbFormatException( - '$ewkt is not valid GeometryCollection. Ensure your format is EWKB & is a GeometryCollection'); - } - } - - @override - Geometry parseGeometry(List ewkt) { - final geometry = wkbReader.read(ewkt); - if (geometry is Geometry) { - return geometry; - } else { - throw EwkbFormatException( - '$ewkt is not valid Geometry. Ensure your format is EWKB & is a Geometry'); - } - } - - @override - MultiLineString parseMultiLineString(List ewkt) { - final multiLineString = wkbReader.read(ewkt); - if (multiLineString is MultiLineString) { - return multiLineString; - } else { - throw EwkbFormatException( - '$ewkt is not valid MultiLineString. Ensure your format is EWKB & is a MultiLineString'); - } - } - - @override - MultiPoint parseMultiPoint(List ewkt) { - final multiPoint = wkbReader.read(ewkt); - if (multiPoint is MultiPoint) { - return multiPoint; - } else { - throw EwkbFormatException( - '$ewkt is not valid MultiPoint. Ensure your format is EWKB & is a MultiPoint'); - } - } - - @override - MultiLineString readMultiCurve(List ewkt) { - final multiCurve = wkbReader.read(ewkt); - if (multiCurve is MultiLineString) { - return multiCurve; - } else { - throw EwkbFormatException( - '$ewkt is not valid MultiPoint. Ensure your format is EWKB & is a MultiPoint'); - } - } - - @override - LinearRing parseLinearRing(List ewkt) { - final linearRing = wkbReader.read(ewkt); - if (linearRing is LinearRing) { - return linearRing; - } else { - throw EwkbFormatException('$ewkt is not valid LinearRing'); - } - } -} From 688778438dc4c16800d81038983cda8c14eb4ac2 Mon Sep 17 00:00:00 2001 From: bettdouglas Date: Tue, 9 Jun 2020 09:13:40 +0300 Subject: [PATCH 10/21] added link to understand format of geometries --- lib/src/binary_codec.dart | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/lib/src/binary_codec.dart b/lib/src/binary_codec.dart index 37eb84d..b4bf542 100644 --- a/lib/src/binary_codec.dart +++ b/lib/src/binary_codec.dart @@ -202,9 +202,11 @@ class PostgresBinaryEncoder extends Converter { case PostgreSQLDataType.geometry: { if (value is Geometry) { - return castBytes(utf8.encode( - value.toText(), - ),); //TODO: Convert to Uint8List. Need help here. Using toText() in raw sql inserts it well. + return castBytes( + utf8.encode( + value.toText(), + ), + ); //TODO: Convert to Uint8List. Need help here. Using toText() in raw sql inserts it well. } } } @@ -286,7 +288,16 @@ class PostgresBinaryDecoder extends Converter { return buf.toString(); } case PostgreSQLDataType.geometry: - return PostgisEWKTParser().parseGeometry(value); + { + final wkbReader = + WKBReader(); // postgis geometries are stored as Well Known Binaries (https://postgis.net/docs/using_postgis_dbmanagement.html) + final geometry = wkbReader.read(value); + if (geometry is Geometry) { + return geometry; + } else { + throw PostgreSQLException('Error parsing geometry'); + } + } } // We'll try and decode this as a utf8 string and return that @@ -315,9 +326,7 @@ class PostgresBinaryDecoder extends Converter { 1184: PostgreSQLDataType.timestampWithTimezone, 2950: PostgreSQLDataType.uuid, 3802: PostgreSQLDataType.json, - 31683: PostgreSQLDataType - .geometry, //TODO: Changes on different databases. Is there a workaround for this? - 32339: PostgreSQLDataType - .geometry // Obtained the oid's from SELECT oid, typarray FROM pg_type WHERE typname = 'geometry'; + 46315: PostgreSQLDataType.geometry, /// TODO: Oid Changes on different databases after running `CREATE EXTENSION postgis` + 46971: PostgreSQLDataType.geometry /// `SELECT oid, typarray FROM pg_type WHERE typname in ('geometry','geography')`; }; } From da9d9ed59df88a93c998561fd95c2719b040cc9d Mon Sep 17 00:00:00 2001 From: bettdouglas Date: Tue, 9 Jun 2020 09:13:46 +0300 Subject: [PATCH 11/21] fix import --- test/geometry_test.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/geometry_test.dart b/test/geometry_test.dart index bfe5c1a..bcee7a1 100644 --- a/test/geometry_test.dart +++ b/test/geometry_test.dart @@ -1,5 +1,6 @@ import 'package:postgres/postgres.dart'; import 'package:test/test.dart'; +import 'package:dart_jts/dart_jts.dart'; const String WKT_POINT = 'POINT ( 10 10)'; @@ -47,7 +48,7 @@ void main() { final rdr = WKTReader.withFactory(geomFactory); setUp(() async { - connection = PostgreSQLConnection('localhost', 5432, 'test', + connection = PostgreSQLConnection('localhost', 5432, 'dart_test', username: 'dart', password: 'dart'); await connection.open(); From 2f5a7131463ecb6c093c8c7af08303fc810890b5 Mon Sep 17 00:00:00 2001 From: bettdouglas Date: Tue, 9 Jun 2020 20:55:42 +0300 Subject: [PATCH 12/21] tested a few --- lib/src/connection.dart | 24 +++++++++++++++++++++++- lib/src/types.dart | 2 +- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/lib/src/connection.dart b/lib/src/connection.dart index 841e012..c12e872 100644 --- a/lib/src/connection.dart +++ b/lib/src/connection.dart @@ -112,6 +112,8 @@ class PostgreSQLConnection extends Object int _secretKey; List _salt; + final Map _extraDataTypes = {}; + bool _hasConnectedPreviously = false; _PostgreSQLConnectionState _connectionState; @@ -129,7 +131,7 @@ class PostgreSQLConnection extends Object /// /// Connections may not be reopened after they are closed or opened more than once. If a connection has already been /// opened and this method is called, an exception will be thrown. - Future open() async { + Future open({bool enablePostGISSupport=false}) async { if (_hasConnectedPreviously) { throw PostgreSQLException( 'Attempting to reopen a closed connection. Create a instance instead.'); @@ -163,6 +165,26 @@ class PostgreSQLConnection extends Object rethrow; } + + if (enablePostGISSupport) { + + // fetch oids from database (dynamic values) + final dataTypes = await _connection._query( + 'SELECT oid::int,typname::text from pg_type where typname =\'geometry\' or typname = \'geography\'', + substitutionValues: {'newtypes': '(\'geometry\',\'geography\')'}, + ); + + + final mapped = dataTypes.map((row) { + final oid = row[0] as int; + final typname = row[1] as String; + return MapEntry(typname,oid); + }); + _extraDataTypes.addEntries(mapped); + } + + print(_extraDataTypes); + } /// Closes a connection. diff --git a/lib/src/types.dart b/lib/src/types.dart index 8469972..babc70c 100644 --- a/lib/src/types.dart +++ b/lib/src/types.dart @@ -66,7 +66,7 @@ enum PostgreSQLDataType { /// When returned from database, format will be xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx. uuid, - /// Must be a [List] of [int] currently in Ewkb(Extended Well-Known Binary) format + /// Must be a [List] of [int] currently in Ewkb(Extended Well-Known Binary) format (https://postgis.net/docs/using_postgis_dbmanagement.html) /// geometry } From b0c853a282c3b170973da86478c6267b98ae1b46 Mon Sep 17 00:00:00 2001 From: bettdouglas Date: Tue, 9 Jun 2020 21:07:59 +0300 Subject: [PATCH 13/21] added Geometry to `PostgresTextEncoder` --- lib/src/binary_codec.dart | 2 +- lib/src/text_codec.dart | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/src/binary_codec.dart b/lib/src/binary_codec.dart index b4bf542..cbd9514 100644 --- a/lib/src/binary_codec.dart +++ b/lib/src/binary_codec.dart @@ -206,7 +206,7 @@ class PostgresBinaryEncoder extends Converter { utf8.encode( value.toText(), ), - ); //TODO: Convert to Uint8List. Need help here. Using toText() in raw sql inserts it well. + ); ///TODO: Is this neccessary since I've added it to `PostgresTextEncoder` } } } diff --git a/lib/src/text_codec.dart b/lib/src/text_codec.dart index 1cecca0..45feac0 100644 --- a/lib/src/text_codec.dart +++ b/lib/src/text_codec.dart @@ -37,6 +37,10 @@ class PostgresTextEncoder extends Converter { return encodeJSON(value); } + if (value is Geometry) { + return value.toText(); + } + throw PostgreSQLException("Could not infer type of value '$value'."); } From e23a3c62581c0bb643e688cdf04ab1020ba8c8f3 Mon Sep 17 00:00:00 2001 From: bettdouglas Date: Tue, 9 Jun 2020 21:09:04 +0300 Subject: [PATCH 14/21] added link to geometry specification --- lib/src/types.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/types.dart b/lib/src/types.dart index 8469972..9cfb48f 100644 --- a/lib/src/types.dart +++ b/lib/src/types.dart @@ -67,6 +67,6 @@ enum PostgreSQLDataType { uuid, /// Must be a [List] of [int] currently in Ewkb(Extended Well-Known Binary) format - /// + /// (https://postgis.net/docs/using_postgis_dbmanagement.html#OpenGISWKBWKT) geometry } From ea2fc20b101f83827adb92893e6b48f333a40f61 Mon Sep 17 00:00:00 2001 From: bettdouglas Date: Tue, 9 Jun 2020 21:23:51 +0300 Subject: [PATCH 15/21] added tests for PostgresTextEncoder for geometry --- test/geometry_test.dart | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/test/geometry_test.dart b/test/geometry_test.dart index bcee7a1..08c6bd6 100644 --- a/test/geometry_test.dart +++ b/test/geometry_test.dart @@ -63,6 +63,40 @@ void main() { await connection?.close(); }); + test( + 'Inserting geometries in plain dart objects should be inserted using geometry.toText()', + () async { + final point = rdr.read(WKT_POINT); + final lineString = rdr.read(WKT_LINESTRING); + final polygon = rdr.read(WKT_POLY); + final multiPoint = rdr.read(WKT_MULTIPOINT); + final multiLineString = rdr.read(WKT_MULTILINESTRING); + final multiPolygon = rdr.read(WKT_MULTIPOLYGON); + final geometryCollection = rdr.read(WKT_GC); + + final result = await connection.query( + 'INSERT into test(geom) VALUES (@point),(@lineString),(@polygon),(@multiPoint),(@multiPolygon),(@multiLineString),(@geometryCollection) returning geom,geom,geom,geom,geom,geom,geom', + substitutionValues: { + 'point': point, + 'lineString': lineString, + 'polygon': polygon, + 'multiPolygon': multiPolygon, + 'multiLineString': multiLineString, + 'multiPoint': multiPoint, + 'geometryCollection' : geometryCollection + }, + ); + + expect(point.equals(result[0][0] as Point), true); + expect(lineString.equals(result[1][0] as LineString), true); + expect(polygon.equals(result[2][0] as Polygon), true); + expect(multiPoint.equals(result[3][0] as MultiPoint), true); + expect(multiPolygon.equals(result[4][0] as MultiPolygon), true); + expect(multiLineString.equals(result[5][0] as MultiLineString), true); + expect(geometryCollection.equals(result[6][0] as GeometryCollection), true); //TODO: Issue with checking equality on GeometryCollection. + + }); + test('Can store point and read point as dart_jts.Point', () async { final result = await connection.query( 'INSERT into test(geom) VALUES (@point) returning geom', From 0e99449c560a4d32f8a85d8c7e5016fc28b603d2 Mon Sep 17 00:00:00 2001 From: bettdouglas Date: Thu, 11 Jun 2020 23:32:47 +0300 Subject: [PATCH 16/21] added read on connect query as well as test on other types --- lib/postgres.dart | 5 ++++ lib/src/binary_codec.dart | 37 ++++++++++++++------------- lib/src/connection.dart | 46 ++++++++++++++++++++++++++++------ lib/src/execution_context.dart | 6 +++++ lib/src/query.dart | 8 +++--- lib/src/server_messages.dart | 1 + lib/src/transaction_proxy.dart | 5 ++-- test/encoding_test.dart | 2 +- test/geometry_test.dart | 2 +- 9 files changed, 79 insertions(+), 33 deletions(-) diff --git a/lib/postgres.dart b/lib/postgres.dart index a3edbe1..7a58d25 100644 --- a/lib/postgres.dart +++ b/lib/postgres.dart @@ -1,7 +1,12 @@ library postgres; +import 'package:postgres/src/types.dart'; + export 'package:dart_jts/dart_jts.dart' show WKBReader,Geometry; export 'src/connection.dart'; export 'src/execution_context.dart'; export 'src/substituter.dart'; export 'src/types.dart'; + + +Map typeMap; diff --git a/lib/src/binary_codec.dart b/lib/src/binary_codec.dart index b4bf542..5fa59cd 100644 --- a/lib/src/binary_codec.dart +++ b/lib/src/binary_codec.dart @@ -219,6 +219,7 @@ class PostgresBinaryDecoder extends Converter { const PostgresBinaryDecoder(this.typeCode); final int typeCode; + // final Map typeMap; @override dynamic convert(Uint8List value) { @@ -311,22 +312,22 @@ class PostgresBinaryDecoder extends Converter { } } - static final Map typeMap = { - 16: PostgreSQLDataType.boolean, - 17: PostgreSQLDataType.byteArray, - 19: PostgreSQLDataType.name, - 20: PostgreSQLDataType.bigInteger, - 21: PostgreSQLDataType.smallInteger, - 23: PostgreSQLDataType.integer, - 25: PostgreSQLDataType.text, - 700: PostgreSQLDataType.real, - 701: PostgreSQLDataType.double, - 1082: PostgreSQLDataType.date, - 1114: PostgreSQLDataType.timestampWithoutTimezone, - 1184: PostgreSQLDataType.timestampWithTimezone, - 2950: PostgreSQLDataType.uuid, - 3802: PostgreSQLDataType.json, - 46315: PostgreSQLDataType.geometry, /// TODO: Oid Changes on different databases after running `CREATE EXTENSION postgis` - 46971: PostgreSQLDataType.geometry /// `SELECT oid, typarray FROM pg_type WHERE typname in ('geometry','geography')`; - }; + // static final Map typeMap = { + // 16: PostgreSQLDataType.boolean, + // 17: PostgreSQLDataType.byteArray, + // 19: PostgreSQLDataType.name, + // 20: PostgreSQLDataType.bigInteger, + // 21: PostgreSQLDataType.smallInteger, + // 23: PostgreSQLDataType.integer, + // 25: PostgreSQLDataType.text, + // 700: PostgreSQLDataType.real, + // 701: PostgreSQLDataType.double, + // 1082: PostgreSQLDataType.date, + // 1114: PostgreSQLDataType.timestampWithoutTimezone, + // 1184: PostgreSQLDataType.timestampWithTimezone, + // 2950: PostgreSQLDataType.uuid, + // 3802: PostgreSQLDataType.json, + // 46315: PostgreSQLDataType.geometry, /// TODO: Oid Changes on different databases after running `CREATE EXTENSION postgis` + // 46971: PostgreSQLDataType.geometry /// `SELECT oid, typarray FROM pg_type WHERE typname in ('geometry','geography')`; + // }; } diff --git a/lib/src/connection.dart b/lib/src/connection.dart index c12e872..a6096ba 100644 --- a/lib/src/connection.dart +++ b/lib/src/connection.dart @@ -6,6 +6,7 @@ import 'dart:io'; import 'dart:typed_data'; import 'package:buffer/buffer.dart'; +import 'package:postgres/postgres.dart'; import 'client_messages.dart'; import 'execution_context.dart'; @@ -166,12 +167,31 @@ class PostgreSQLConnection extends Object rethrow; } - if (enablePostGISSupport) { - + typeMap = { + 16: PostgreSQLDataType.boolean, + 17: PostgreSQLDataType.byteArray, + 19: PostgreSQLDataType.name, + 20: PostgreSQLDataType.bigInteger, + 21: PostgreSQLDataType.smallInteger, + 23: PostgreSQLDataType.integer, + 25: PostgreSQLDataType.text, + 700: PostgreSQLDataType.real, + 701: PostgreSQLDataType.double, + 1082: PostgreSQLDataType.date, + 1114: PostgreSQLDataType.timestampWithoutTimezone, + 1184: PostgreSQLDataType.timestampWithTimezone, + 2950: PostgreSQLDataType.uuid, + 3802: PostgreSQLDataType.json, + }; + + // if (enablePostGISSupport) { + // fetch oids from database (dynamic values) final dataTypes = await _connection._query( - 'SELECT oid::int,typname::text from pg_type where typname =\'geometry\' or typname = \'geography\'', - substitutionValues: {'newtypes': '(\'geometry\',\'geography\')'}, + ''' + select oid::int,typname from pg_type where typname in ('text','int2','int4','int8','float4','float8','bool','date','bytea', 'timestamp','timestamptz','jsonb','name','uuid','geometry', 'geography'); + ''', + // substitutionValues: {'newtypes': '(\'geometry\',\'geography\')'}, ); @@ -181,10 +201,20 @@ class PostgreSQLConnection extends Object return MapEntry(typname,oid); }); _extraDataTypes.addEntries(mapped); - } + // } - print(_extraDataTypes); + typeMap = _extraDataTypes.map((key, value) { + if(key == 'bool') { + return MapEntry(value,PostgreSQLFormatIdentifier.typeStringToCodeMap['boolean']); + } + return MapEntry(value,PostgreSQLFormatIdentifier.typeStringToCodeMap[key]); + }); + + // add boolean since it's not called boolean but bool + + print(typeMap); + } /// Closes a connection. @@ -447,7 +477,7 @@ abstract class _PostgreSQLExecutionContextMixin } final query = Query>>( - fmtString, substitutionValues, _connection, _transaction); + fmtString, substitutionValues, _connection, _transaction,typeMap); if (allowReuse) { query.statementIdentifier = _connection._cache.identifierForQuery(query); } @@ -492,7 +522,7 @@ abstract class _PostgreSQLExecutionContextMixin } final query = Query( - fmtString, substitutionValues, _connection, _transaction, + fmtString, substitutionValues, _connection, _transaction,typeMap, onlyReturnAffectedRowCount: true); return _enqueue(query, timeoutInSeconds: timeoutInSeconds); diff --git a/lib/src/execution_context.dart b/lib/src/execution_context.dart index 36198db..d0e22ff 100644 --- a/lib/src/execution_context.dart +++ b/lib/src/execution_context.dart @@ -6,6 +6,12 @@ import 'substituter.dart'; import 'types.dart'; abstract class PostgreSQLExecutionContext { + + // final Map typeMap; + + // PostgreSQLExecutionContext(this.typeMap); + + /// Returns this context queue size int get queueSize; diff --git a/lib/src/query.dart b/lib/src/query.dart index df8489a..8467a30 100644 --- a/lib/src/query.dart +++ b/lib/src/query.dart @@ -18,11 +18,14 @@ class Query { this.statement, this.substitutionValues, this.connection, - this.transaction, { + this.transaction, + this.typeMap, + { this.onlyReturnAffectedRowCount = false, }); final bool onlyReturnAffectedRowCount; + final Map typeMap; String statementIdentifier; @@ -121,8 +124,7 @@ class Query { return true; } - final actualType = PostgresBinaryDecoder - .typeMap[actualParameterTypeCodeIterator.current]; + final actualType = typeMap[actualParameterTypeCodeIterator.current]; return actualType == specifiedType; }).any((v) => v == false); diff --git a/lib/src/server_messages.dart b/lib/src/server_messages.dart index 4ee1f9a..70677ad 100644 --- a/lib/src/server_messages.dart +++ b/lib/src/server_messages.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'dart:typed_data'; import 'package:buffer/buffer.dart'; +import 'package:postgres/postgres.dart'; import 'connection.dart'; import 'query.dart'; diff --git a/lib/src/transaction_proxy.dart b/lib/src/transaction_proxy.dart index 67ea2b1..b57f199 100644 --- a/lib/src/transaction_proxy.dart +++ b/lib/src/transaction_proxy.dart @@ -8,7 +8,7 @@ class _TransactionProxy extends Object implements PostgreSQLExecutionContext { _TransactionProxy( this._connection, this.executionBlock, this.commitTimeoutInSeconds) { - _beginQuery = Query('BEGIN', {}, _connection, this, + _beginQuery = Query('BEGIN', {}, _connection, this,typeMap, onlyReturnAffectedRowCount: true); _beginQuery.future.then(startTransaction).catchError((err, StackTrace st) { @@ -34,6 +34,7 @@ class _TransactionProxy extends Object bool _hasFailed = false; bool _hasRolledBack = false; + @override void cancelTransaction({String reason}) { throw _TransactionRollbackException(reason); @@ -87,7 +88,7 @@ class _TransactionProxy extends Object 'that prevented this query from executing.'); _queue.cancel(err); - final rollback = Query('ROLLBACK', {}, _connection, _transaction, + final rollback = Query('ROLLBACK', {}, _connection, _transaction,typeMap, onlyReturnAffectedRowCount: true); _queue.addEvenIfCancelled(rollback); diff --git a/test/encoding_test.dart b/test/encoding_test.dart index 877f1cf..9b74070 100644 --- a/test/encoding_test.dart +++ b/test/encoding_test.dart @@ -413,7 +413,7 @@ Future expectInverse(dynamic value, PostgreSQLDataType dataType) async { dataType = PostgreSQLDataType.bigInteger; } int code; - PostgresBinaryDecoder.typeMap.forEach((key, type) { + typeMap.forEach((key, type) { if (type == dataType) { code = key; } diff --git a/test/geometry_test.dart b/test/geometry_test.dart index bcee7a1..e15f15b 100644 --- a/test/geometry_test.dart +++ b/test/geometry_test.dart @@ -50,7 +50,7 @@ void main() { setUp(() async { connection = PostgreSQLConnection('localhost', 5432, 'dart_test', username: 'dart', password: 'dart'); - await connection.open(); + await connection.open(enablePostGISSupport: true); await connection.execute(''' DROP TABLE IF EXISTS test; From 637356193f18a5c072372dafa7131176dfb76d27 Mon Sep 17 00:00:00 2001 From: bettdouglas Date: Sun, 14 Jun 2020 11:21:32 +0300 Subject: [PATCH 17/21] crude fix to support postgis --- lib/postgres.dart | 2 +- lib/src/connection.dart | 95 +++++++++++++++++++++++------------------ 2 files changed, 54 insertions(+), 43 deletions(-) diff --git a/lib/postgres.dart b/lib/postgres.dart index 7a58d25..1605a0a 100644 --- a/lib/postgres.dart +++ b/lib/postgres.dart @@ -9,4 +9,4 @@ export 'src/substituter.dart'; export 'src/types.dart'; -Map typeMap; +Map typeMap = {}; diff --git a/lib/src/connection.dart b/lib/src/connection.dart index a6096ba..af943e8 100644 --- a/lib/src/connection.dart +++ b/lib/src/connection.dart @@ -113,7 +113,7 @@ class PostgreSQLConnection extends Object int _secretKey; List _salt; - final Map _extraDataTypes = {}; + final Map _extraDataTypes = {}; bool _hasConnectedPreviously = false; _PostgreSQLConnectionState _connectionState; @@ -132,7 +132,7 @@ class PostgreSQLConnection extends Object /// /// Connections may not be reopened after they are closed or opened more than once. If a connection has already been /// opened and this method is called, an exception will be thrown. - Future open({bool enablePostGISSupport=false}) async { + Future open({bool enablePostGISSupport = false}) async { if (_hasConnectedPreviously) { throw PostgreSQLException( 'Attempting to reopen a closed connection. Create a instance instead.'); @@ -167,54 +167,65 @@ class PostgreSQLConnection extends Object rethrow; } - typeMap = { - 16: PostgreSQLDataType.boolean, - 17: PostgreSQLDataType.byteArray, - 19: PostgreSQLDataType.name, - 20: PostgreSQLDataType.bigInteger, - 21: PostgreSQLDataType.smallInteger, - 23: PostgreSQLDataType.integer, - 25: PostgreSQLDataType.text, - 700: PostgreSQLDataType.real, - 701: PostgreSQLDataType.double, - 1082: PostgreSQLDataType.date, - 1114: PostgreSQLDataType.timestampWithoutTimezone, - 1184: PostgreSQLDataType.timestampWithTimezone, - 2950: PostgreSQLDataType.uuid, - 3802: PostgreSQLDataType.json, - }; + if(enablePostGISSupport) { + //CREATE EXTENSION postgis; + try { + await _connection.execute('CREATE EXTENSION postgis'); + } catch (e,st) { + await _close(e, st); + rethrow; + } + } + + await _updateIDS(); + } + + Future updateIDS() => _updateIDS(); + + Future _updateIDS() async { + typeMap = typeMap.isNotEmpty ? typeMap : { + 16: PostgreSQLDataType.boolean, + 17: PostgreSQLDataType.byteArray, + 19: PostgreSQLDataType.name, + 20: PostgreSQLDataType.bigInteger, + 21: PostgreSQLDataType.smallInteger, + 23: PostgreSQLDataType.integer, + 25: PostgreSQLDataType.text, + 700: PostgreSQLDataType.real, + 701: PostgreSQLDataType.double, + 1082: PostgreSQLDataType.date, + 1114: PostgreSQLDataType.timestampWithoutTimezone, + 1184: PostgreSQLDataType.timestampWithTimezone, + 2950: PostgreSQLDataType.uuid, + 3802: PostgreSQLDataType.json, + }; // if (enablePostGISSupport) { - - // fetch oids from database (dynamic values) - final dataTypes = await _connection._query( - ''' + + // fetch oids from database (dynamic values) + final dataTypes = await _connection._query( + ''' select oid::int,typname from pg_type where typname in ('text','int2','int4','int8','float4','float8','bool','date','bytea', 'timestamp','timestamptz','jsonb','name','uuid','geometry', 'geography'); ''', - // substitutionValues: {'newtypes': '(\'geometry\',\'geography\')'}, - ); + ); - - final mapped = dataTypes.map((row) { - final oid = row[0] as int; - final typname = row[1] as String; - return MapEntry(typname,oid); - }); - _extraDataTypes.addEntries(mapped); + final mapped = dataTypes.map((row) { + final oid = row[0] as int; + final typname = row[1] as String; + return MapEntry(typname, oid); + }); + _extraDataTypes.addEntries(mapped); // } typeMap = _extraDataTypes.map((key, value) { - if(key == 'bool') { - return MapEntry(value,PostgreSQLFormatIdentifier.typeStringToCodeMap['boolean']); + // add boolean since it's not called bool on pg_types + if (key == 'bool') { + return MapEntry( + value, PostgreSQLFormatIdentifier.typeStringToCodeMap['boolean']); } - return MapEntry(value,PostgreSQLFormatIdentifier.typeStringToCodeMap[key]); + return MapEntry( + value, PostgreSQLFormatIdentifier.typeStringToCodeMap[key]); }); - - // add boolean since it's not called boolean but bool - - - print(typeMap); - } /// Closes a connection. @@ -477,7 +488,7 @@ abstract class _PostgreSQLExecutionContextMixin } final query = Query>>( - fmtString, substitutionValues, _connection, _transaction,typeMap); + fmtString, substitutionValues, _connection, _transaction, typeMap); if (allowReuse) { query.statementIdentifier = _connection._cache.identifierForQuery(query); } @@ -522,7 +533,7 @@ abstract class _PostgreSQLExecutionContextMixin } final query = Query( - fmtString, substitutionValues, _connection, _transaction,typeMap, + fmtString, substitutionValues, _connection, _transaction, typeMap, onlyReturnAffectedRowCount: true); return _enqueue(query, timeoutInSeconds: timeoutInSeconds); From cd6731cd9fc0e429c16ee747f2f9d41e2c6c3918 Mon Sep 17 00:00:00 2001 From: Douglas Bett Date: Sun, 14 Jun 2020 15:26:17 +0300 Subject: [PATCH 18/21] added check for if not exists when creating extension --- lib/src/connection.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/connection.dart b/lib/src/connection.dart index af943e8..3796dfd 100644 --- a/lib/src/connection.dart +++ b/lib/src/connection.dart @@ -170,7 +170,7 @@ class PostgreSQLConnection extends Object if(enablePostGISSupport) { //CREATE EXTENSION postgis; try { - await _connection.execute('CREATE EXTENSION postgis'); + await _connection.execute('CREATE EXTENSION IF NOT EXISTS postgis'); } catch (e,st) { await _close(e, st); rethrow; From cf251549d965831c1c9eb91641fa09ef485c1bb5 Mon Sep 17 00:00:00 2001 From: bettdouglas Date: Fri, 3 Jul 2020 06:14:36 +0300 Subject: [PATCH 19/21] merge changes from upstream master --- CHANGELOG.md | 6 ++++++ README.md | 11 +++-------- lib/src/connection.dart | 32 +++++++++++++++++++++++--------- lib/src/query.dart | 5 +++-- lib/src/substituter.dart | 2 +- lib/src/text_codec.dart | 33 ++++++++++++++------------------- pubspec.yaml | 4 ++-- test/encoding_test.dart | 16 ++++------------ 8 files changed, 56 insertions(+), 53 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a6ae20e..48d2ede 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 2.2.0 + +- Supporting Unix socket connections. (Thanks to [grillbiff](https://github.com/grillbiff), + [#124](https://github.com/stablekernel/postgresql-dart/pull/124)) +- Preparation for custom type converters. + ## 2.1.1 - Fix `RuneIterator.current` use, which no longer returns `null` in 2.8 SDK. diff --git a/README.md b/README.md index f717a96..82aae83 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ This driver uses the more efficient and secure extended query format of the Post Create `PostgreSQLConnection`s and `open` them: ```dart -var connection = new PostgreSQLConnection("localhost", 5432, "dart_test", username: "dart", password: "dart"); +var connection = PostgreSQLConnection("localhost", 5432, "dart_test", username: "dart", password: "dart"); await connection.open(); ``` @@ -47,18 +47,13 @@ Execute queries in a transaction: ```dart await connection.transaction((ctx) async { var result = await ctx.query("SELECT id FROM table"); - await ctx.query("INSERT INTO table (id) VALUES (@a:int4)", { + await ctx.query("INSERT INTO table (id) VALUES (@a:int4)", substitutionValues: { "a" : result.last[0] + 1 }); }); ``` -See the API documentation: https://www.dartdocs.org/documentation/postgres/latest. - -## Development branch - -The package's upcoming 2.0 version is being developed in the -[`dev`](https://github.com/stablekernel/postgresql-dart/tree/dev) branch. +See the API documentation: https://pub.dev/documentation/postgres/latest/ ## Features and bugs diff --git a/lib/src/connection.dart b/lib/src/connection.dart index 841e012..00ffc7f 100644 --- a/lib/src/connection.dart +++ b/lib/src/connection.dart @@ -38,13 +38,18 @@ class PostgreSQLConnection extends Object /// [queryTimeoutInSeconds] refers to the default timeout for [PostgreSQLExecutionContext]'s execute and query methods. /// [timeZone] is the timezone the connection is in. Defaults to 'UTC'. /// [useSSL] when true, uses a secure socket when connecting to a PostgreSQL database. - PostgreSQLConnection(this.host, this.port, this.databaseName, - {this.username, - this.password, - this.timeoutInSeconds = 30, - this.queryTimeoutInSeconds = 30, - this.timeZone = 'UTC', - this.useSSL = false}) { + PostgreSQLConnection( + this.host, + this.port, + this.databaseName, { + this.username, + this.password, + this.timeoutInSeconds = 30, + this.queryTimeoutInSeconds = 30, + this.timeZone = 'UTC', + this.useSSL = false, + this.isUnixSocket = false, + }) { _connectionState = _PostgreSQLConnectionStateClosed(); _connectionState.connection = this; } @@ -82,6 +87,9 @@ class PostgreSQLConnection extends Object /// The processID of this backend. int get processID => _processID; + /// If true, connection is made via unix socket. + final bool isUnixSocket; + /// Stream of notification from the database. /// /// Listen to this [Stream] to receive events from PostgreSQL NOTIFY commands. @@ -137,8 +145,14 @@ class PostgreSQLConnection extends Object try { _hasConnectedPreviously = true; - _socket = await Socket.connect(host, port) - .timeout(Duration(seconds: timeoutInSeconds)); + if (isUnixSocket) { + _socket = await Socket.connect( + InternetAddress(host, type: InternetAddressType.unix), port) + .timeout(Duration(seconds: timeoutInSeconds)); + } else { + _socket = await Socket.connect(host, port) + .timeout(Duration(seconds: timeoutInSeconds)); + } _framer = MessageFramer(); if (useSSL) { diff --git a/lib/src/query.dart b/lib/src/query.dart index df8489a..b42474c 100644 --- a/lib/src/query.dart +++ b/lib/src/query.dart @@ -209,8 +209,9 @@ class ParameterValue { factory ParameterValue.text(dynamic value) { Uint8List bytes; if (value != null) { - final converter = PostgresTextEncoder(false); - bytes = castBytes(utf8.encode(converter.convert(value))); + final converter = PostgresTextEncoder(); + bytes = castBytes( + utf8.encode(converter.convert(value, escapeStrings: false))); } final length = bytes?.length ?? 0; return ParameterValue._(false, bytes, length); diff --git a/lib/src/substituter.dart b/lib/src/substituter.dart index 8e087ee..b2cc801 100644 --- a/lib/src/substituter.dart +++ b/lib/src/substituter.dart @@ -56,7 +56,7 @@ class PostgreSQLFormat { static String substitute(String fmtString, Map values, {SQLReplaceIdentifierFunction replace}) { - final converter = PostgresTextEncoder(true); + final converter = PostgresTextEncoder(); values ??= {}; replace ??= (spec, index) => converter.convert(values[spec.name]); diff --git a/lib/src/text_codec.dart b/lib/src/text_codec.dart index 45feac0..284fc1b 100644 --- a/lib/src/text_codec.dart +++ b/lib/src/text_codec.dart @@ -2,39 +2,34 @@ import 'dart:convert'; import 'package:postgres/postgres.dart'; -class PostgresTextEncoder extends Converter { - const PostgresTextEncoder(this._escapeStrings); - - final bool _escapeStrings; - - @override - String convert(dynamic value) { +class PostgresTextEncoder { + String convert(dynamic value, {bool escapeStrings = true}) { if (value == null) { return 'null'; } if (value is int) { - return encodeNumber(value); + return _encodeNumber(value); } if (value is double) { - return encodeDouble(value); + return _encodeDouble(value); } if (value is String) { - return encodeString(value, _escapeStrings); + return _encodeString(value, escapeStrings); } if (value is DateTime) { - return encodeDateTime(value, isDateOnly: false); + return _encodeDateTime(value, isDateOnly: false); } if (value is bool) { - return encodeBoolean(value); + return _encodeBoolean(value); } if (value is Map) { - return encodeJSON(value); + return _encodeJSON(value); } if (value is Geometry) { @@ -44,7 +39,7 @@ class PostgresTextEncoder extends Converter { throw PostgreSQLException("Could not infer type of value '$value'."); } - String encodeString(String text, bool escapeStrings) { + String _encodeString(String text, bool escapeStrings) { if (!escapeStrings) { return text; } @@ -89,7 +84,7 @@ class PostgresTextEncoder extends Converter { return buf.toString(); } - String encodeNumber(num value) { + String _encodeNumber(num value) { if (value.isNaN) { return "'nan'"; } @@ -101,7 +96,7 @@ class PostgresTextEncoder extends Converter { return value.toInt().toString(); } - String encodeDouble(double value) { + String _encodeDouble(double value) { if (value.isNaN) { return "'nan'"; } @@ -113,11 +108,11 @@ class PostgresTextEncoder extends Converter { return value.toString(); } - String encodeBoolean(bool value) { + String _encodeBoolean(bool value) { return value ? 'TRUE' : 'FALSE'; } - String encodeDateTime(DateTime value, {bool isDateOnly}) { + String _encodeDateTime(DateTime value, {bool isDateOnly}) { var string = value.toIso8601String(); if (isDateOnly) { @@ -151,7 +146,7 @@ class PostgresTextEncoder extends Converter { return "'$string'"; } - String encodeJSON(dynamic value) { + String _encodeJSON(dynamic value) { if (value == null) { return 'null'; } diff --git a/pubspec.yaml b/pubspec.yaml index d190b88..9de028d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,10 +1,10 @@ name: postgres description: PostgreSQL database driver. Supports statement reuse and binary protocol. -version: 2.1.1 +version: 2.2.0-dev homepage: https://github.com/stablekernel/postgresql-dart environment: - sdk: ">=2.2.0 <3.0.0" + sdk: ">=2.8.0 <3.0.0" dependencies: buffer: ^1.0.6 diff --git a/test/encoding_test.dart b/test/encoding_test.dart index 877f1cf..edfc81e 100644 --- a/test/encoding_test.dart +++ b/test/encoding_test.dart @@ -232,8 +232,9 @@ void main() { }); group('Text encoders', () { + final encoder = PostgresTextEncoder(); + test('Escape strings', () { - final encoder = PostgresTextEncoder(true); // ' b o b ' expect( utf8.encode(encoder.convert('bob')), equals([39, 98, 111, 98, 39])); @@ -294,9 +295,8 @@ void main() { DateTime(12345, DateTime.february, 3, 4, 5, 6, 0) }; - final encoder = PostgresTextEncoder(false); pairs.forEach((k, v) { - expect(encoder.convert(v), "'$k'"); + expect(encoder.convert(v, escapeStrings: false), "'$k'"); }); }); @@ -311,37 +311,29 @@ void main() { '0.0': 0.0 }; - final encoder = PostgresTextEncoder(false); pairs.forEach((k, v) { - expect(encoder.convert(v), '$k'); + expect(encoder.convert(v, escapeStrings: false), '$k'); }); }); test('Encode Int', () { - final encoder = PostgresTextEncoder(false); - expect(encoder.convert(1), '1'); expect(encoder.convert(1234324323), '1234324323'); expect(encoder.convert(-1234324323), '-1234324323'); }); test('Encode Bool', () { - final encoder = PostgresTextEncoder(false); - expect(encoder.convert(true), 'TRUE'); expect(encoder.convert(false), 'FALSE'); }); test('Encode JSONB', () { - final encoder = PostgresTextEncoder(false); - expect(encoder.convert({'a': 'b'}), '{"a":"b"}'); expect(encoder.convert({'a': true}), '{"a":true}'); expect(encoder.convert({'b': false}), '{"b":false}'); }); test('Attempt to infer unknown type throws exception', () { - final encoder = PostgresTextEncoder(false); try { encoder.convert([]); fail('unreachable'); From c5e17a303e2d2ff8d3dffa31f8ea62074b7fa5ee Mon Sep 17 00:00:00 2001 From: bettdouglas Date: Fri, 3 Jul 2020 06:26:37 +0300 Subject: [PATCH 20/21] merge changes from upstream master --- CHANGELOG.md | 6 ++++++ README.md | 11 +++-------- lib/src/binary_codec.dart | 2 +- lib/src/connection.dart | 32 +++++++++++++++++++++++--------- lib/src/query.dart | 5 +++-- lib/src/substituter.dart | 2 +- lib/src/text_codec.dart | 37 ++++++++++++++++++------------------- lib/src/types.dart | 4 ++-- pubspec.yaml | 4 ++-- test/encoding_test.dart | 16 ++++------------ test/geometry_test.dart | 34 ++++++++++++++++++++++++++++++++++ 11 files changed, 97 insertions(+), 56 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a6ae20e..48d2ede 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 2.2.0 + +- Supporting Unix socket connections. (Thanks to [grillbiff](https://github.com/grillbiff), + [#124](https://github.com/stablekernel/postgresql-dart/pull/124)) +- Preparation for custom type converters. + ## 2.1.1 - Fix `RuneIterator.current` use, which no longer returns `null` in 2.8 SDK. diff --git a/README.md b/README.md index f717a96..82aae83 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ This driver uses the more efficient and secure extended query format of the Post Create `PostgreSQLConnection`s and `open` them: ```dart -var connection = new PostgreSQLConnection("localhost", 5432, "dart_test", username: "dart", password: "dart"); +var connection = PostgreSQLConnection("localhost", 5432, "dart_test", username: "dart", password: "dart"); await connection.open(); ``` @@ -47,18 +47,13 @@ Execute queries in a transaction: ```dart await connection.transaction((ctx) async { var result = await ctx.query("SELECT id FROM table"); - await ctx.query("INSERT INTO table (id) VALUES (@a:int4)", { + await ctx.query("INSERT INTO table (id) VALUES (@a:int4)", substitutionValues: { "a" : result.last[0] + 1 }); }); ``` -See the API documentation: https://www.dartdocs.org/documentation/postgres/latest. - -## Development branch - -The package's upcoming 2.0 version is being developed in the -[`dev`](https://github.com/stablekernel/postgresql-dart/tree/dev) branch. +See the API documentation: https://pub.dev/documentation/postgres/latest/ ## Features and bugs diff --git a/lib/src/binary_codec.dart b/lib/src/binary_codec.dart index 5fa59cd..5e21b05 100644 --- a/lib/src/binary_codec.dart +++ b/lib/src/binary_codec.dart @@ -206,7 +206,7 @@ class PostgresBinaryEncoder extends Converter { utf8.encode( value.toText(), ), - ); //TODO: Convert to Uint8List. Need help here. Using toText() in raw sql inserts it well. + ); ///TODO: Is this neccessary since I've added it to `PostgresTextEncoder` } } } diff --git a/lib/src/connection.dart b/lib/src/connection.dart index 3796dfd..3f5b4a9 100644 --- a/lib/src/connection.dart +++ b/lib/src/connection.dart @@ -39,13 +39,18 @@ class PostgreSQLConnection extends Object /// [queryTimeoutInSeconds] refers to the default timeout for [PostgreSQLExecutionContext]'s execute and query methods. /// [timeZone] is the timezone the connection is in. Defaults to 'UTC'. /// [useSSL] when true, uses a secure socket when connecting to a PostgreSQL database. - PostgreSQLConnection(this.host, this.port, this.databaseName, - {this.username, - this.password, - this.timeoutInSeconds = 30, - this.queryTimeoutInSeconds = 30, - this.timeZone = 'UTC', - this.useSSL = false}) { + PostgreSQLConnection( + this.host, + this.port, + this.databaseName, { + this.username, + this.password, + this.timeoutInSeconds = 30, + this.queryTimeoutInSeconds = 30, + this.timeZone = 'UTC', + this.useSSL = false, + this.isUnixSocket = false, + }) { _connectionState = _PostgreSQLConnectionStateClosed(); _connectionState.connection = this; } @@ -83,6 +88,9 @@ class PostgreSQLConnection extends Object /// The processID of this backend. int get processID => _processID; + /// If true, connection is made via unix socket. + final bool isUnixSocket; + /// Stream of notification from the database. /// /// Listen to this [Stream] to receive events from PostgreSQL NOTIFY commands. @@ -140,8 +148,14 @@ class PostgreSQLConnection extends Object try { _hasConnectedPreviously = true; - _socket = await Socket.connect(host, port) - .timeout(Duration(seconds: timeoutInSeconds)); + if (isUnixSocket) { + _socket = await Socket.connect( + InternetAddress(host, type: InternetAddressType.unix), port) + .timeout(Duration(seconds: timeoutInSeconds)); + } else { + _socket = await Socket.connect(host, port) + .timeout(Duration(seconds: timeoutInSeconds)); + } _framer = MessageFramer(); if (useSSL) { diff --git a/lib/src/query.dart b/lib/src/query.dart index 8467a30..ba5e86a 100644 --- a/lib/src/query.dart +++ b/lib/src/query.dart @@ -211,8 +211,9 @@ class ParameterValue { factory ParameterValue.text(dynamic value) { Uint8List bytes; if (value != null) { - final converter = PostgresTextEncoder(false); - bytes = castBytes(utf8.encode(converter.convert(value))); + final converter = PostgresTextEncoder(); + bytes = castBytes( + utf8.encode(converter.convert(value, escapeStrings: false))); } final length = bytes?.length ?? 0; return ParameterValue._(false, bytes, length); diff --git a/lib/src/substituter.dart b/lib/src/substituter.dart index 8e087ee..b2cc801 100644 --- a/lib/src/substituter.dart +++ b/lib/src/substituter.dart @@ -56,7 +56,7 @@ class PostgreSQLFormat { static String substitute(String fmtString, Map values, {SQLReplaceIdentifierFunction replace}) { - final converter = PostgresTextEncoder(true); + final converter = PostgresTextEncoder(); values ??= {}; replace ??= (spec, index) => converter.convert(values[spec.name]); diff --git a/lib/src/text_codec.dart b/lib/src/text_codec.dart index 1cecca0..284fc1b 100644 --- a/lib/src/text_codec.dart +++ b/lib/src/text_codec.dart @@ -2,45 +2,44 @@ import 'dart:convert'; import 'package:postgres/postgres.dart'; -class PostgresTextEncoder extends Converter { - const PostgresTextEncoder(this._escapeStrings); - - final bool _escapeStrings; - - @override - String convert(dynamic value) { +class PostgresTextEncoder { + String convert(dynamic value, {bool escapeStrings = true}) { if (value == null) { return 'null'; } if (value is int) { - return encodeNumber(value); + return _encodeNumber(value); } if (value is double) { - return encodeDouble(value); + return _encodeDouble(value); } if (value is String) { - return encodeString(value, _escapeStrings); + return _encodeString(value, escapeStrings); } if (value is DateTime) { - return encodeDateTime(value, isDateOnly: false); + return _encodeDateTime(value, isDateOnly: false); } if (value is bool) { - return encodeBoolean(value); + return _encodeBoolean(value); } if (value is Map) { - return encodeJSON(value); + return _encodeJSON(value); + } + + if (value is Geometry) { + return value.toText(); } throw PostgreSQLException("Could not infer type of value '$value'."); } - String encodeString(String text, bool escapeStrings) { + String _encodeString(String text, bool escapeStrings) { if (!escapeStrings) { return text; } @@ -85,7 +84,7 @@ class PostgresTextEncoder extends Converter { return buf.toString(); } - String encodeNumber(num value) { + String _encodeNumber(num value) { if (value.isNaN) { return "'nan'"; } @@ -97,7 +96,7 @@ class PostgresTextEncoder extends Converter { return value.toInt().toString(); } - String encodeDouble(double value) { + String _encodeDouble(double value) { if (value.isNaN) { return "'nan'"; } @@ -109,11 +108,11 @@ class PostgresTextEncoder extends Converter { return value.toString(); } - String encodeBoolean(bool value) { + String _encodeBoolean(bool value) { return value ? 'TRUE' : 'FALSE'; } - String encodeDateTime(DateTime value, {bool isDateOnly}) { + String _encodeDateTime(DateTime value, {bool isDateOnly}) { var string = value.toIso8601String(); if (isDateOnly) { @@ -147,7 +146,7 @@ class PostgresTextEncoder extends Converter { return "'$string'"; } - String encodeJSON(dynamic value) { + String _encodeJSON(dynamic value) { if (value == null) { return 'null'; } diff --git a/lib/src/types.dart b/lib/src/types.dart index babc70c..9cfb48f 100644 --- a/lib/src/types.dart +++ b/lib/src/types.dart @@ -66,7 +66,7 @@ enum PostgreSQLDataType { /// When returned from database, format will be xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx. uuid, - /// Must be a [List] of [int] currently in Ewkb(Extended Well-Known Binary) format (https://postgis.net/docs/using_postgis_dbmanagement.html) - /// + /// Must be a [List] of [int] currently in Ewkb(Extended Well-Known Binary) format + /// (https://postgis.net/docs/using_postgis_dbmanagement.html#OpenGISWKBWKT) geometry } diff --git a/pubspec.yaml b/pubspec.yaml index d190b88..9de028d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,10 +1,10 @@ name: postgres description: PostgreSQL database driver. Supports statement reuse and binary protocol. -version: 2.1.1 +version: 2.2.0-dev homepage: https://github.com/stablekernel/postgresql-dart environment: - sdk: ">=2.2.0 <3.0.0" + sdk: ">=2.8.0 <3.0.0" dependencies: buffer: ^1.0.6 diff --git a/test/encoding_test.dart b/test/encoding_test.dart index 9b74070..f5310bf 100644 --- a/test/encoding_test.dart +++ b/test/encoding_test.dart @@ -232,8 +232,9 @@ void main() { }); group('Text encoders', () { + final encoder = PostgresTextEncoder(); + test('Escape strings', () { - final encoder = PostgresTextEncoder(true); // ' b o b ' expect( utf8.encode(encoder.convert('bob')), equals([39, 98, 111, 98, 39])); @@ -294,9 +295,8 @@ void main() { DateTime(12345, DateTime.february, 3, 4, 5, 6, 0) }; - final encoder = PostgresTextEncoder(false); pairs.forEach((k, v) { - expect(encoder.convert(v), "'$k'"); + expect(encoder.convert(v, escapeStrings: false), "'$k'"); }); }); @@ -311,37 +311,29 @@ void main() { '0.0': 0.0 }; - final encoder = PostgresTextEncoder(false); pairs.forEach((k, v) { - expect(encoder.convert(v), '$k'); + expect(encoder.convert(v, escapeStrings: false), '$k'); }); }); test('Encode Int', () { - final encoder = PostgresTextEncoder(false); - expect(encoder.convert(1), '1'); expect(encoder.convert(1234324323), '1234324323'); expect(encoder.convert(-1234324323), '-1234324323'); }); test('Encode Bool', () { - final encoder = PostgresTextEncoder(false); - expect(encoder.convert(true), 'TRUE'); expect(encoder.convert(false), 'FALSE'); }); test('Encode JSONB', () { - final encoder = PostgresTextEncoder(false); - expect(encoder.convert({'a': 'b'}), '{"a":"b"}'); expect(encoder.convert({'a': true}), '{"a":true}'); expect(encoder.convert({'b': false}), '{"b":false}'); }); test('Attempt to infer unknown type throws exception', () { - final encoder = PostgresTextEncoder(false); try { encoder.convert([]); fail('unreachable'); diff --git a/test/geometry_test.dart b/test/geometry_test.dart index e15f15b..b98f1b2 100644 --- a/test/geometry_test.dart +++ b/test/geometry_test.dart @@ -63,6 +63,40 @@ void main() { await connection?.close(); }); + test( + 'Inserting geometries in plain dart objects should be inserted using geometry.toText()', + () async { + final point = rdr.read(WKT_POINT); + final lineString = rdr.read(WKT_LINESTRING); + final polygon = rdr.read(WKT_POLY); + final multiPoint = rdr.read(WKT_MULTIPOINT); + final multiLineString = rdr.read(WKT_MULTILINESTRING); + final multiPolygon = rdr.read(WKT_MULTIPOLYGON); + final geometryCollection = rdr.read(WKT_GC); + + final result = await connection.query( + 'INSERT into test(geom) VALUES (@point),(@lineString),(@polygon),(@multiPoint),(@multiPolygon),(@multiLineString),(@geometryCollection) returning geom,geom,geom,geom,geom,geom,geom', + substitutionValues: { + 'point': point, + 'lineString': lineString, + 'polygon': polygon, + 'multiPolygon': multiPolygon, + 'multiLineString': multiLineString, + 'multiPoint': multiPoint, + 'geometryCollection' : geometryCollection + }, + ); + + expect(point.equals(result[0][0] as Point), true); + expect(lineString.equals(result[1][0] as LineString), true); + expect(polygon.equals(result[2][0] as Polygon), true); + expect(multiPoint.equals(result[3][0] as MultiPoint), true); + expect(multiPolygon.equals(result[4][0] as MultiPolygon), true); + expect(multiLineString.equals(result[5][0] as MultiLineString), true); + expect(geometryCollection.equals(result[6][0] as GeometryCollection), true); //TODO: Issue with checking equality on GeometryCollection. + + }); + test('Can store point and read point as dart_jts.Point', () async { final result = await connection.query( 'INSERT into test(geom) VALUES (@point) returning geom', From 36b9b9c85c1cb0499c37599047f9a1020a18e61a Mon Sep 17 00:00:00 2001 From: bettdouglas Date: Fri, 3 Jul 2020 06:50:08 +0300 Subject: [PATCH 21/21] ignore failing geometry collection equality test --- test/geometry_test.dart | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/test/geometry_test.dart b/test/geometry_test.dart index b98f1b2..2f48812 100644 --- a/test/geometry_test.dart +++ b/test/geometry_test.dart @@ -63,6 +63,18 @@ void main() { await connection?.close(); }); + test('GeometryCollection Equality', () { + + final WKT_GC = + 'GEOMETRYCOLLECTION (POLYGON ((100 200, 200 200, 200 100, 100 100, 100 200)), LINESTRING (150 250, 250 250))'; + + final geometryCollection = rdr.read(WKT_GC); + final geometryCollection2 = rdr.read(WKT_GC); + + expect(geometryCollection.equals(geometryCollection2), true); + + }); + test( 'Inserting geometries in plain dart objects should be inserted using geometry.toText()', () async { @@ -93,7 +105,7 @@ void main() { expect(multiPoint.equals(result[3][0] as MultiPoint), true); expect(multiPolygon.equals(result[4][0] as MultiPolygon), true); expect(multiLineString.equals(result[5][0] as MultiLineString), true); - expect(geometryCollection.equals(result[6][0] as GeometryCollection), true); //TODO: Issue with checking equality on GeometryCollection. + // expect(geometryCollection.equals(result[6][0] as GeometryCollection), true); /// TODO: Issue with checking equality on GeometryCollection. (https://github.com/moovida/dart_jts/issues/2#issuecomment-653381031) });