Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Add support for Postgis Geometry data types #130

Closed
wants to merge 23 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
c77478c
add dart_jts dependecy to parse EWKB format
bettdouglas May 30, 2020
b5b12d8
Updated all switch statements on the type
bettdouglas Jun 7, 2020
191a713
added the various geometric types in Dart
bettdouglas Jun 7, 2020
6a016d5
Added geometry_tests
bettdouglas Jun 8, 2020
a9eb4c5
add LinearRing parse to interface
bettdouglas Jun 8, 2020
5260cd3
changed to ungrouped test
bettdouglas Jun 8, 2020
844c325
fix typo postgis and dartfmt
bettdouglas Jun 8, 2020
9e2084b
run dartfmt
bettdouglas Jun 8, 2020
876f039
Reduce boilerplate
bettdouglas Jun 9, 2020
6887784
added link to understand format of geometries
bettdouglas Jun 9, 2020
da9d9ed
fix import
bettdouglas Jun 9, 2020
2f5a713
tested a few
bettdouglas Jun 9, 2020
b0c853a
added Geometry to `PostgresTextEncoder`
bettdouglas Jun 9, 2020
e23a3c6
added link to geometry specification
bettdouglas Jun 9, 2020
ea2fc20
added tests for PostgresTextEncoder for geometry
bettdouglas Jun 9, 2020
0e99449
added read on connect query as well as test on other types
bettdouglas Jun 11, 2020
6373561
crude fix to support postgis
bettdouglas Jun 14, 2020
cd6731c
added check for if not exists when creating extension
bettdouglas Jun 14, 2020
cf25154
merge changes from upstream master
bettdouglas Jul 3, 2020
3975520
merge changes from upstream master
bettdouglas Jul 3, 2020
c5e17a3
merge changes from upstream master
bettdouglas Jul 3, 2020
ba15df0
merge conflicts
bettdouglas Jul 3, 2020
36b9b9c
ignore failing geometry collection equality test
bettdouglas Jul 3, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
11 changes: 3 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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();
```

Expand Down Expand Up @@ -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

Expand Down
6 changes: 6 additions & 0 deletions lib/postgres.dart
Original file line number Diff line number Diff line change
@@ -1,6 +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<int, PostgreSQLDataType> typeMap = {};
56 changes: 40 additions & 16 deletions lib/src/binary_codec.dart
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,16 @@ class PostgresBinaryEncoder extends Converter<dynamic, Uint8List> {
}
return outBuffer;
}
case PostgreSQLDataType.geometry:
{
if (value is Geometry) {
return castBytes(
utf8.encode(
value.toText(),
),
); ///TODO: Is this neccessary since I've added it to `PostgresTextEncoder`
}
}
}

throw PostgreSQLException('Unsupported datatype');
Expand All @@ -209,6 +219,7 @@ class PostgresBinaryDecoder extends Converter<Uint8List, dynamic> {
const PostgresBinaryDecoder(this.typeCode);

final int typeCode;
// final Map<int,PostgreSQLDataType> typeMap;

@override
dynamic convert(Uint8List value) {
Expand Down Expand Up @@ -277,6 +288,17 @@ class PostgresBinaryDecoder extends Converter<Uint8List, dynamic> {

return buf.toString();
}
case PostgreSQLDataType.geometry:
{
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
Expand All @@ -290,20 +312,22 @@ class PostgresBinaryDecoder extends Converter<Uint8List, dynamic> {
}
}

static final Map<int, PostgreSQLDataType> 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,
};
// static final Map<int, PostgreSQLDataType> 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')`;
// };
}
101 changes: 89 additions & 12 deletions lib/src/connection.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -38,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;
}
Expand Down Expand Up @@ -82,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.
Expand Down Expand Up @@ -112,6 +121,8 @@ class PostgreSQLConnection extends Object
int _secretKey;
List<int> _salt;

final Map<String, int> _extraDataTypes = {};

bool _hasConnectedPreviously = false;
_PostgreSQLConnectionState _connectionState;

Expand All @@ -129,16 +140,22 @@ 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.');
}

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) {
Expand All @@ -163,6 +180,66 @@ class PostgreSQLConnection extends Object

rethrow;
}

if(enablePostGISSupport) {
//CREATE EXTENSION postgis;
try {
await _connection.execute('CREATE EXTENSION IF NOT EXISTS 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(
'''
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');
''',
);

final mapped = dataTypes.map((row) {
final oid = row[0] as int;
final typname = row[1] as String;
return MapEntry<String, int>(typname, oid);
});
_extraDataTypes.addEntries(mapped);
// }

typeMap = _extraDataTypes.map((key, value) {
// add boolean since it's not called bool on pg_types
if (key == 'bool') {
return MapEntry<int, PostgreSQLDataType>(
value, PostgreSQLFormatIdentifier.typeStringToCodeMap['boolean']);
}
return MapEntry<int, PostgreSQLDataType>(
value, PostgreSQLFormatIdentifier.typeStringToCodeMap[key]);
});
}

/// Closes a connection.
Expand Down Expand Up @@ -425,7 +502,7 @@ abstract class _PostgreSQLExecutionContextMixin
}

final query = Query<List<List<dynamic>>>(
fmtString, substitutionValues, _connection, _transaction);
fmtString, substitutionValues, _connection, _transaction, typeMap);
if (allowReuse) {
query.statementIdentifier = _connection._cache.identifierForQuery(query);
}
Expand Down Expand Up @@ -470,7 +547,7 @@ abstract class _PostgreSQLExecutionContextMixin
}

final query = Query<int>(
fmtString, substitutionValues, _connection, _transaction,
fmtString, substitutionValues, _connection, _transaction, typeMap,
onlyReturnAffectedRowCount: true);

return _enqueue(query, timeoutInSeconds: timeoutInSeconds);
Expand Down
6 changes: 6 additions & 0 deletions lib/src/execution_context.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ import 'substituter.dart';
import 'types.dart';

abstract class PostgreSQLExecutionContext {

// final Map<int,PostgreSQLDataType> typeMap;

// PostgreSQLExecutionContext(this.typeMap);


/// Returns this context queue size
int get queueSize;

Expand Down
17 changes: 11 additions & 6 deletions lib/src/query.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,14 @@ class Query<T> {
this.statement,
this.substitutionValues,
this.connection,
this.transaction, {
this.transaction,
this.typeMap,
{
this.onlyReturnAffectedRowCount = false,
});

final bool onlyReturnAffectedRowCount;
final Map<int,PostgreSQLDataType> typeMap;

String statementIdentifier;

Expand Down Expand Up @@ -121,8 +124,7 @@ class Query<T> {
return true;
}

final actualType = PostgresBinaryDecoder
.typeMap[actualParameterTypeCodeIterator.current];
final actualType = typeMap[actualParameterTypeCodeIterator.current];
return actualType == specifiedType;
}).any((v) => v == false);

Expand Down Expand Up @@ -209,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);
Expand Down Expand Up @@ -315,7 +318,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) {
Expand Down
1 change: 1 addition & 0 deletions lib/src/server_messages.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
4 changes: 3 additions & 1 deletion lib/src/substituter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,16 @@ class PostgreSQLFormat {
return 'name';
case PostgreSQLDataType.uuid:
return 'uuid';
case PostgreSQLDataType.geometry:
return 'geometry';
}

return null;
}

static String substitute(String fmtString, Map<String, dynamic> values,
{SQLReplaceIdentifierFunction replace}) {
final converter = PostgresTextEncoder(true);
final converter = PostgresTextEncoder();
values ??= <String, dynamic>{};
replace ??= (spec, index) => converter.convert(values[spec.name]);

Expand Down
Loading