From 1de4f36f9f281deae4e544b1289a1bfac718cee2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Istv=C3=A1n=20So=C3=B3s?= Date: Thu, 14 Mar 2019 22:47:52 +0100 Subject: [PATCH 01/20] Use package:buffer to parse incoming message frames. (#72) --- lib/src/message_window.dart | 119 ++++++------------------------------ pubspec.yaml | 1 + 2 files changed, 21 insertions(+), 99 deletions(-) diff --git a/lib/src/message_window.dart b/lib/src/message_window.dart index e466d2e..130f044 100644 --- a/lib/src/message_window.dart +++ b/lib/src/message_window.dart @@ -1,7 +1,8 @@ import 'dart:collection'; -import 'dart:io'; import 'dart:typed_data'; +import 'package:buffer/buffer.dart'; + import 'server_messages.dart'; class MessageFrame { @@ -22,8 +23,6 @@ class MessageFrame { 116: () => new ParameterDescriptionMessage() }; - int get bytesAvailable => packets.fold(0, (sum, v) => sum + v.lengthInBytes); - List packets = []; bool get hasReadHeader => type != null; int type; int expectedLength; @@ -31,97 +30,6 @@ class MessageFrame { bool get isComplete => data != null || expectedLength == 0; Uint8List data; - ByteData consumeNextBytes(int length) { - if (length == 0) { - return null; - } - - if (bytesAvailable >= length) { - var firstPacket = packets.first; - - // The packet exactly matches the size of the bytes needed, - // remove & return it. - if (firstPacket.lengthInBytes == length) { - packets.removeAt(0); - return firstPacket.buffer - .asByteData(firstPacket.offsetInBytes, firstPacket.lengthInBytes); - } - - if (firstPacket.lengthInBytes > length) { - // We have to split up this packet and remove & return the first portion of it, - // and replace it with the second portion of it. - var remainingOffset = firstPacket.offsetInBytes + length; - var bytesNeeded = - firstPacket.buffer.asByteData(firstPacket.offsetInBytes, length); - var bytesRemaining = firstPacket.buffer - .asUint8List(remainingOffset, firstPacket.lengthInBytes - length); - packets.removeAt(0); - packets.insert(0, bytesRemaining); - - return bytesNeeded; - } - - // Otherwise, the first packet can't fill this message, but we know - // we have enough packets overall to fulfill it. So we can build - // a total buffer by accumulating multiple packets into that buffer. - // Each packet gets removed along the way, except for the last one, - // in which case if it has more bytes available, it gets replaced - // with the remaining bytes. - - var builder = new BytesBuilder(copy: false); - var bytesNeeded = length - builder.length; - while (bytesNeeded > 0) { - var packet = packets.removeAt(0); - var bytesRemaining = packet.lengthInBytes; - - if (bytesRemaining <= bytesNeeded) { - builder.add(packet.buffer - .asUint8List(packet.offsetInBytes, packet.lengthInBytes)); - } else { - builder.add( - packet.buffer.asUint8List(packet.offsetInBytes, bytesNeeded)); - packets.insert( - 0, - packet.buffer - .asUint8List(bytesNeeded, bytesRemaining - bytesNeeded)); - } - - bytesNeeded = length - builder.length; - } - - return new Uint8List.fromList(builder.takeBytes()).buffer.asByteData(); - } - - return null; - } - - int addBytes(Uint8List packet) { - packets.add(packet); - - if (!hasReadHeader) { - ByteData headerBuffer = consumeNextBytes(HeaderByteSize); - if (headerBuffer == null) { - return packet.lengthInBytes; - } - - type = headerBuffer.getUint8(0); - expectedLength = headerBuffer.getUint32(1) - 4; - } - - if (expectedLength == 0) { - return packet.lengthInBytes - bytesAvailable; - } - - var body = consumeNextBytes(expectedLength); - if (body == null) { - return packet.lengthInBytes; - } - - data = body.buffer.asUint8List(body.offsetInBytes, body.lengthInBytes); - - return packet.lengthInBytes - bytesAvailable; - } - ServerMessage get message { var msgMaker = messageTypeMap[type] ?? () => new UnknownMessage()..code = type; @@ -133,21 +41,34 @@ class MessageFrame { } class MessageFramer { + final _reader = new ByteDataReader(); MessageFrame messageInProgress = new MessageFrame(); final messageQueue = new Queue(); void addBytes(Uint8List bytes) { - var offsetIntoBytesRead = 0; + _reader.add(bytes); + + bool evaluateNextMessage = true; + while (evaluateNextMessage) { + evaluateNextMessage = false; + if (!messageInProgress.hasReadHeader && + _reader.remainingLength >= MessageFrame.HeaderByteSize) { + messageInProgress.type = _reader.readUint8(); + messageInProgress.expectedLength = _reader.readUint32() - 4; + } - do { - var byteList = new Uint8List.view(bytes.buffer, offsetIntoBytesRead); - offsetIntoBytesRead += messageInProgress.addBytes(byteList); + if (messageInProgress.hasReadHeader && + messageInProgress.expectedLength > 0 && + _reader.remainingLength >= messageInProgress.expectedLength) { + messageInProgress.data = _reader.read(messageInProgress.expectedLength); + } if (messageInProgress.isComplete) { messageQueue.add(messageInProgress); messageInProgress = new MessageFrame(); + evaluateNextMessage = true; } - } while (offsetIntoBytesRead != bytes.length); + } } bool get hasMessage => messageQueue.isNotEmpty; diff --git a/pubspec.yaml b/pubspec.yaml index c1d9e09..326ae3b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -9,6 +9,7 @@ environment: sdk: ">=2.0.0 <3.0.0" dependencies: + buffer: ^1.0.5 crypto: ^2.0.0 dev_dependencies: From 236c1e01aebf8f929d3c742b9de169cae9aa590d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Istv=C3=A1n=20So=C3=B3s?= Date: Sat, 16 Mar 2019 15:19:53 +0100 Subject: [PATCH 02/20] Update ClientMessage(s) to use package:buffer. (#78) --- lib/src/client_messages.dart | 208 ++++++++++++----------------------- 1 file changed, 70 insertions(+), 138 deletions(-) diff --git a/lib/src/client_messages.dart b/lib/src/client_messages.dart index 85b852f..88f9db8 100644 --- a/lib/src/client_messages.dart +++ b/lib/src/client_messages.dart @@ -2,6 +2,8 @@ import 'utf8_backed_string.dart'; import 'dart:typed_data'; import 'query.dart'; import 'constants.dart'; + +import 'package:buffer/buffer.dart'; import 'package:crypto/crypto.dart'; abstract class ClientMessage { @@ -20,44 +22,27 @@ abstract class ClientMessage { int get length; - int applyStringToBuffer( - UTF8BackedString string, ByteData buffer, int offset) { - var postStringOffset = string.utf8Bytes.fold(offset, (idx, unit) { - buffer.setInt8(idx, unit); - return idx + 1; - }); - - buffer.setInt8(postStringOffset, 0); - return postStringOffset + 1; + void applyStringToBuffer(UTF8BackedString string, ByteDataWriter buffer) { + buffer.write(string.utf8Bytes); + buffer.writeInt8(0); } - int applyBytesToBuffer(List bytes, ByteData buffer, int offset) { - var postStringOffset = bytes.fold(offset, (idx, unit) { - buffer.setInt8(idx, unit); - return idx + 1; - }); - - return postStringOffset; + void applyBytesToBuffer(List bytes, ByteDataWriter buffer) { + buffer.write(bytes); } - int applyToBuffer(ByteData aggregateBuffer, int offsetIntoAggregateBuffer); + void applyToBuffer(ByteDataWriter buffer); Uint8List asBytes() { - var buffer = new ByteData(length); - applyToBuffer(buffer, 0); - return buffer.buffer.asUint8List(); + var buffer = new ByteDataWriter(); + applyToBuffer(buffer); + return buffer.toBytes(); } static Uint8List aggregateBytes(List messages) { - var totalLength = - messages.fold(0, (total, ClientMessage next) => total + next.length); - var buffer = new ByteData(totalLength); - - var offset = 0; - messages.fold( - offset, (inOffset, msg) => msg.applyToBuffer(buffer, inOffset)); - - return buffer.buffer.asUint8List(); + var buffer = new ByteDataWriter(); + messages.forEach((cm) => cm.applyToBuffer(buffer)); + return buffer.toBytes(); } } @@ -86,31 +71,25 @@ class StartupMessage extends ClientMessage { return fixedLength + variableLength; } - int applyToBuffer(ByteData buffer, int offset) { - buffer.setInt32(offset, length); - offset += 4; - buffer.setInt32(offset, ClientMessage.ProtocolVersion); - offset += 4; + void applyToBuffer(ByteDataWriter buffer) { + buffer.writeInt32(length); + buffer.writeInt32(ClientMessage.ProtocolVersion); if (username != null) { - offset = applyBytesToBuffer((UTF8ByteConstants.user), buffer, offset); - offset = applyStringToBuffer(username, buffer, offset); + applyBytesToBuffer((UTF8ByteConstants.user), buffer); + applyStringToBuffer(username, buffer); } - offset = applyBytesToBuffer(UTF8ByteConstants.database, buffer, offset); - offset = applyStringToBuffer(databaseName, buffer, offset); + applyBytesToBuffer(UTF8ByteConstants.database, buffer); + applyStringToBuffer(databaseName, buffer); - offset = - applyBytesToBuffer(UTF8ByteConstants.clientEncoding, buffer, offset); - offset = applyBytesToBuffer(UTF8ByteConstants.utf8, buffer, offset); + applyBytesToBuffer(UTF8ByteConstants.clientEncoding, buffer); + applyBytesToBuffer(UTF8ByteConstants.utf8, buffer); - offset = applyBytesToBuffer(UTF8ByteConstants.timeZone, buffer, offset); - offset = applyStringToBuffer(timeZone, buffer, offset); + applyBytesToBuffer(UTF8ByteConstants.timeZone, buffer); + applyStringToBuffer(timeZone, buffer); - buffer.setInt8(offset, 0); - offset += 1; - - return offset; + buffer.writeInt8(0); } } @@ -129,14 +108,10 @@ class AuthMD5Message extends ClientMessage { return 6 + hashedAuthString.utf8Length; } - int applyToBuffer(ByteData buffer, int offset) { - buffer.setUint8(offset, ClientMessage.PasswordIdentifier); - offset += 1; - buffer.setUint32(offset, length - 1); - offset += 4; - offset = applyStringToBuffer(hashedAuthString, buffer, offset); - - return offset; + void applyToBuffer(ByteDataWriter buffer) { + buffer.writeUint8(ClientMessage.PasswordIdentifier); + buffer.writeUint32(length - 1); + applyStringToBuffer(hashedAuthString, buffer); } } @@ -151,14 +126,10 @@ class QueryMessage extends ClientMessage { return 6 + queryString.utf8Length; } - int applyToBuffer(ByteData buffer, int offset) { - buffer.setUint8(offset, ClientMessage.QueryIdentifier); - offset += 1; - buffer.setUint32(offset, length - 1); - offset += 4; - offset = applyStringToBuffer(queryString, buffer, offset); - - return offset; + void applyToBuffer(ByteDataWriter buffer) { + buffer.writeUint8(ClientMessage.QueryIdentifier); + buffer.writeUint32(length - 1); + applyStringToBuffer(queryString, buffer); } } @@ -175,19 +146,13 @@ class ParseMessage extends ClientMessage { return 9 + statement.utf8Length + statementName.utf8Length; } - int applyToBuffer(ByteData buffer, int offset) { - buffer.setUint8(offset, ClientMessage.ParseIdentifier); - offset += 1; - buffer.setUint32(offset, length - 1); - offset += 4; + void applyToBuffer(ByteDataWriter buffer) { + buffer.writeUint8(ClientMessage.ParseIdentifier); + buffer.writeUint32(length - 1); // Name of prepared statement - offset = applyStringToBuffer(statementName, buffer, offset); - offset = applyStringToBuffer(statement, buffer, offset); // Query string - buffer.setUint16(offset, 0); - // Specifying types - may add this in the future, for now indicating we want the backend to infer. - offset += 2; - - return offset; + applyStringToBuffer(statementName, buffer); + applyStringToBuffer(statement, buffer); // Query string + buffer.writeUint16(0); } } @@ -202,17 +167,11 @@ class DescribeMessage extends ClientMessage { return 7 + statementName.utf8Length; } - int applyToBuffer(ByteData buffer, int offset) { - buffer.setUint8(offset, ClientMessage.DescribeIdentifier); - offset += 1; - buffer.setUint32(offset, length - 1); - offset += 4; - buffer.setUint8(offset, 83); - offset += 1; // Indicate we are referencing a prepared statement - offset = applyStringToBuffer( - statementName, buffer, offset); // Name of prepared statement - - return offset; + void applyToBuffer(ByteDataWriter buffer) { + buffer.writeUint8(ClientMessage.DescribeIdentifier); + buffer.writeUint32(length - 1); + buffer.writeUint8(83); + applyStringToBuffer(statementName, buffer); // Name of prepared statement } } @@ -249,67 +208,48 @@ class BindMessage extends ClientMessage { return _cachedLength; } - int applyToBuffer(ByteData buffer, int offset) { - buffer.setUint8(offset, ClientMessage.BindIdentifier); - offset += 1; - buffer.setUint32(offset, length - 1); - offset += 4; + void applyToBuffer(ByteDataWriter buffer) { + buffer.writeUint8(ClientMessage.BindIdentifier); + buffer.writeUint32(length - 1); // Name of portal - currently unnamed portal. - offset = applyBytesToBuffer([0], buffer, offset); + applyBytesToBuffer([0], buffer); // Name of prepared statement. - offset = applyStringToBuffer(statementName, buffer, offset); + applyStringToBuffer(statementName, buffer); // OK, if we have no specified types at all, we can use 0. If we have all specified types, we can use 1. If we have a mix, we have to individually // call out each type. if (typeSpecCount == parameters.length) { - buffer.setUint16(offset, 1); + buffer.writeUint16(1); // Apply following format code for all parameters by indicating 1 - offset += 2; - buffer.setUint16(offset, ClientMessage.FormatBinary); - offset += 2; // Specify format code for all params is BINARY + buffer.writeUint16(ClientMessage.FormatBinary); } else if (typeSpecCount == 0) { - buffer.setUint16(offset, 1); + buffer.writeUint16(1); // Apply following format code for all parameters by indicating 1 - offset += 2; - buffer.setUint16(offset, ClientMessage.FormatText); - offset += 2; // Specify format code for all params is TEXT + buffer.writeUint16(ClientMessage.FormatText); } else { // Well, we have some text and some binary, so we have to be explicit about each one - buffer.setUint16(offset, parameters.length); - offset += 2; + buffer.writeUint16(parameters.length); parameters.forEach((p) { - buffer.setUint16(offset, + buffer.writeUint16( p.isBinary ? ClientMessage.FormatBinary : ClientMessage.FormatText); - offset += 2; }); } // This must be the number of $n's in the query. - buffer.setUint16(offset, parameters.length); - offset += 2; // Number of parameters specified by query + buffer.writeUint16(parameters.length); parameters.forEach((p) { if (p.bytes == null) { - buffer.setInt32(offset, -1); - offset += 4; + buffer.writeInt32(-1); } else { - buffer.setInt32(offset, p.length); - offset += 4; - - offset = p.bytes.fold(offset, (inOffset, byte) { - buffer.setUint8(inOffset, byte); - return inOffset + 1; - }); + buffer.writeInt32(p.length); + buffer.write(p.bytes); } }); // Result columns - we always want binary for all of them, so specify 1:1. - buffer.setUint16(offset, 1); - offset += 2; // Apply format code for all result values by indicating 1 - buffer.setUint16(offset, 1); - offset += 2; // Specify format code for all result values in binary - - return offset; + buffer.writeUint16(1); + buffer.writeUint16(1); } } @@ -320,16 +260,11 @@ class ExecuteMessage extends ClientMessage { return 10; } - int applyToBuffer(ByteData buffer, int offset) { - buffer.setUint8(offset, ClientMessage.ExecuteIdentifier); - offset += 1; - buffer.setUint32(offset, length - 1); - offset += 4; - offset = applyBytesToBuffer([0], buffer, offset); // Portal name - buffer.setUint32(offset, 0); - offset += 4; // Row limit - - return offset; + void applyToBuffer(ByteDataWriter buffer) { + buffer.writeUint8(ClientMessage.ExecuteIdentifier); + buffer.writeUint32(length - 1); + applyBytesToBuffer([0], buffer); // Portal name + buffer.writeUint32(0); } } @@ -340,11 +275,8 @@ class SyncMessage extends ClientMessage { return 5; } - int applyToBuffer(ByteData buffer, int offset) { - buffer.setUint8(offset, ClientMessage.SyncIdentifier); - offset += 1; - buffer.setUint32(offset, 4); - offset += 4; - return offset; + void applyToBuffer(ByteDataWriter buffer) { + buffer.writeUint8(ClientMessage.SyncIdentifier); + buffer.writeUint32(4); } } From e2a99c07f2fa254244d181095d7e2eec9f816c92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Istv=C3=A1n=20So=C3=B3s?= Date: Wed, 20 Mar 2019 19:34:20 +0100 Subject: [PATCH 03/20] Update CI: run dartfmt, analyzer and test. (#79) * Update CI: run dartfmt, analyzer and test. * running dartfmt --- .travis.yml | 7 +- analysis_options.yaml | 3 +- lib/src/binary_codec.dart | 100 +++++++++----- lib/src/connection.dart | 121 +++++++++++------ lib/src/connection_fsm.dart | 44 ++++--- lib/src/exceptions.dart | 4 +- lib/src/execution_context.dart | 16 ++- lib/src/query.dart | 51 +++++--- lib/src/query_cache.dart | 2 +- lib/src/query_queue.dart | 6 +- lib/src/server_messages.dart | 12 +- lib/src/substituter.dart | 15 ++- lib/src/text_codec.dart | 3 +- lib/src/transaction_proxy.dart | 16 ++- lib/src/types.dart | 2 +- test/connection_test.dart | 231 ++++++++++++++++++++------------- test/decode_test.dart | 28 ++-- test/encoding_test.dart | 140 ++++++++++++-------- test/interpolation_test.dart | 58 +++++---- test/json_test.dart | 133 ++++++++++++++----- test/map_return_test.dart | 43 ++++-- test/notification_test.dart | 54 +++----- test/query_reuse_test.dart | 6 +- test/query_test.dart | 47 +++---- test/timeout_test.dart | 27 +++- test/transaction_test.dart | 55 +++++--- 26 files changed, 774 insertions(+), 450 deletions(-) diff --git a/.travis.yml b/.travis.yml index 172431b..f2e6fdb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,7 +16,12 @@ before_script: - psql -c 'create user darttrust with createdb;' -U postgres - psql -c 'grant all on database dart_test to darttrust;' -U postgres - pub get -script: bash ci/script.sh +#script: bash ci/script.sh +dart_task: + - test: --run-skipped -r expanded -j 1 + - dartfmt + - dartanalyzer: --fatal-infos --fatal-warnings . + #after_success: bash ci/after_script.sh branches: only: diff --git a/analysis_options.yaml b/analysis_options.yaml index 4205311..2c63e94 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -4,7 +4,8 @@ # https://github.com/dart-lang/sdk/tree/master/pkg/analyzer#configuring-the-analyzer analyzer: - strong-mode: true +# strong-mode: +# implicit-casts: false # excludes: # - path/to/excluded/files/** diff --git a/lib/src/binary_codec.dart b/lib/src/binary_codec.dart index cc4c2eb..1b061f5 100644 --- a/lib/src/binary_codec.dart +++ b/lib/src/binary_codec.dart @@ -20,8 +20,8 @@ class PostgresBinaryEncoder extends Converter { case PostgreSQLDataType.boolean: { if (value is! bool) { - throw new FormatException("Invalid type for parameter value. Expected: bool Got: ${value - .runtimeType}"); + throw new FormatException( + "Invalid type for parameter value. Expected: bool Got: ${value.runtimeType}"); } var bd = new ByteData(1); @@ -32,8 +32,8 @@ class PostgresBinaryEncoder extends Converter { case PostgreSQLDataType.bigInteger: { if (value is! int) { - throw new FormatException("Invalid type for parameter value. Expected: int Got: ${value - .runtimeType}"); + throw new FormatException( + "Invalid type for parameter value. Expected: int Got: ${value.runtimeType}"); } var bd = new ByteData(8); @@ -44,8 +44,8 @@ class PostgresBinaryEncoder extends Converter { case PostgreSQLDataType.integer: { if (value is! int) { - throw new FormatException("Invalid type for parameter value. Expected: int Got: ${value - .runtimeType}"); + throw new FormatException( + "Invalid type for parameter value. Expected: int Got: ${value.runtimeType}"); } var bd = new ByteData(4); @@ -55,8 +55,8 @@ class PostgresBinaryEncoder extends Converter { case PostgreSQLDataType.smallInteger: { if (value is! int) { - throw new FormatException("Invalid type for parameter value. Expected: int Got: ${value - .runtimeType}"); + throw new FormatException( + "Invalid type for parameter value. Expected: int Got: ${value.runtimeType}"); } var bd = new ByteData(2); @@ -67,8 +67,8 @@ class PostgresBinaryEncoder extends Converter { case PostgreSQLDataType.text: { if (value is! String) { - throw new FormatException("Invalid type for parameter value. Expected: String Got: ${value - .runtimeType}"); + throw new FormatException( + "Invalid type for parameter value. Expected: String Got: ${value.runtimeType}"); } return utf8.encode(value); @@ -76,8 +76,8 @@ class PostgresBinaryEncoder extends Converter { case PostgreSQLDataType.real: { if (value is! double) { - throw new FormatException("Invalid type for parameter value. Expected: double Got: ${value - .runtimeType}"); + throw new FormatException( + "Invalid type for parameter value. Expected: double Got: ${value.runtimeType}"); } var bd = new ByteData(4); @@ -87,8 +87,8 @@ class PostgresBinaryEncoder extends Converter { case PostgreSQLDataType.double: { if (value is! double) { - throw new FormatException("Invalid type for parameter value. Expected: double Got: ${value - .runtimeType}"); + throw new FormatException( + "Invalid type for parameter value. Expected: double Got: ${value.runtimeType}"); } var bd = new ByteData(8); @@ -98,20 +98,21 @@ class PostgresBinaryEncoder extends Converter { case PostgreSQLDataType.date: { if (value is! DateTime) { - throw new FormatException("Invalid type for parameter value. Expected: DateTime Got: ${value - .runtimeType}"); + throw new FormatException( + "Invalid type for parameter value. Expected: DateTime Got: ${value.runtimeType}"); } var bd = new ByteData(4); - bd.setInt32(0, value.toUtc().difference(new DateTime.utc(2000)).inDays); + bd.setInt32( + 0, value.toUtc().difference(new DateTime.utc(2000)).inDays); return bd.buffer.asUint8List(); } case PostgreSQLDataType.timestampWithoutTimezone: { if (value is! DateTime) { - throw new FormatException("Invalid type for parameter value. Expected: DateTime Got: ${value - .runtimeType}"); + throw new FormatException( + "Invalid type for parameter value. Expected: DateTime Got: ${value.runtimeType}"); } var bd = new ByteData(8); @@ -123,12 +124,13 @@ class PostgresBinaryEncoder extends Converter { case PostgreSQLDataType.timestampWithTimezone: { if (value is! DateTime) { - throw new FormatException("Invalid type for parameter value. Expected: DateTime Got: ${value - .runtimeType}"); + throw new FormatException( + "Invalid type for parameter value. Expected: DateTime Got: ${value.runtimeType}"); } var bd = new ByteData(8); - bd.setInt64(0, value.toUtc().difference(new DateTime.utc(2000)).inMicroseconds); + bd.setInt64(0, + value.toUtc().difference(new DateTime.utc(2000)).inMicroseconds); return bd.buffer.asUint8List(); } @@ -147,8 +149,8 @@ class PostgresBinaryEncoder extends Converter { case PostgreSQLDataType.byteArray: { if (value is! List) { - throw new FormatException("Invalid type for parameter value. Expected: List Got: ${value - .runtimeType}"); + throw new FormatException( + "Invalid type for parameter value. Expected: List Got: ${value.runtimeType}"); } return new Uint8List.fromList(value); } @@ -156,12 +158,16 @@ class PostgresBinaryEncoder extends Converter { case PostgreSQLDataType.uuid: { if (value is! String) { - throw new FormatException("Invalid type for parameter value. Expected: String Got: ${value - .runtimeType}"); + throw new FormatException( + "Invalid type for parameter value. Expected: String Got: ${value.runtimeType}"); } final dashUnit = "-".codeUnits.first; - final hexBytes = (value as String).toLowerCase().codeUnits.where((c) => c != dashUnit).toList(); + final hexBytes = (value as String) + .toLowerCase() + .codeUnits + .where((c) => c != dashUnit) + .toList(); if (hexBytes.length != 32) { throw new FormatException( "Invalid UUID string. There must be exactly 32 hexadecimal (0-9 and a-f) characters and any number of '-' characters."); @@ -174,7 +180,8 @@ class PostgresBinaryEncoder extends Converter { return charCode - 87; } - throw new FormatException("Invalid UUID string. Contains non-hexadecimal character (0-9 and a-f)."); + throw new FormatException( + "Invalid UUID string. Contains non-hexadecimal character (0-9 and a-f)."); }; final outBuffer = new Uint8List(16); @@ -205,12 +212,14 @@ class PostgresBinaryDecoder extends Converter { return null; } - final buffer = new ByteData.view(value.buffer, value.offsetInBytes, value.lengthInBytes); + final buffer = new ByteData.view( + value.buffer, value.offsetInBytes, value.lengthInBytes); switch (dataType) { case PostgreSQLDataType.name: case PostgreSQLDataType.text: - return utf8.decode(value.buffer.asUint8List(value.offsetInBytes, value.lengthInBytes)); + return utf8.decode( + value.buffer.asUint8List(value.offsetInBytes, value.lengthInBytes)); case PostgreSQLDataType.boolean: return buffer.getInt8(0) != 0; case PostgreSQLDataType.smallInteger: @@ -227,26 +236,47 @@ class PostgresBinaryDecoder extends Converter { return buffer.getFloat64(0); case PostgreSQLDataType.timestampWithoutTimezone: case PostgreSQLDataType.timestampWithTimezone: - return new DateTime.utc(2000).add(new Duration(microseconds: buffer.getInt64(0))); + return new DateTime.utc(2000) + .add(new Duration(microseconds: buffer.getInt64(0))); case PostgreSQLDataType.date: - return new DateTime.utc(2000).add(new Duration(days: buffer.getInt32(0))); + return new DateTime.utc(2000) + .add(new Duration(days: buffer.getInt32(0))); case PostgreSQLDataType.json: { // Removes version which is first character and currently always '1' - final bytes = value.buffer.asUint8List(value.offsetInBytes + 1, value.lengthInBytes - 1); + final bytes = value.buffer + .asUint8List(value.offsetInBytes + 1, value.lengthInBytes - 1); return json.decode(utf8.decode(bytes)); } case PostgreSQLDataType.byteArray: - return value.buffer.asUint8List(value.offsetInBytes, value.lengthInBytes); + return value.buffer + .asUint8List(value.offsetInBytes, value.lengthInBytes); case PostgreSQLDataType.uuid: { final codeDash = "-".codeUnitAt(0); - final cipher = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f']; + final cipher = [ + '0', + '1', + '2', + '3', + '4', + '5', + '6', + '7', + '8', + '9', + 'a', + 'b', + 'c', + 'd', + 'e', + 'f' + ]; final byteConvert = (int value) { return cipher[value]; }; diff --git a/lib/src/connection.dart b/lib/src/connection.dart index d6d93c6..72dfe81 100644 --- a/lib/src/connection.dart +++ b/lib/src/connection.dart @@ -24,7 +24,9 @@ part 'exceptions.dart'; /// /// The primary type of this library, a connection is responsible for connecting to databases and executing queries. /// A connection may be opened with [open] after it is created. -class PostgreSQLConnection extends Object with _PostgreSQLExecutionContextMixin implements PostgreSQLExecutionContext { +class PostgreSQLConnection extends Object + with _PostgreSQLExecutionContextMixin + implements PostgreSQLExecutionContext { /// Creates an instance of [PostgreSQLConnection]. /// /// [host] must be a hostname, e.g. "foobar.com" or IP address. Do not include scheme or port. @@ -36,12 +38,18 @@ class PostgreSQLConnection extends Object with _PostgreSQLExecutionContextMixin /// [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: null, this.password: null, this.timeoutInSeconds: 30, this.queryTimeoutInSeconds: 30, this.timeZone: "UTC", this.useSSL: false}) { + {this.username: null, + this.password: null, + this.timeoutInSeconds: 30, + this.queryTimeoutInSeconds: 30, + this.timeZone: "UTC", + this.useSSL: false}) { _connectionState = new _PostgreSQLConnectionStateClosed(); _connectionState.connection = this; } - final StreamController _notifications = new StreamController.broadcast(); + final StreamController _notifications = + new StreamController.broadcast(); /// Hostname of database this connection refers to. final String host; @@ -98,6 +106,7 @@ class PostgreSQLConnection extends Object with _PostgreSQLExecutionContextMixin Socket _socket; MessageFramer _framer = new MessageFramer(); int _processID; + // ignore: unused_field int _secretKey; List _salt; @@ -118,12 +127,14 @@ class PostgreSQLConnection extends Object with _PostgreSQLExecutionContextMixin /// opened and this method is called, an exception will be thrown. Future open() async { if (_hasConnectedPreviously) { - throw new PostgreSQLException("Attempting to reopen a closed connection. Create a new instance instead."); + throw new PostgreSQLException( + "Attempting to reopen a closed connection. Create a new instance instead."); } try { _hasConnectedPreviously = true; - _socket = await Socket.connect(host, port).timeout(new Duration(seconds: timeoutInSeconds)); + _socket = await Socket.connect(host, port) + .timeout(new Duration(seconds: timeoutInSeconds)); _framer = new MessageFramer(); if (useSSL) { @@ -131,13 +142,17 @@ class PostgreSQLConnection extends Object with _PostgreSQLExecutionContextMixin } var connectionComplete = new Completer(); - _socket.listen(_readData, onError: (err, st) => _close(err, st), onDone: () => _close()); + _socket.listen(_readData, + onError: (err, st) => _close(err, st), onDone: () => _close()); - _transitionToState(new _PostgreSQLConnectionStateSocketConnected(connectionComplete)); + _transitionToState( + new _PostgreSQLConnectionStateSocketConnected(connectionComplete)); - await connectionComplete.future.timeout(new Duration(seconds: timeoutInSeconds)); + await connectionComplete.future + .timeout(new Duration(seconds: timeoutInSeconds)); } on TimeoutException catch (e, st) { - final err = new PostgreSQLException("Failed to connect to database $host:$port/$databaseName failed to connect."); + final err = new PostgreSQLException( + "Failed to connect to database $host:$port/$databaseName failed to connect."); await _close(err, st); rethrow; } catch (e, st) { @@ -180,9 +195,12 @@ class PostgreSQLConnection extends Object with _PostgreSQLExecutionContextMixin /// If specified, the final `"COMMIT"` query of the transaction will use /// [commitTimeoutInSeconds] as its timeout, otherwise the connection's /// default query timeout will be used. - Future transaction(Future queryBlock(PostgreSQLExecutionContext connection), {int commitTimeoutInSeconds}) async { + Future transaction( + Future queryBlock(PostgreSQLExecutionContext connection), + {int commitTimeoutInSeconds}) async { if (isClosed) { - throw new PostgreSQLException("Attempting to execute query, but connection is not open."); + throw new PostgreSQLException( + "Attempting to execute query, but connection is not open."); } var proxy = new _TransactionProxy(this, queryBlock, commitTimeoutInSeconds); @@ -234,7 +252,8 @@ class PostgreSQLConnection extends Object with _PostgreSQLExecutionContextMixin if (msg is ErrorResponseMessage) { _transitionToState(_connectionState.onErrorResponse(msg)); } else if (msg is NotificationResponseMessage) { - _notifications.add(new Notification(msg.processID, msg.channel, msg.payload)); + _notifications + .add(new Notification(msg.processID, msg.channel, msg.payload)); } else { _transitionToState(_connectionState.onMessage(msg)); } @@ -250,15 +269,15 @@ class PostgreSQLConnection extends Object with _PostgreSQLExecutionContextMixin originalSocket.listen( (data) { if (data.length != 1) { - sslCompleter.completeError( - new PostgreSQLException("Could not initalize SSL connection, received unknown byte stream.")); + sslCompleter.completeError(new PostgreSQLException( + "Could not initalize SSL connection, received unknown byte stream.")); return; } sslCompleter.complete(data.first); }, - onDone: () => sslCompleter.completeError( - new PostgreSQLException("Could not initialize SSL connection, connection closed during handshake.")), + onDone: () => sslCompleter.completeError(new PostgreSQLException( + "Could not initialize SSL connection, connection closed during handshake.")), onError: (err) { sslCompleter.completeError(err); }); @@ -268,13 +287,16 @@ class PostgreSQLConnection extends Object with _PostgreSQLExecutionContextMixin byteBuffer.setUint32(4, 80877103); originalSocket.add(byteBuffer.buffer.asUint8List()); - return sslCompleter.future.timeout(new Duration(seconds: timeout)).then((responseByte) { + return sslCompleter.future + .timeout(new Duration(seconds: timeout)) + .then((responseByte) { if (responseByte != 83) { - throw new PostgreSQLException("The database server is not accepting SSL connections."); + throw new PostgreSQLException( + "The database server is not accepting SSL connections."); } - return SecureSocket - .secure(originalSocket, onBadCertificate: (certificate) => true) + return SecureSocket.secure(originalSocket, + onBadCertificate: (certificate) => true) .timeout(new Duration(seconds: timeout)); }); } @@ -303,7 +325,8 @@ class Notification { final String payload; } -abstract class _PostgreSQLExecutionContextMixin implements PostgreSQLExecutionContext { +abstract class _PostgreSQLExecutionContextMixin + implements PostgreSQLExecutionContext { Map _tableOIDNameMap = {}; QueryQueue _queue = new QueryQueue(); @@ -314,13 +337,17 @@ abstract class _PostgreSQLExecutionContextMixin implements PostgreSQLExecutionCo int get queueSize => _queue.length; Future>> query(String fmtString, - {Map substitutionValues: null, bool allowReuse: true, int timeoutInSeconds}) async { + {Map substitutionValues: null, + bool allowReuse: true, + int timeoutInSeconds}) async { timeoutInSeconds ??= _connection.queryTimeoutInSeconds; if (_connection.isClosed) { - throw new PostgreSQLException("Attempting to execute query, but connection is not open."); + throw new PostgreSQLException( + "Attempting to execute query, but connection is not open."); } - var query = new Query>>(fmtString, substitutionValues, _connection, _transaction); + var query = new Query>>( + fmtString, substitutionValues, _connection, _transaction); if (allowReuse) { query.statementIdentifier = _connection._cache.identifierForQuery(query); } @@ -328,14 +355,19 @@ abstract class _PostgreSQLExecutionContextMixin implements PostgreSQLExecutionCo return _enqueue(query, timeoutInSeconds: timeoutInSeconds); } - Future>>> mappedResultsQuery(String fmtString, - {Map substitutionValues: null, bool allowReuse: true, int timeoutInSeconds}) async { + Future>>> mappedResultsQuery( + String fmtString, + {Map substitutionValues: null, + bool allowReuse: true, + int timeoutInSeconds}) async { timeoutInSeconds ??= _connection.queryTimeoutInSeconds; if (_connection.isClosed) { - throw new PostgreSQLException("Attempting to execute query, but connection is not open."); + throw new PostgreSQLException( + "Attempting to execute query, but connection is not open."); } - var query = new Query>>(fmtString, substitutionValues, _connection, _transaction); + var query = new Query>>( + fmtString, substitutionValues, _connection, _transaction); if (allowReuse) { query.statementIdentifier = _connection._cache.identifierForQuery(query); } @@ -345,14 +377,17 @@ abstract class _PostgreSQLExecutionContextMixin implements PostgreSQLExecutionCo return _mapifyRows(rows, query.fieldDescriptions); } - Future execute(String fmtString, {Map substitutionValues: null, int timeoutInSeconds}) { + Future execute(String fmtString, + {Map substitutionValues: null, int timeoutInSeconds}) { timeoutInSeconds ??= _connection.queryTimeoutInSeconds; if (_connection.isClosed) { - throw new PostgreSQLException("Attempting to execute query, but connection is not open."); + throw new PostgreSQLException( + "Attempting to execute query, but connection is not open."); } - var query = new Query(fmtString, substitutionValues, _connection, _transaction) - ..onlyReturnAffectedRowCount = true; + var query = + new Query(fmtString, substitutionValues, _connection, _transaction) + ..onlyReturnAffectedRowCount = true; return _enqueue(query, timeoutInSeconds: timeoutInSeconds); } @@ -365,8 +400,10 @@ abstract class _PostgreSQLExecutionContextMixin implements PostgreSQLExecutionCo // It's not a significant impact here, but an area for optimization. This includes // assigning resolvedTableName final tableOIDs = new Set.from(columns.map((f) => f.tableID)); - final List unresolvedTableOIDs = - tableOIDs.where((oid) => oid != null && oid > 0 && !_tableOIDNameMap.containsKey(oid)).toList(); + final List unresolvedTableOIDs = tableOIDs + .where((oid) => + oid != null && oid > 0 && !_tableOIDNameMap.containsKey(oid)) + .toList(); unresolvedTableOIDs.sort((int lhs, int rhs) => lhs.compareTo(rhs)); if (unresolvedTableOIDs.isNotEmpty) { @@ -379,13 +416,16 @@ abstract class _PostgreSQLExecutionContextMixin implements PostgreSQLExecutionCo final tableNames = tableOIDs.map((oid) => _tableOIDNameMap[oid]).toList(); return rows.map((row) { - var rowMap = new Map>.fromIterable(tableNames, - key: (name) => name, value: (_) => {}); + var rowMap = new Map>.fromIterable( + tableNames, + key: (name) => name, + value: (_) => {}); final iterator = columns.iterator; row.forEach((column) { iterator.moveNext(); - rowMap[iterator.current.resolvedTableName][iterator.current.fieldName] = column; + rowMap[iterator.current.resolvedTableName][iterator.current.fieldName] = + column; }); return rowMap; @@ -394,8 +434,8 @@ abstract class _PostgreSQLExecutionContextMixin implements PostgreSQLExecutionCo Future _resolveTableOIDs(List oids) async { final unresolvedIDString = oids.join(","); - final orderedTableNames = - await query("SELECT relname FROM pg_class WHERE relkind='r' AND oid IN ($unresolvedIDString) ORDER BY oid ASC"); + final orderedTableNames = await query( + "SELECT relname FROM pg_class WHERE relkind='r' AND oid IN ($unresolvedIDString) ORDER BY oid ASC"); final iterator = oids.iterator; orderedTableNames.forEach((tableName) { @@ -411,7 +451,8 @@ abstract class _PostgreSQLExecutionContextMixin implements PostgreSQLExecutionCo _connection._transitionToState(_connection._connectionState.awake()); try { - final result = await query.future.timeout(new Duration(seconds: timeoutInSeconds)); + final result = + await query.future.timeout(new Duration(seconds: timeoutInSeconds)); _connection._cache.add(query); _queue.remove(query); return result; diff --git a/lib/src/connection_fsm.dart b/lib/src/connection_fsm.dart index 8d04550..27409ad 100644 --- a/lib/src/connection_fsm.dart +++ b/lib/src/connection_fsm.dart @@ -18,7 +18,8 @@ abstract class _PostgreSQLConnectionState { _PostgreSQLConnectionState onErrorResponse(ErrorResponseMessage message) { var exception = new PostgreSQLException._(message.fields); - if (exception.severity == PostgreSQLSeverity.fatal || exception.severity == PostgreSQLSeverity.panic) { + if (exception.severity == PostgreSQLSeverity.fatal || + exception.severity == PostgreSQLSeverity.panic) { return new _PostgreSQLConnectionStateClosed(); } @@ -38,14 +39,16 @@ class _PostgreSQLConnectionStateClosed extends _PostgreSQLConnectionState {} Socket connected, prior to any PostgreSQL handshaking - initiates that handshaking */ -class _PostgreSQLConnectionStateSocketConnected extends _PostgreSQLConnectionState { +class _PostgreSQLConnectionStateSocketConnected + extends _PostgreSQLConnectionState { _PostgreSQLConnectionStateSocketConnected(this.completer); Completer completer; _PostgreSQLConnectionState onEnter() { - var startupMessage = - new StartupMessage(connection.databaseName, connection.timeZone, username: connection.username); + var startupMessage = new StartupMessage( + connection.databaseName, connection.timeZone, + username: connection.username); connection._socket.add(startupMessage.asBytes()); @@ -72,8 +75,8 @@ class _PostgreSQLConnectionStateSocketConnected extends _PostgreSQLConnectionSta return new _PostgreSQLConnectionStateAuthenticating(completer); } - completer.completeError(new PostgreSQLException("Unsupported authentication type ${authMessage - .type}, closing connection.")); + completer.completeError(new PostgreSQLException( + "Unsupported authentication type ${authMessage.type}, closing connection.")); return new _PostgreSQLConnectionStateClosed(); } @@ -83,13 +86,15 @@ class _PostgreSQLConnectionStateSocketConnected extends _PostgreSQLConnectionSta Authenticating state */ -class _PostgreSQLConnectionStateAuthenticating extends _PostgreSQLConnectionState { +class _PostgreSQLConnectionStateAuthenticating + extends _PostgreSQLConnectionState { _PostgreSQLConnectionStateAuthenticating(this.completer); Completer completer; _PostgreSQLConnectionState onEnter() { - var authMessage = new AuthMD5Message(connection.username, connection.password, connection._salt); + var authMessage = new AuthMD5Message( + connection.username, connection.password, connection._salt); connection._socket.add(authMessage.asBytes()); @@ -124,7 +129,8 @@ class _PostgreSQLConnectionStateAuthenticating extends _PostgreSQLConnectionStat Authenticated state */ -class _PostgreSQLConnectionStateAuthenticated extends _PostgreSQLConnectionState { +class _PostgreSQLConnectionStateAuthenticated + extends _PostgreSQLConnectionState { _PostgreSQLConnectionStateAuthenticated(this.completer); Completer completer; @@ -221,7 +227,8 @@ class _PostgreSQLConnectionStateBusy extends _PostgreSQLConnectionState { var exception = new PostgreSQLException._(message.fields); returningException ??= exception; - if (exception.severity == PostgreSQLSeverity.fatal || exception.severity == PostgreSQLSeverity.panic) { + if (exception.severity == PostgreSQLSeverity.fatal || + exception.severity == PostgreSQLSeverity.panic) { return new _PostgreSQLConnectionStateClosed(); } @@ -232,12 +239,13 @@ class _PostgreSQLConnectionStateBusy extends _PostgreSQLConnectionState { // We ignore NoData, as it doesn't tell us anything we don't already know // or care about. - // print("(${query.statement}) -> $message"); + // print("(${query.statement}) -> $message"); if (message is ReadyForQueryMessage) { if (message.state == ReadyForQueryMessage.StateTransactionError) { query.completeError(returningException); - return new _PostgreSQLConnectionStateReadyInTransaction(query.transaction); + return new _PostgreSQLConnectionStateReadyInTransaction( + query.transaction); } if (returningException != null) { @@ -247,7 +255,8 @@ class _PostgreSQLConnectionStateBusy extends _PostgreSQLConnectionState { } if (message.state == ReadyForQueryMessage.StateTransaction) { - return new _PostgreSQLConnectionStateReadyInTransaction(query.transaction); + return new _PostgreSQLConnectionStateReadyInTransaction( + query.transaction); } return new _PostgreSQLConnectionStateIdle(); @@ -258,7 +267,8 @@ class _PostgreSQLConnectionStateBusy extends _PostgreSQLConnectionState { } else if (message is DataRowMessage) { query.addRow(message.values); } else if (message is ParameterDescriptionMessage) { - var validationException = query.validateParameters(message.parameterTypeIDs); + var validationException = + query.validateParameters(message.parameterTypeIDs); if (validationException != null) { query.cache = null; } @@ -271,7 +281,8 @@ class _PostgreSQLConnectionStateBusy extends _PostgreSQLConnectionState { /* Idle Transaction State */ -class _PostgreSQLConnectionStateReadyInTransaction extends _PostgreSQLConnectionState { +class _PostgreSQLConnectionStateReadyInTransaction + extends _PostgreSQLConnectionState { _PostgreSQLConnectionStateReadyInTransaction(this.transaction); _TransactionProxy transaction; @@ -314,4 +325,5 @@ class _PostgreSQLConnectionStateReadyInTransaction extends _PostgreSQLConnection Hack for deferred error */ -class _PostgreSQLConnectionStateDeferredFailure extends _PostgreSQLConnectionState {} +class _PostgreSQLConnectionStateDeferredFailure + extends _PostgreSQLConnectionState {} diff --git a/lib/src/exceptions.dart b/lib/src/exceptions.dart index 153905f..e0a5bc7 100644 --- a/lib/src/exceptions.dart +++ b/lib/src/exceptions.dart @@ -47,8 +47,8 @@ class PostgreSQLException implements Exception { (ErrorField e) => e.identificationToken == identifer, orElse: () => null)); - severity = ErrorField - .severityFromString(finder(ErrorField.SeverityIdentifier).text); + severity = ErrorField.severityFromString( + finder(ErrorField.SeverityIdentifier).text); code = finder(ErrorField.CodeIdentifier).text; message = finder(ErrorField.MessageIdentifier).text; detail = finder(ErrorField.DetailIdentifier)?.text; diff --git a/lib/src/execution_context.dart b/lib/src/execution_context.dart index 79c5066..3ec8bed 100644 --- a/lib/src/execution_context.dart +++ b/lib/src/execution_context.dart @@ -29,7 +29,9 @@ abstract class PostgreSQLExecutionContext { /// anything to opt in to this behavior, this connection will track the necessary information required to reuse queries without intervention. (The [fmtString] is /// the unique identifier to look up reuse information.) You can disable reuse by passing false for [allowReuse]. Future>> query(String fmtString, - {Map substitutionValues: null, bool allowReuse: true, int timeoutInSeconds}); + {Map substitutionValues: null, + bool allowReuse: true, + int timeoutInSeconds}); /// Executes a query on this context. /// @@ -38,7 +40,8 @@ abstract class PostgreSQLExecutionContext { /// This method returns the number of rows affected and no additional information. This method uses the least efficient and less secure command /// for executing queries in the PostgreSQL protocol; [query] is preferred for queries that will be executed more than once, will contain user input, /// or return rows. - Future execute(String fmtString, {Map substitutionValues: null, int timeoutInSeconds}); + Future execute(String fmtString, + {Map substitutionValues: null, int timeoutInSeconds}); /// Cancels a transaction on this context. /// @@ -76,6 +79,9 @@ abstract class PostgreSQLExecutionContext { /// "company: {"name": "stable|kernel"} /// } /// ] - Future>>> mappedResultsQuery(String fmtString, - {Map substitutionValues: null, bool allowReuse: true, int timeoutInSeconds}); -} \ No newline at end of file + Future>>> mappedResultsQuery( + String fmtString, + {Map substitutionValues: null, + bool allowReuse: true, + int timeoutInSeconds}); +} diff --git a/lib/src/query.dart b/lib/src/query.dart index 1d045f6..d99d455 100644 --- a/lib/src/query.dart +++ b/lib/src/query.dart @@ -13,7 +13,8 @@ import 'substituter.dart'; import 'client_messages.dart'; class Query { - Query(this.statement, this.substitutionValues, this.connection, this.transaction); + Query(this.statement, this.substitutionValues, this.connection, + this.transaction); bool onlyReturnAffectedRowCount = false; @@ -67,7 +68,9 @@ class Query { specifiedParameterTypeCodes = formatIdentifiers.map((i) => i.type).toList(); - var parameterList = formatIdentifiers.map((id) => new ParameterValue(id, substitutionValues)).toList(); + var parameterList = formatIdentifiers + .map((id) => new ParameterValue(id, substitutionValues)) + .toList(); var messages = [ new ParseMessage(sqlString, statementName: statementName), @@ -84,27 +87,34 @@ class Query { socket.add(ClientMessage.aggregateBytes(messages)); } - void sendCachedQuery(Socket socket, CachedQuery cacheQuery, Map substitutionValues) { + void sendCachedQuery(Socket socket, CachedQuery cacheQuery, + Map substitutionValues) { var statementName = cacheQuery.preparedStatementName; - var parameterList = - cacheQuery.orderedParameters.map((identifier) => new ParameterValue(identifier, substitutionValues)).toList(); + var parameterList = cacheQuery.orderedParameters + .map((identifier) => new ParameterValue(identifier, substitutionValues)) + .toList(); - var bytes = ClientMessage.aggregateBytes( - [new BindMessage(parameterList, statementName: statementName), new ExecuteMessage(), new SyncMessage()]); + var bytes = ClientMessage.aggregateBytes([ + new BindMessage(parameterList, statementName: statementName), + new ExecuteMessage(), + new SyncMessage() + ]); socket.add(bytes); } PostgreSQLException validateParameters(List parameterTypeIDs) { var actualParameterTypeCodeIterator = parameterTypeIDs.iterator; - var parametersAreMismatched = specifiedParameterTypeCodes.map((specifiedType) { + var parametersAreMismatched = + specifiedParameterTypeCodes.map((specifiedType) { actualParameterTypeCodeIterator.moveNext(); if (specifiedType == null) { return true; } - final actualType = PostgresBinaryDecoder.typeMap[actualParameterTypeCodeIterator.current]; + final actualType = PostgresBinaryDecoder + .typeMap[actualParameterTypeCodeIterator.current]; return actualType == specifiedType; }).any((v) => v == false); @@ -125,7 +135,8 @@ class Query { var lazyDecodedData = rawRowData.map((bd) { iterator.moveNext(); - return iterator.current.converter.convert(bd?.buffer?.asUint8List(bd.offsetInBytes, bd.lengthInBytes)); + return iterator.current.converter + .convert(bd?.buffer?.asUint8List(bd.offsetInBytes, bd.lengthInBytes)); }); rows.add(lazyDecodedData.toList()); @@ -163,20 +174,25 @@ class CachedQuery { List fieldDescriptions; bool get isValid { - return preparedStatementName != null && orderedParameters != null && fieldDescriptions != null; + return preparedStatementName != null && + orderedParameters != null && + fieldDescriptions != null; } } class ParameterValue { - factory ParameterValue(PostgreSQLFormatIdentifier identifier, Map substitutionValues) { + factory ParameterValue(PostgreSQLFormatIdentifier identifier, + Map substitutionValues) { if (identifier.type == null) { return new ParameterValue.text(substitutionValues[identifier.name]); } - return new ParameterValue.binary(substitutionValues[identifier.name], identifier.type); + return new ParameterValue.binary( + substitutionValues[identifier.name], identifier.type); } - ParameterValue.binary(dynamic value, PostgreSQLDataType postgresType) : isBinary = true { + ParameterValue.binary(dynamic value, PostgreSQLDataType postgresType) + : isBinary = true { final converter = new PostgresBinaryEncoder(postgresType); bytes = converter.convert(value); length = bytes?.length ?? 0; @@ -245,7 +261,8 @@ class FieldDescription { } } -typedef String SQLReplaceIdentifierFunction(PostgreSQLFormatIdentifier identifier, int index); +typedef String SQLReplaceIdentifierFunction( + PostgreSQLFormatIdentifier identifier, int index); enum PostgreSQLFormatTokenType { text, variable } @@ -257,7 +274,6 @@ class PostgreSQLFormatToken { } class PostgreSQLFormatIdentifier { - static Map typeStringToCodeMap = { "text": PostgreSQLDataType.text, "int2": PostgreSQLDataType.smallInteger, @@ -291,7 +307,8 @@ class PostgreSQLFormatIdentifier { if (dataTypeString != null) { type = typeStringToCodeMap[dataTypeString]; if (type == null) { - throw new FormatException("Invalid type code in substitution variable '$t'"); + throw new FormatException( + "Invalid type code in substitution variable '$t'"); } } } else { diff --git a/lib/src/query_cache.dart b/lib/src/query_cache.dart index 7c83680..c7fef2c 100644 --- a/lib/src/query_cache.dart +++ b/lib/src/query_cache.dart @@ -34,4 +34,4 @@ class QueryCache { return string; } -} \ No newline at end of file +} diff --git a/lib/src/query_queue.dart b/lib/src/query_queue.dart index 2135f17..53d12d0 100644 --- a/lib/src/query_queue.dart +++ b/lib/src/query_queue.dart @@ -4,11 +4,13 @@ import 'dart:collection'; import 'package:postgres/postgres.dart'; import 'package:postgres/src/query.dart'; -class QueryQueue extends ListBase> implements List> { +class QueryQueue extends ListBase> + implements List> { List> _inner = []; bool _isCancelled = false; - PostgreSQLException get _cancellationException => new PostgreSQLException("Query cancelled due to the database connection closing."); + PostgreSQLException get _cancellationException => new PostgreSQLException( + "Query cancelled due to the database connection closing."); Query get pending { if (_inner.isEmpty) { diff --git a/lib/src/server_messages.dart b/lib/src/server_messages.dart index b6e5668..a50bcab 100644 --- a/lib/src/server_messages.dart +++ b/lib/src/server_messages.dart @@ -12,7 +12,8 @@ class ErrorResponseMessage implements ServerMessage { List fields = [new ErrorField()]; void readBytes(Uint8List bytes) { - var lastByteRemovedList = new Uint8List.view(bytes.buffer, bytes.offsetInBytes, bytes.length - 1); + var lastByteRemovedList = + new Uint8List.view(bytes.buffer, bytes.offsetInBytes, bytes.length - 1); lastByteRemovedList.forEach((byte) { if (byte != 0) { @@ -58,7 +59,8 @@ class ParameterStatusMessage extends ServerMessage { void readBytes(Uint8List bytes) { name = utf8.decode(bytes.sublist(0, bytes.indexOf(0))); - value = utf8.decode(bytes.sublist(bytes.indexOf(0) + 1, bytes.lastIndexOf(0))); + value = + utf8.decode(bytes.sublist(bytes.indexOf(0) + 1, bytes.lastIndexOf(0))); } } @@ -121,7 +123,8 @@ class DataRowMessage extends ServerMessage { } else if (dataSize == -1) { values.add(null); } else { - var rawBytes = new ByteData.view(bytes.buffer, bytes.offsetInBytes + offset, dataSize); + var rawBytes = new ByteData.view( + bytes.buffer, bytes.offsetInBytes + offset, dataSize); values.add(rawBytes); offset += dataSize; } @@ -140,7 +143,8 @@ class NotificationResponseMessage extends ServerMessage { var view = new ByteData.view(bytes.buffer, bytes.offsetInBytes); processID = view.getUint32(0); channel = utf8.decode(bytes.sublist(4, bytes.indexOf(0, 4))); - payload = utf8.decode(bytes.sublist(bytes.indexOf(0, 4) + 1, bytes.lastIndexOf(0))); + payload = utf8 + .decode(bytes.sublist(bytes.indexOf(0, 4) + 1, bytes.lastIndexOf(0))); } } diff --git a/lib/src/substituter.dart b/lib/src/substituter.dart index 48b34aa..4682fca 100644 --- a/lib/src/substituter.dart +++ b/lib/src/substituter.dart @@ -66,17 +66,20 @@ class PostgreSQLFormat { while (iterator.current != null) { if (currentPtr == null) { if (iterator.current == _AtSignCodeUnit) { - currentPtr = new PostgreSQLFormatToken(PostgreSQLFormatTokenType.variable); + currentPtr = + new PostgreSQLFormatToken(PostgreSQLFormatTokenType.variable); currentPtr.buffer.writeCharCode(iterator.current); items.add(currentPtr); } else { - currentPtr = new PostgreSQLFormatToken(PostgreSQLFormatTokenType.text); + currentPtr = + new PostgreSQLFormatToken(PostgreSQLFormatTokenType.text); currentPtr.buffer.writeCharCode(iterator.current); items.add(currentPtr); } } else if (currentPtr.type == PostgreSQLFormatTokenType.text) { if (iterator.current == _AtSignCodeUnit) { - currentPtr = new PostgreSQLFormatToken(PostgreSQLFormatTokenType.variable); + currentPtr = + new PostgreSQLFormatToken(PostgreSQLFormatTokenType.variable); currentPtr.buffer.writeCharCode(iterator.current); items.add(currentPtr); } else { @@ -98,7 +101,8 @@ class PostgreSQLFormat { } else if (_isIdentifier(iterator.current)) { currentPtr.buffer.writeCharCode(iterator.current); } else { - currentPtr = new PostgreSQLFormatToken(PostgreSQLFormatTokenType.text); + currentPtr = + new PostgreSQLFormatToken(PostgreSQLFormatTokenType.text); currentPtr.buffer.writeCharCode(iterator.current); items.add(currentPtr); } @@ -118,8 +122,7 @@ class PostgreSQLFormat { if (!values.containsKey(identifier.name)) { throw new FormatException( - "Format string specified identifier with name ${identifier - .name}, but key was not present in values. Format string: $fmtString"); + "Format string specified identifier with name ${identifier.name}, but key was not present in values. Format string: $fmtString"); } var val = replace(identifier, idx); diff --git a/lib/src/text_codec.dart b/lib/src/text_codec.dart index 844c15e..92f914b 100644 --- a/lib/src/text_codec.dart +++ b/lib/src/text_codec.dart @@ -124,7 +124,8 @@ class PostgresTextEncoder extends Converter { var timezoneMinuteOffset = value.timeZoneOffset.inMinutes % 60; var hourComponent = timezoneHourOffset.abs().toString().padLeft(2, "0"); - var minuteComponent = timezoneMinuteOffset.abs().toString().padLeft(2, "0"); + var minuteComponent = + timezoneMinuteOffset.abs().toString().padLeft(2, "0"); if (timezoneHourOffset >= 0) { hourComponent = "+${hourComponent}"; diff --git a/lib/src/transaction_proxy.dart b/lib/src/transaction_proxy.dart index e39ad52..a661afe 100644 --- a/lib/src/transaction_proxy.dart +++ b/lib/src/transaction_proxy.dart @@ -1,10 +1,15 @@ part of postgres.connection; -typedef Future _TransactionQuerySignature(PostgreSQLExecutionContext connection); +typedef Future _TransactionQuerySignature( + PostgreSQLExecutionContext connection); -class _TransactionProxy extends Object with _PostgreSQLExecutionContextMixin implements PostgreSQLExecutionContext { - _TransactionProxy(this._connection, this.executionBlock, this.commitTimeoutInSeconds) { - beginQuery = new Query("BEGIN", {}, _connection, this)..onlyReturnAffectedRowCount = true; +class _TransactionProxy extends Object + with _PostgreSQLExecutionContextMixin + implements PostgreSQLExecutionContext { + _TransactionProxy( + this._connection, this.executionBlock, this.commitTimeoutInSeconds) { + beginQuery = new Query("BEGIN", {}, _connection, this) + ..onlyReturnAffectedRowCount = true; beginQuery.future.then(startTransaction).catchError((err, st) { new Future(() { @@ -79,7 +84,8 @@ class _TransactionProxy extends Object with _PostgreSQLExecutionContextMixin imp "that prevented this query from executing."); _queue.cancel(err); - var rollback = new Query("ROLLBACK", {}, _connection, _transaction)..onlyReturnAffectedRowCount = true; + var rollback = new Query("ROLLBACK", {}, _connection, _transaction) + ..onlyReturnAffectedRowCount = true; _queue.addEvenIfCancelled(rollback); _connection._transitionToState(_connection._connectionState.awake()); diff --git a/lib/src/types.dart b/lib/src/types.dart index 44238af..ee76281 100644 --- a/lib/src/types.dart +++ b/lib/src/types.dart @@ -65,4 +65,4 @@ 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 -} \ No newline at end of file +} diff --git a/test/connection_test.dart b/test/connection_test.dart index fef74bc..9e6fd3c 100644 --- a/test/connection_test.dart +++ b/test/connection_test.dart @@ -14,7 +14,8 @@ void main() { }); test("Connect with md5 auth required", () async { - conn = new PostgreSQLConnection("localhost", 5432, "dart_test", username: "dart", password: "dart"); + conn = new PostgreSQLConnection("localhost", 5432, "dart_test", + username: "dart", password: "dart"); await conn.open(); @@ -22,63 +23,67 @@ void main() { }); test("SSL Connect with md5 auth required", () async { - conn = new PostgreSQLConnection("localhost", 5432, "dart_test", username: "dart", password: "dart", useSSL: true); + conn = new PostgreSQLConnection("localhost", 5432, "dart_test", + username: "dart", password: "dart", useSSL: true); await conn.open(); expect(await conn.execute("select 1"), equals(1)); - var socketMirror = reflect(conn) - .type - .declarations - .values - .firstWhere((DeclarationMirror dm) => dm.simpleName.toString().contains("_socket")); - var underlyingSocket = reflect(conn).getField(socketMirror.simpleName).reflectee; + var socketMirror = reflect(conn).type.declarations.values.firstWhere( + (DeclarationMirror dm) => + dm.simpleName.toString().contains("_socket")); + var underlyingSocket = + reflect(conn).getField(socketMirror.simpleName).reflectee; expect(underlyingSocket is SecureSocket, true); }); test("Connect with no auth required", () async { - conn = new PostgreSQLConnection("localhost", 5432, "dart_test", username: "darttrust"); + conn = new PostgreSQLConnection("localhost", 5432, "dart_test", + username: "darttrust"); await conn.open(); expect(await conn.execute("select 1"), equals(1)); }); test("SSL Connect with no auth required", () async { - conn = new PostgreSQLConnection("localhost", 5432, "dart_test", username: "darttrust", useSSL: true); + conn = new PostgreSQLConnection("localhost", 5432, "dart_test", + username: "darttrust", useSSL: true); await conn.open(); expect(await conn.execute("select 1"), equals(1)); }); - test("Closing idle connection succeeds, closes underlying socket", () async { - conn = new PostgreSQLConnection("localhost", 5432, "dart_test", username: "darttrust"); + test("Closing idle connection succeeds, closes underlying socket", + () async { + conn = new PostgreSQLConnection("localhost", 5432, "dart_test", + username: "darttrust"); await conn.open(); await conn.close(); - var socketMirror = reflect(conn) - .type - .declarations - .values - .firstWhere((DeclarationMirror dm) => dm.simpleName.toString().contains("_socket")); - Socket underlyingSocket = reflect(conn).getField(socketMirror.simpleName).reflectee; + var socketMirror = reflect(conn).type.declarations.values.firstWhere( + (DeclarationMirror dm) => + dm.simpleName.toString().contains("_socket")); + Socket underlyingSocket = + reflect(conn).getField(socketMirror.simpleName).reflectee; expect(await underlyingSocket.done, isNotNull); conn = null; }); - test("SSL Closing idle connection succeeds, closes underlying socket", () async { - conn = new PostgreSQLConnection("localhost", 5432, "dart_test", username: "darttrust", useSSL: true); + test("SSL Closing idle connection succeeds, closes underlying socket", + () async { + conn = new PostgreSQLConnection("localhost", 5432, "dart_test", + username: "darttrust", useSSL: true); await conn.open(); await conn.close(); - var socketMirror = reflect(conn) - .type - .declarations - .values - .firstWhere((DeclarationMirror dm) => dm.simpleName.toString().contains("_socket")); - Socket underlyingSocket = reflect(conn).getField(socketMirror.simpleName).reflectee; + var socketMirror = reflect(conn).type.declarations.values.firstWhere( + (DeclarationMirror dm) => + dm.simpleName.toString().contains("_socket")); + Socket underlyingSocket = + reflect(conn).getField(socketMirror.simpleName).reflectee; expect(await underlyingSocket.done, isNotNull); conn = null; @@ -87,7 +92,8 @@ void main() { test( "Closing connection while busy succeeds, queued queries are all accounted for (canceled), closes underlying socket", () async { - conn = new PostgreSQLConnection("localhost", 5432, "dart_test", username: "darttrust"); + conn = new PostgreSQLConnection("localhost", 5432, "dart_test", + username: "darttrust"); await conn.open(); var errors = []; @@ -106,13 +112,15 @@ void main() { await conn.close(); await Future.wait(futures); expect(errors.length, 5); - expect(errors.map((e) => e.message), everyElement(contains("Query cancelled"))); + expect(errors.map((e) => e.message), + everyElement(contains("Query cancelled"))); }); test( "SSL Closing connection while busy succeeds, queued queries are all accounted for (canceled), closes underlying socket", () async { - conn = new PostgreSQLConnection("localhost", 5432, "dart_test", username: "darttrust", useSSL: true); + conn = new PostgreSQLConnection("localhost", 5432, "dart_test", + username: "darttrust", useSSL: true); await conn.open(); var errors = []; @@ -131,7 +139,8 @@ void main() { await conn.close(); await Future.wait(futures); expect(errors.length, 5); - expect(errors.map((e) => e.message), everyElement(contains("Query cancelled"))); + expect(errors.map((e) => e.message), + everyElement(contains("Query cancelled"))); }); }); @@ -139,7 +148,8 @@ void main() { PostgreSQLConnection conn = null; setUp(() async { - conn = new PostgreSQLConnection("localhost", 5432, "dart_test", username: "darttrust"); + conn = new PostgreSQLConnection("localhost", 5432, "dart_test", + username: "darttrust"); await conn.open(); }); @@ -147,7 +157,9 @@ void main() { await conn?.close(); }); - test("Issuing multiple queries and awaiting between each one successfully returns the right value", () async { + test( + "Issuing multiple queries and awaiting between each one successfully returns the right value", + () async { expect( await conn.query("select 1", allowReuse: false), equals([ @@ -175,7 +187,9 @@ void main() { ])); }); - test("Issuing multiple queries without awaiting are returned with appropriate values", () async { + test( + "Issuing multiple queries without awaiting are returned with appropriate values", + () async { var futures = [ conn.query("select 1", allowReuse: false), conn.query("select 2", allowReuse: false), @@ -216,7 +230,8 @@ void main() { }); test("Sending queries to opening connection triggers error", () async { - conn = new PostgreSQLConnection("localhost", 5432, "dart_test", username: "darttrust"); + conn = new PostgreSQLConnection("localhost", 5432, "dart_test", + username: "darttrust"); openFuture = conn.open(); try { @@ -228,7 +243,8 @@ void main() { }); test("SSL Sending queries to opening connection triggers error", () async { - conn = new PostgreSQLConnection("localhost", 5432, "dart_test", username: "darttrust", useSSL: true); + conn = new PostgreSQLConnection("localhost", 5432, "dart_test", + username: "darttrust", useSSL: true); openFuture = conn.open(); try { @@ -239,8 +255,10 @@ void main() { } }); - test("Starting transaction while opening connection triggers error", () async { - conn = new PostgreSQLConnection("localhost", 5432, "dart_test", username: "darttrust"); + test("Starting transaction while opening connection triggers error", + () async { + conn = new PostgreSQLConnection("localhost", 5432, "dart_test", + username: "darttrust"); openFuture = conn.open(); try { @@ -253,8 +271,10 @@ void main() { } }); - test("SSL Starting transaction while opening connection triggers error", () async { - conn = new PostgreSQLConnection("localhost", 5432, "dart_test", username: "darttrust", useSSL: true); + test("SSL Starting transaction while opening connection triggers error", + () async { + conn = new PostgreSQLConnection("localhost", 5432, "dart_test", + username: "darttrust", useSSL: true); openFuture = conn.open(); try { @@ -267,8 +287,10 @@ void main() { } }); - test("Invalid password reports error, conn is closed, disables conn", () async { - conn = new PostgreSQLConnection("localhost", 5432, "dart_test", username: "dart", password: "notdart"); + test("Invalid password reports error, conn is closed, disables conn", + () async { + conn = new PostgreSQLConnection("localhost", 5432, "dart_test", + username: "dart", password: "notdart"); try { await conn.open(); @@ -280,9 +302,10 @@ void main() { await expectConnectionIsInvalid(conn); }); - test("SSL Invalid password reports error, conn is closed, disables conn", () async { - conn = - new PostgreSQLConnection("localhost", 5432, "dart_test", username: "dart", password: "notdart", useSSL: true); + test("SSL Invalid password reports error, conn is closed, disables conn", + () async { + conn = new PostgreSQLConnection("localhost", 5432, "dart_test", + username: "dart", password: "notdart", useSSL: true); try { await conn.open(); @@ -294,8 +317,10 @@ void main() { await expectConnectionIsInvalid(conn); }); - test("A query error maintains connectivity, allows future queries", () async { - conn = new PostgreSQLConnection("localhost", 5432, "dart_test", username: "darttrust"); + test("A query error maintains connectivity, allows future queries", + () async { + conn = new PostgreSQLConnection("localhost", 5432, "dart_test", + username: "darttrust"); await conn.open(); await conn.execute("CREATE TEMPORARY TABLE t (i int unique)"); @@ -310,8 +335,11 @@ void main() { await conn.execute("INSERT INTO t (i) VALUES (2)"); }); - test("A query error maintains connectivity, continues processing pending queries", () async { - conn = new PostgreSQLConnection("localhost", 5432, "dart_test", username: "darttrust"); + test( + "A query error maintains connectivity, continues processing pending queries", + () async { + conn = new PostgreSQLConnection("localhost", 5432, "dart_test", + username: "darttrust"); await conn.open(); await conn.execute("CREATE TEMPORARY TABLE t (i int unique)"); @@ -341,17 +369,19 @@ void main() { ] ]); - var queueMirror = reflect(conn) - .type - .instanceMembers - .values - .firstWhere((DeclarationMirror dm) => dm.simpleName.toString().contains("_queue")); - List queue = reflect(conn).getField(queueMirror.simpleName).reflectee; + var queueMirror = reflect(conn).type.instanceMembers.values.firstWhere( + (DeclarationMirror dm) => + dm.simpleName.toString().contains("_queue")); + List queue = + reflect(conn).getField(queueMirror.simpleName).reflectee; expect(queue, isEmpty); }); - test("A query error maintains connectivity, continues processing pending transactions", () async { - conn = new PostgreSQLConnection("localhost", 5432, "dart_test", username: "darttrust"); + test( + "A query error maintains connectivity, continues processing pending transactions", + () async { + conn = new PostgreSQLConnection("localhost", 5432, "dart_test", + username: "darttrust"); await conn.open(); await conn.execute("CREATE TEMPORARY TABLE t (i int unique)"); @@ -379,8 +409,11 @@ void main() { expect(orderEnsurer, [2, 1, 3, 4]); }); - test("Building query throws error, connection continues processing pending queries", () async { - conn = new PostgreSQLConnection("localhost", 5432, "dart_test", username: "darttrust"); + test( + "Building query throws error, connection continues processing pending queries", + () async { + conn = new PostgreSQLConnection("localhost", 5432, "dart_test", + username: "darttrust"); await conn.open(); // Make some async queries that'll exit the event loop, but then fail on a query that'll die early @@ -403,12 +436,11 @@ void main() { ] ]); - var queueMirror = reflect(conn) - .type - .instanceMembers - .values - .firstWhere((DeclarationMirror dm) => dm.simpleName.toString().contains("_queue")); - List queue = reflect(conn).getField(queueMirror.simpleName).reflectee; + var queueMirror = reflect(conn).type.instanceMembers.values.firstWhere( + (DeclarationMirror dm) => + dm.simpleName.toString().contains("_queue")); + List queue = + reflect(conn).getField(queueMirror.simpleName).reflectee; expect(queue, isEmpty); }); }); @@ -422,7 +454,9 @@ void main() { await socket?.close(); }); - test("Socket fails to connect reports error, disables connection for future use", () async { + test( + "Socket fails to connect reports error, disables connection for future use", + () async { var conn = new PostgreSQLConnection("localhost", 5431, "dart_test"); try { @@ -433,8 +467,11 @@ void main() { await expectConnectionIsInvalid(conn); }); - test("SSL Socket fails to connect reports error, disables connection for future use", () async { - var conn = new PostgreSQLConnection("localhost", 5431, "dart_test", useSSL: true); + test( + "SSL Socket fails to connect reports error, disables connection for future use", + () async { + var conn = new PostgreSQLConnection("localhost", 5431, "dart_test", + useSSL: true); try { await conn.open(); @@ -444,15 +481,19 @@ void main() { await expectConnectionIsInvalid(conn); }); - test("Connection that times out throws appropriate error and cannot be reused", () async { - serverSocket = await ServerSocket.bind(InternetAddress.loopbackIPv4, 5433); + test( + "Connection that times out throws appropriate error and cannot be reused", + () async { + serverSocket = + await ServerSocket.bind(InternetAddress.loopbackIPv4, 5433); serverSocket.listen((s) { socket = s; // Don't respond on purpose s.listen((bytes) {}); }); - var conn = new PostgreSQLConnection("localhost", 5433, "dart_test", timeoutInSeconds: 2); + var conn = new PostgreSQLConnection("localhost", 5433, "dart_test", + timeoutInSeconds: 2); try { await conn.open(); @@ -462,15 +503,19 @@ void main() { await expectConnectionIsInvalid(conn); }); - test("SSL Connection that times out throws appropriate error and cannot be reused", () async { - serverSocket = await ServerSocket.bind(InternetAddress.loopbackIPv4, 5433); + test( + "SSL Connection that times out throws appropriate error and cannot be reused", + () async { + serverSocket = + await ServerSocket.bind(InternetAddress.loopbackIPv4, 5433); serverSocket.listen((s) { socket = s; // Don't respond on purpose s.listen((bytes) {}); }); - var conn = new PostgreSQLConnection("localhost", 5433, "dart_test", timeoutInSeconds: 2, useSSL: true); + var conn = new PostgreSQLConnection("localhost", 5433, "dart_test", + timeoutInSeconds: 2, useSSL: true); try { await conn.open(); @@ -480,9 +525,11 @@ void main() { await expectConnectionIsInvalid(conn); }); - test("Connection that times out triggers future for pending queries", () async { + test("Connection that times out triggers future for pending queries", + () async { var openCompleter = new Completer(); - serverSocket = await ServerSocket.bind(InternetAddress.loopbackIPv4, 5433); + serverSocket = + await ServerSocket.bind(InternetAddress.loopbackIPv4, 5433); serverSocket.listen((s) { socket = s; // Don't respond on purpose @@ -492,7 +539,8 @@ void main() { }); }); - var conn = new PostgreSQLConnection("localhost", 5433, "dart_test", timeoutInSeconds: 2); + var conn = new PostgreSQLConnection("localhost", 5433, "dart_test", + timeoutInSeconds: 2); conn.open().catchError((e) {}); await openCompleter.future; @@ -505,9 +553,11 @@ void main() { } }); - test("SSL Connection that times out triggers future for pending queries", () async { + test("SSL Connection that times out triggers future for pending queries", + () async { var openCompleter = new Completer(); - serverSocket = await ServerSocket.bind(InternetAddress.loopbackIPv4, 5433); + serverSocket = + await ServerSocket.bind(InternetAddress.loopbackIPv4, 5433); serverSocket.listen((s) { socket = s; // Don't respond on purpose @@ -517,8 +567,11 @@ void main() { }); }); - var conn = new PostgreSQLConnection("localhost", 5433, "dart_test", timeoutInSeconds: 2, useSSL: true); - conn.open().catchError((e) { return null;}); + var conn = new PostgreSQLConnection("localhost", 5433, "dart_test", + timeoutInSeconds: 2, useSSL: true); + conn.open().catchError((e) { + return null; + }); await openCompleter.future; @@ -537,7 +590,8 @@ void main() { }); test("If connection is closed, do not allow .execute", () async { - final conn = new PostgreSQLConnection("localhost", 5432, "dart_test", username: "dart", password: "dart"); + final conn = new PostgreSQLConnection("localhost", 5432, "dart_test", + username: "dart", password: "dart"); try { await conn.execute("SELECT 1"); fail('unreachable'); @@ -547,29 +601,32 @@ void main() { }); test("If connection is closed, do not allow .query", () async { - final conn = new PostgreSQLConnection("localhost", 5432, "dart_test", username: "dart", password: "dart"); + final conn = new PostgreSQLConnection("localhost", 5432, "dart_test", + username: "dart", password: "dart"); try { await conn.query("SELECT 1"); fail('unreachable'); } on PostgreSQLException catch (e) { expect(e.toString(), contains("connection is not open")); } - }); test("If connection is closed, do not allow .mappedResultsQuery", () async { - final conn = new PostgreSQLConnection("localhost", 5432, "dart_test", username: "dart", password: "dart"); + final conn = new PostgreSQLConnection("localhost", 5432, "dart_test", + username: "dart", password: "dart"); try { await conn.mappedResultsQuery("SELECT 1"); fail('unreachable'); } on PostgreSQLException catch (e) { expect(e.toString(), contains("connection is not open")); } - }); - test("Queue size, should be 0 on open, >0 if queries added and 0 again after queries executed", () async { - final conn = new PostgreSQLConnection("localhost", 5432, "dart_test", username: "dart", password: "dart"); + test( + "Queue size, should be 0 on open, >0 if queries added and 0 again after queries executed", + () async { + final conn = new PostgreSQLConnection("localhost", 5432, "dart_test", + username: "dart", password: "dart"); await conn.open(); expect(conn.queueSize, 0); diff --git a/test/decode_test.dart b/test/decode_test.dart index 2da41c1..7d255fc 100644 --- a/test/decode_test.dart +++ b/test/decode_test.dart @@ -4,7 +4,8 @@ import 'package:test/test.dart'; void main() { PostgreSQLConnection connection; setUp(() async { - connection = new PostgreSQLConnection("localhost", 5432, "dart_test", username: "dart", password: "dart"); + connection = new PostgreSQLConnection("localhost", 5432, "dart_test", + username: "dart", password: "dart"); await connection.open(); await connection.execute(""" @@ -14,19 +15,22 @@ void main() { u uuid) """); - await connection.execute("INSERT INTO t (i, bi, bl, si, t, f, d, dt, ts, tsz, j, ba, u) " + await connection.execute( + "INSERT INTO t (i, bi, bl, si, t, f, d, dt, ts, tsz, j, ba, u) " "VALUES (-2147483648, -9223372036854775808, TRUE, -32768, " "'string', 10.0, 10.0, '1983-11-06', " "'1983-11-06 06:00:00.000000', '1983-11-06 06:00:00.000000', " "'{\"key\":\"value\"}', E'\\\\000', '00000000-0000-0000-0000-000000000000')"); - await connection.execute("INSERT INTO t (i, bi, bl, si, t, f, d, dt, ts, tsz, j, ba, u) " + await connection.execute( + "INSERT INTO t (i, bi, bl, si, t, f, d, dt, ts, tsz, j, ba, u) " "VALUES (2147483647, 9223372036854775807, FALSE, 32767, " "'a significantly longer string to the point where i doubt this actually matters', " "10.25, 10.125, '2183-11-06', '2183-11-06 00:00:00.111111', " "'2183-11-06 00:00:00.999999', " "'[{\"key\":1}]', E'\\\\377', 'FFFFFFFF-ffff-ffff-ffff-ffffffffffff')"); - await connection.execute("INSERT INTO t (i, bi, bl, si, t, f, d, dt, ts, tsz, j, ba, u) " + await connection.execute( + "INSERT INTO t (i, bi, bl, si, t, f, d, dt, ts, tsz, j, ba, u) " "VALUES (null, null, null, null, null, null, null, null, null, null, null, null, null)"); }); tearDown(() async { @@ -66,7 +70,10 @@ void main() { expect(row2[3], equals(2)); expect(row2[4], equals(false)); expect(row2[5], equals(32767)); - expect(row2[6], equals("a significantly longer string to the point where i doubt this actually matters")); + expect( + row2[6], + equals( + "a significantly longer string to the point where i doubt this actually matters")); expect(row2[7] is double, true); expect(row2[7], equals(10.25)); expect(row2[8] is double, true); @@ -82,7 +89,6 @@ void main() { expect(row2[13], equals([255])); expect(row2[14], equals("ffffffff-ffff-ffff-ffff-ffffffffffff")); - // all null row expect(row3[0], isNull); expect(row3[1], equals(3)); @@ -103,8 +109,9 @@ void main() { test("Fetch/insert empty string", () async { await connection.execute("CREATE TEMPORARY TABLE u (t text)"); - var results = - await connection.query("INSERT INTO u (t) VALUES (@t:text) returning t", substitutionValues: {"t": ""}); + var results = await connection.query( + "INSERT INTO u (t) VALUES (@t:text) returning t", + substitutionValues: {"t": ""}); expect(results, [ [""] ]); @@ -117,8 +124,9 @@ void main() { test("Fetch/insert null value", () async { await connection.execute("CREATE TEMPORARY TABLE u (t text)"); - var results = - await connection.query("INSERT INTO u (t) VALUES (@t:text) returning t", substitutionValues: {"t": null}); + var results = await connection.query( + "INSERT INTO u (t) VALUES (@t:text) returning t", + substitutionValues: {"t": null}); expect(results, [ [null] ]); diff --git a/test/encoding_test.dart b/test/encoding_test.dart index 03367cf..d736d12 100644 --- a/test/encoding_test.dart +++ b/test/encoding_test.dart @@ -14,7 +14,8 @@ PostgreSQLConnection conn; void main() { group("Binary encoders", () { setUp(() async { - conn = new PostgreSQLConnection("localhost", 5432, "dart_test", username: "dart", password: "dart"); + conn = new PostgreSQLConnection("localhost", 5432, "dart_test", + username: "dart", password: "dart"); await conn.open(); }); @@ -31,7 +32,8 @@ void main() { await expectInverse(true, PostgreSQLDataType.boolean); await expectInverse(false, PostgreSQLDataType.boolean); try { - await conn.query("INSERT INTO t (v) VALUES (@v:boolean)", substitutionValues: {"v": "not-bool"}); + await conn.query("INSERT INTO t (v) VALUES (@v:boolean)", + substitutionValues: {"v": "not-bool"}); fail('unreachable'); } on FormatException catch (e) { expect(e.toString(), contains("Expected: bool")); @@ -43,7 +45,8 @@ void main() { await expectInverse(0, PostgreSQLDataType.smallInteger); await expectInverse(1, PostgreSQLDataType.smallInteger); try { - await conn.query("INSERT INTO t (v) VALUES (@v:int2)", substitutionValues: {"v": "not-int2"}); + await conn.query("INSERT INTO t (v) VALUES (@v:int2)", + substitutionValues: {"v": "not-int2"}); fail('unreachable'); } on FormatException catch (e) { expect(e.toString(), contains("Expected: int")); @@ -55,7 +58,8 @@ void main() { await expectInverse(0, PostgreSQLDataType.integer); await expectInverse(1, PostgreSQLDataType.integer); try { - await conn.query("INSERT INTO t (v) VALUES (@v:int4)", substitutionValues: {"v": "not-int4"}); + await conn.query("INSERT INTO t (v) VALUES (@v:int4)", + substitutionValues: {"v": "not-int4"}); fail('unreachable'); } on FormatException catch (e) { expect(e.toString(), contains("Expected: int")); @@ -66,12 +70,12 @@ void main() { await expectInverse(0, PostgreSQLDataType.serial); await expectInverse(1, PostgreSQLDataType.serial); try { - await conn.query("INSERT INTO t (v) VALUES (@v:int4)", substitutionValues: {"v": "not-serial"}); + await conn.query("INSERT INTO t (v) VALUES (@v:int4)", + substitutionValues: {"v": "not-serial"}); fail('unreachable'); } on FormatException catch (e) { expect(e.toString(), contains("Expected: int")); } - }); test("bigint", () async { @@ -79,24 +83,24 @@ void main() { await expectInverse(0, PostgreSQLDataType.bigInteger); await expectInverse(1, PostgreSQLDataType.bigInteger); try { - await conn.query("INSERT INTO t (v) VALUES (@v:int8)", substitutionValues: {"v": "not-int8"}); + await conn.query("INSERT INTO t (v) VALUES (@v:int8)", + substitutionValues: {"v": "not-int8"}); fail('unreachable'); } on FormatException catch (e) { expect(e.toString(), contains("Expected: int")); } - }); test("bigserial", () async { await expectInverse(0, PostgreSQLDataType.bigSerial); await expectInverse(1, PostgreSQLDataType.bigSerial); try { - await conn.query("INSERT INTO t (v) VALUES (@v:int8)", substitutionValues: {"v": "not-bigserial"}); + await conn.query("INSERT INTO t (v) VALUES (@v:int8)", + substitutionValues: {"v": "not-bigserial"}); fail('unreachable'); } on FormatException catch (e) { expect(e.toString(), contains("Expected: int")); } - }); test("text", () async { @@ -105,7 +109,8 @@ void main() { await expectInverse("foo\n", PostgreSQLDataType.text); await expectInverse("foo\nbar;s", PostgreSQLDataType.text); try { - await conn.query("INSERT INTO t (v) VALUES (@v:text)", substitutionValues: {"v": 0}); + await conn.query("INSERT INTO t (v) VALUES (@v:text)", + substitutionValues: {"v": 0}); fail('unreachable'); } on FormatException catch (e) { expect(e.toString(), contains("Expected: String")); @@ -117,7 +122,8 @@ void main() { await expectInverse(0.0, PostgreSQLDataType.real); await expectInverse(1.0, PostgreSQLDataType.real); try { - await conn.query("INSERT INTO t (v) VALUES (@v:float4)", substitutionValues: {"v": "not-real"}); + await conn.query("INSERT INTO t (v) VALUES (@v:float4)", + substitutionValues: {"v": "not-real"}); fail('unreachable'); } on FormatException catch (e) { expect(e.toString(), contains("Expected: double")); @@ -129,7 +135,8 @@ void main() { await expectInverse(0.0, PostgreSQLDataType.double); await expectInverse(1.0, PostgreSQLDataType.double); try { - await conn.query("INSERT INTO t (v) VALUES (@v:float8)", substitutionValues: {"v": "not-double"}); + await conn.query("INSERT INTO t (v) VALUES (@v:float8)", + substitutionValues: {"v": "not-double"}); fail('unreachable'); } on FormatException catch (e) { expect(e.toString(), contains("Expected: double")); @@ -137,11 +144,15 @@ void main() { }); test("date", () async { - await expectInverse(new DateTime.utc(1920, 10, 1), PostgreSQLDataType.date); - await expectInverse(new DateTime.utc(2120, 10, 5), PostgreSQLDataType.date); - await expectInverse(new DateTime.utc(2016, 10, 1), PostgreSQLDataType.date); + await expectInverse( + new DateTime.utc(1920, 10, 1), PostgreSQLDataType.date); + await expectInverse( + new DateTime.utc(2120, 10, 5), PostgreSQLDataType.date); + await expectInverse( + new DateTime.utc(2016, 10, 1), PostgreSQLDataType.date); try { - await conn.query("INSERT INTO t (v) VALUES (@v:date)", substitutionValues: {"v": "not-date"}); + await conn.query("INSERT INTO t (v) VALUES (@v:date)", + substitutionValues: {"v": "not-date"}); fail('unreachable'); } on FormatException catch (e) { expect(e.toString(), contains("Expected: DateTime")); @@ -149,10 +160,13 @@ void main() { }); test("timestamp", () async { - await expectInverse(new DateTime.utc(1920, 10, 1), PostgreSQLDataType.timestampWithoutTimezone); - await expectInverse(new DateTime.utc(2120, 10, 5), PostgreSQLDataType.timestampWithoutTimezone); + await expectInverse(new DateTime.utc(1920, 10, 1), + PostgreSQLDataType.timestampWithoutTimezone); + await expectInverse(new DateTime.utc(2120, 10, 5), + PostgreSQLDataType.timestampWithoutTimezone); try { - await conn.query("INSERT INTO t (v) VALUES (@v:timestamp)", substitutionValues: {"v": "not-timestamp"}); + await conn.query("INSERT INTO t (v) VALUES (@v:timestamp)", + substitutionValues: {"v": "not-timestamp"}); fail('unreachable'); } on FormatException catch (e) { expect(e.toString(), contains("Expected: DateTime")); @@ -160,10 +174,13 @@ void main() { }); test("timestamptz", () async { - await expectInverse(new DateTime.utc(1920, 10, 1), PostgreSQLDataType.timestampWithTimezone); - await expectInverse(new DateTime.utc(2120, 10, 5), PostgreSQLDataType.timestampWithTimezone); + await expectInverse(new DateTime.utc(1920, 10, 1), + PostgreSQLDataType.timestampWithTimezone); + await expectInverse(new DateTime.utc(2120, 10, 5), + PostgreSQLDataType.timestampWithTimezone); try { - await conn.query("INSERT INTO t (v) VALUES (@v:timestamptz)", substitutionValues: {"v": "not-timestamptz"}); + await conn.query("INSERT INTO t (v) VALUES (@v:timestamptz)", + substitutionValues: {"v": "not-timestamptz"}); fail('unreachable'); } on FormatException catch (e) { expect(e.toString(), contains("Expected: DateTime")); @@ -181,18 +198,20 @@ void main() { }, PostgreSQLDataType.json); try { - await conn.query("INSERT INTO t (v) VALUES (@v:jsonb)", substitutionValues: {"v": new DateTime.now()}); + await conn.query("INSERT INTO t (v) VALUES (@v:jsonb)", + substitutionValues: {"v": new DateTime.now()}); fail('unreachable'); } on JsonUnsupportedObjectError catch (_) {} }); test("bytea", () async { await expectInverse([0], PostgreSQLDataType.byteArray); - await expectInverse([1,2,3,4,5], PostgreSQLDataType.byteArray); + await expectInverse([1, 2, 3, 4, 5], PostgreSQLDataType.byteArray); await expectInverse([255, 254, 253], PostgreSQLDataType.byteArray); try { - await conn.query("INSERT INTO t (v) VALUES (@v:bytea)", substitutionValues: {"v": new DateTime.now()}); + await conn.query("INSERT INTO t (v) VALUES (@v:bytea)", + substitutionValues: {"v": new DateTime.now()}); fail('unreachable'); } on FormatException catch (e) { expect(e.toString(), contains("Expected: List")); @@ -200,11 +219,14 @@ void main() { }); test("uuid", () async { - await expectInverse("00000000-0000-0000-0000-000000000000", PostgreSQLDataType.uuid); - await expectInverse("12345678-abcd-efab-cdef-012345678901", PostgreSQLDataType.uuid); + await expectInverse( + "00000000-0000-0000-0000-000000000000", PostgreSQLDataType.uuid); + await expectInverse( + "12345678-abcd-efab-cdef-012345678901", PostgreSQLDataType.uuid); try { - await conn.query("INSERT INTO t (v) VALUES (@v:uuid)", substitutionValues: {"v": new DateTime.now()}); + await conn.query("INSERT INTO t (v) VALUES (@v:uuid)", + substitutionValues: {"v": new DateTime.now()}); fail('unreachable'); } on FormatException catch (e) { expect(e.toString(), contains("Expected: String")); @@ -216,52 +238,63 @@ void main() { test("Escape strings", () { final encoder = new PostgresTextEncoder(true); // ' b o b ' - expect(utf8.encode(encoder.convert('bob')), equals([39, 98, 111, 98, 39])); + expect( + utf8.encode(encoder.convert('bob')), equals([39, 98, 111, 98, 39])); // ' b o \n b ' - expect(utf8.encode(encoder.convert('bo\nb')), equals([39, 98, 111, 10, 98, 39])); + expect(utf8.encode(encoder.convert('bo\nb')), + equals([39, 98, 111, 10, 98, 39])); // ' b o \r b ' - expect(utf8.encode(encoder.convert('bo\rb')), equals([39, 98, 111, 13, 98, 39])); + expect(utf8.encode(encoder.convert('bo\rb')), + equals([39, 98, 111, 13, 98, 39])); // ' b o \b b ' - expect(utf8.encode(encoder.convert('bo\bb')), equals([39, 98, 111, 8, 98, 39])); + expect(utf8.encode(encoder.convert('bo\bb')), + equals([39, 98, 111, 8, 98, 39])); // ' ' ' ' expect(utf8.encode(encoder.convert("'")), equals([39, 39, 39, 39])); // ' ' ' ' ' ' - expect(utf8.encode(encoder.convert("''")), equals([39, 39, 39, 39, 39, 39])); + expect( + utf8.encode(encoder.convert("''")), equals([39, 39, 39, 39, 39, 39])); // ' ' ' ' ' ' - expect(utf8.encode(encoder.convert("\''")), equals([39, 39, 39, 39, 39, 39])); + expect(utf8.encode(encoder.convert("\''")), + equals([39, 39, 39, 39, 39, 39])); // sp E ' \ \ ' ' ' ' ' - expect(utf8.encode(encoder.convert("\\''")), equals([32, 69, 39, 92, 92, 39, 39, 39, 39, 39])); + expect(utf8.encode(encoder.convert("\\''")), + equals([32, 69, 39, 92, 92, 39, 39, 39, 39, 39])); // sp E ' \ \ ' ' ' - expect(utf8.encode(encoder.convert("\\'")), equals([32, 69, 39, 92, 92, 39, 39, 39])); + expect(utf8.encode(encoder.convert("\\'")), + equals([32, 69, 39, 92, 92, 39, 39, 39])); }); test("Encode DateTime", () { // Get users current timezone var tz = new DateTime(2001, 2, 3).timeZoneOffset; var tzOffsetDelimiter = "${tz.isNegative ? '-' : '+'}" - "${tz - .abs() - .inHours - .toString() - .padLeft(2, '0')}" + "${tz.abs().inHours.toString().padLeft(2, '0')}" ":${(tz.inSeconds % 60).toString().padLeft(2, '0')}"; var pairs = { - "2001-02-03T00:00:00.000$tzOffsetDelimiter": new DateTime(2001, DateTime.february, 3), - "2001-02-03T04:05:06.000$tzOffsetDelimiter": new DateTime(2001, DateTime.february, 3, 4, 5, 6, 0), - "2001-02-03T04:05:06.999$tzOffsetDelimiter": new DateTime(2001, DateTime.february, 3, 4, 5, 6, 999), - "0010-02-03T04:05:06.123$tzOffsetDelimiter BC": new DateTime(-10, DateTime.february, 3, 4, 5, 6, 123), - "0010-02-03T04:05:06.000$tzOffsetDelimiter BC": new DateTime(-10, DateTime.february, 3, 4, 5, 6, 0), - "012345-02-03T04:05:06.000$tzOffsetDelimiter BC": new DateTime(-12345, DateTime.february, 3, 4, 5, 6, 0), - "012345-02-03T04:05:06.000$tzOffsetDelimiter": new DateTime(12345, DateTime.february, 3, 4, 5, 6, 0) + "2001-02-03T00:00:00.000$tzOffsetDelimiter": + new DateTime(2001, DateTime.february, 3), + "2001-02-03T04:05:06.000$tzOffsetDelimiter": + new DateTime(2001, DateTime.february, 3, 4, 5, 6, 0), + "2001-02-03T04:05:06.999$tzOffsetDelimiter": + new DateTime(2001, DateTime.february, 3, 4, 5, 6, 999), + "0010-02-03T04:05:06.123$tzOffsetDelimiter BC": + new DateTime(-10, DateTime.february, 3, 4, 5, 6, 123), + "0010-02-03T04:05:06.000$tzOffsetDelimiter BC": + new DateTime(-10, DateTime.february, 3, 4, 5, 6, 0), + "012345-02-03T04:05:06.000$tzOffsetDelimiter BC": + new DateTime(-12345, DateTime.february, 3, 4, 5, 6, 0), + "012345-02-03T04:05:06.000$tzOffsetDelimiter": + new DateTime(12345, DateTime.february, 3, 4, 5, 6, 0) }; final encoder = new PostgresTextEncoder(false); @@ -321,7 +354,8 @@ void main() { }); }); - test("UTF8String caches string regardless of which method is called first", () { + test("UTF8String caches string regardless of which method is called first", + () { var u = new UTF8BackedString("abcd"); var v = new UTF8BackedString("abcd"); @@ -368,9 +402,9 @@ Future expectInverse(dynamic value, PostgreSQLDataType dataType) async { final type = PostgreSQLFormat.dataTypeStringForDataType(dataType); await conn.execute("CREATE TEMPORARY TABLE IF NOT EXISTS t (v $type)"); - final result = await conn.query("INSERT INTO t (v) VALUES (${PostgreSQLFormat.id("v", type: dataType)}) RETURNING v", substitutionValues: { - "v": value - }); + final result = await conn.query( + "INSERT INTO t (v) VALUES (${PostgreSQLFormat.id("v", type: dataType)}) RETURNING v", + substitutionValues: {"v": value}); expect(result.first.first, equals(value)); final encoder = new PostgresBinaryEncoder(dataType); diff --git a/test/interpolation_test.dart b/test/interpolation_test.dart index 6203950..810a528 100644 --- a/test/interpolation_test.dart +++ b/test/interpolation_test.dart @@ -4,22 +4,30 @@ import 'package:postgres/postgres.dart'; import 'package:postgres/src/query.dart'; import 'package:test/test.dart'; - void main() { test("Ensure all types/format type mappings are available and accurate", () { - PostgreSQLDataType.values.where((t) => t != PostgreSQLDataType.bigSerial && t != PostgreSQLDataType.serial).forEach((t) { - expect(PostgreSQLFormatIdentifier.typeStringToCodeMap.values.contains(t), true); - final code = PostgreSQLFormat.dataTypeStringForDataType(t); + PostgreSQLDataType.values + .where((t) => + t != PostgreSQLDataType.bigSerial && t != PostgreSQLDataType.serial) + .forEach((t) { + expect(PostgreSQLFormatIdentifier.typeStringToCodeMap.values.contains(t), + true); + final code = PostgreSQLFormat.dataTypeStringForDataType(t); expect(PostgreSQLFormatIdentifier.typeStringToCodeMap[code], t); }); }); test("Ensure bigserial gets translated to int8", () { - expect(PostgreSQLFormat.dataTypeStringForDataType(PostgreSQLDataType.serial), "int4"); + expect( + PostgreSQLFormat.dataTypeStringForDataType(PostgreSQLDataType.serial), + "int4"); }); test("Ensure serial gets translated to int4", () { - expect(PostgreSQLFormat.dataTypeStringForDataType(PostgreSQLDataType.bigSerial), "int8"); + expect( + PostgreSQLFormat.dataTypeStringForDataType( + PostgreSQLDataType.bigSerial), + "int8"); }); test("Simple replacement", () { @@ -48,56 +56,52 @@ void main() { }); test("Identifiers next to eachother with type info", () { - var result = PostgreSQLFormat - .substitute("@id:int2@foo:float4", {"id": 12, "foo": 2.0}); + var result = PostgreSQLFormat.substitute( + "@id:int2@foo:float4", {"id": 12, "foo": 2.0}); expect(result, equals("122.0")); }); test("Disambiguate PostgreSQL typecast", () { - var result = PostgreSQLFormat - .substitute("@id::jsonb", {"id": "12"}); + var result = PostgreSQLFormat.substitute("@id::jsonb", {"id": "12"}); expect(result, "'12'::jsonb"); }); test("PostgreSQL typecast appears in query", () { - var results = PostgreSQLFormat.substitute("SELECT * FROM t WHERE id=@id:int2 WHERE blob=@blob::jsonb AND blob='{\"a\":1}'::jsonb", { - "id": 2, - "blob": "{\"key\":\"value\"}" - }); + var results = PostgreSQLFormat.substitute( + "SELECT * FROM t WHERE id=@id:int2 WHERE blob=@blob::jsonb AND blob='{\"a\":1}'::jsonb", + {"id": 2, "blob": "{\"key\":\"value\"}"}); - expect(results, "SELECT * FROM t WHERE id=2 WHERE blob='{\"key\":\"value\"}'::jsonb AND blob='{\"a\":1}'::jsonb"); + expect(results, + "SELECT * FROM t WHERE id=2 WHERE blob='{\"key\":\"value\"}'::jsonb AND blob='{\"a\":1}'::jsonb"); }); test("Can both provide type and typecast", () { - var results = PostgreSQLFormat.substitute("SELECT * FROM t WHERE id=@id:int2::int4", { - "id": 2, - "blob": "{\"key\":\"value\"}" - }); + var results = PostgreSQLFormat.substitute( + "SELECT * FROM t WHERE id=@id:int2::int4", + {"id": 2, "blob": "{\"key\":\"value\"}"}); expect(results, "SELECT * FROM t WHERE id=2::int4"); }); test("UTF16 symbols with quotes", () { var value = "'©™®'"; - var results = PostgreSQLFormat.substitute("INSERT INTO t (t) VALUES (@t)", { - "t": value - }); + var results = PostgreSQLFormat.substitute( + "INSERT INTO t (t) VALUES (@t)", {"t": value}); expect(results, "INSERT INTO t (t) VALUES ('''©™®''')"); }); test("UTF16 symbols with backslash", () { var value = "'©\\™®'"; - var results = PostgreSQLFormat.substitute("INSERT INTO t (t) VALUES (@t)", { - "t": value - }); + var results = PostgreSQLFormat.substitute( + "INSERT INTO t (t) VALUES (@t)", {"t": value}); expect(results, "INSERT INTO t (t) VALUES ( E'''©\\\\™®''')"); }); test("String identifiers get escaped", () { - var result = PostgreSQLFormat - .substitute("@id:text @foo", {"id": "1';select", "foo": "3\\4"}); + var result = PostgreSQLFormat.substitute( + "@id:text @foo", {"id": "1';select", "foo": "3\\4"}); // ' 1 ' ' ; s e l e c t ' sp sp E ' 3 \ \ 4 ' expect(utf8.encode(result), [ diff --git a/test/json_test.dart b/test/json_test.dart index eaa7587..8a95e1d 100644 --- a/test/json_test.dart +++ b/test/json_test.dart @@ -5,7 +5,8 @@ void main() { PostgreSQLConnection connection; setUp(() async { - connection = new PostgreSQLConnection("localhost", 5432, "dart_test", username: "dart", password: "dart"); + connection = new PostgreSQLConnection("localhost", 5432, "dart_test", + username: "dart", password: "dart"); await connection.open(); await connection.execute(""" @@ -19,67 +20,133 @@ void main() { group("Storage", () { test("Can store JSON String", () async { - var result = await connection.query("INSERT INTO t (j) VALUES ('\"xyz\"'::jsonb) RETURNING j"); - expect(result, [["xyz"]]); + var result = await connection + .query("INSERT INTO t (j) VALUES ('\"xyz\"'::jsonb) RETURNING j"); + expect(result, [ + ["xyz"] + ]); result = await connection.query("SELECT j FROM t"); - expect(result, [["xyz"]]); + expect(result, [ + ["xyz"] + ]); }); test("Can store JSON String with driver type annotation", () async { - var result = await connection.query("INSERT INTO t (j) VALUES (@a:jsonb) RETURNING j", substitutionValues: { - "a" : "xyz" - }); - expect(result, [["xyz"]]); + var result = await connection.query( + "INSERT INTO t (j) VALUES (@a:jsonb) RETURNING j", + substitutionValues: {"a": "xyz"}); + expect(result, [ + ["xyz"] + ]); result = await connection.query("SELECT j FROM t"); - expect(result, [["xyz"]]); + expect(result, [ + ["xyz"] + ]); }); test("Can store JSON Number", () async { - var result = await connection.query("INSERT INTO t (j) VALUES ('4'::jsonb) RETURNING j"); - expect(result, [[4]]); + var result = await connection + .query("INSERT INTO t (j) VALUES ('4'::jsonb) RETURNING j"); + expect(result, [ + [4] + ]); result = await connection.query("SELECT j FROM t"); - expect(result, [[4]]); + expect(result, [ + [4] + ]); }); test("Can store JSON Number with driver type annotation", () async { - var result = await connection.query("INSERT INTO t (j) VALUES (@a:jsonb) RETURNING j", substitutionValues: { - "a": 4 - }); - expect(result, [[4]]); + var result = await connection.query( + "INSERT INTO t (j) VALUES (@a:jsonb) RETURNING j", + substitutionValues: {"a": 4}); + expect(result, [ + [4] + ]); result = await connection.query("SELECT j FROM t"); - expect(result, [[4]]); + expect(result, [ + [4] + ]); }); test("Can store JSON map", () async { - var result = await connection.query("INSERT INTO t (j) VALUES ('{\"a\":4}') RETURNING j"); - expect(result, [[{"a":4}]]); + var result = await connection + .query("INSERT INTO t (j) VALUES ('{\"a\":4}') RETURNING j"); + expect(result, [ + [ + {"a": 4} + ] + ]); result = await connection.query("SELECT j FROM t"); - expect(result, [[{"a":4}]]); + expect(result, [ + [ + {"a": 4} + ] + ]); }); test("Can store JSON map with driver type annotation", () async { - var result = await connection.query("INSERT INTO t (j) VALUES (@a:jsonb) RETURNING j", substitutionValues: { - "a": {"a":4} - }); - expect(result, [[{"a":4}]]); + var result = await connection.query( + "INSERT INTO t (j) VALUES (@a:jsonb) RETURNING j", + substitutionValues: { + "a": {"a": 4} + }); + expect(result, [ + [ + {"a": 4} + ] + ]); result = await connection.query("SELECT j FROM t"); - expect(result, [[{"a":4}]]); + expect(result, [ + [ + {"a": 4} + ] + ]); }); test("Can store JSON list", () async { - var result = await connection.query("INSERT INTO t (j) VALUES ('[{\"a\":4}]') RETURNING j"); - expect(result, [[[{"a":4}]]]); + var result = await connection + .query("INSERT INTO t (j) VALUES ('[{\"a\":4}]') RETURNING j"); + expect(result, [ + [ + [ + {"a": 4} + ] + ] + ]); result = await connection.query("SELECT j FROM t"); - expect(result, [[[{"a":4}]]]); + expect(result, [ + [ + [ + {"a": 4} + ] + ] + ]); }); test("Can store JSON list with driver type annotation", () async { - var result = await connection.query("INSERT INTO t (j) VALUES (@a:jsonb) RETURNING j", substitutionValues: { - "a": [{"a":4}] - }); - expect(result, [[[{"a":4}]]]); + var result = await connection.query( + "INSERT INTO t (j) VALUES (@a:jsonb) RETURNING j", + substitutionValues: { + "a": [ + {"a": 4} + ] + }); + expect(result, [ + [ + [ + {"a": 4} + ] + ] + ]); result = await connection.query("SELECT j FROM t"); - expect(result, [[[{"a":4}]]]); + expect(result, [ + [ + [ + {"a": 4} + ] + ] + ]); }); }); } diff --git a/test/map_return_test.dart b/test/map_return_test.dart index ad02f9b..2a1de3b 100644 --- a/test/map_return_test.dart +++ b/test/map_return_test.dart @@ -6,7 +6,8 @@ void main() { InterceptingConnection connection; setUp(() async { - connection = new InterceptingConnection("localhost", 5432, "dart_test", username: "dart", password: "dart"); + connection = new InterceptingConnection("localhost", 5432, "dart_test", + username: "dart", password: "dart"); await connection.open(); await connection.execute(""" @@ -20,9 +21,12 @@ void main() { await connection.execute("INSERT INTO t (id, name) VALUES (1, 'a')"); await connection.execute("INSERT INTO t (id, name) VALUES (2, 'b')"); await connection.execute("INSERT INTO t (id, name) VALUES (3, 'c')"); - await connection.execute("INSERT INTO u (id, name, t_id) VALUES (1, 'ua', 1)"); - await connection.execute("INSERT INTO u (id, name, t_id) VALUES (2, 'ub', 1)"); - await connection.execute("INSERT INTO u (id, name, t_id) VALUES (3, 'uc', 2)"); + await connection + .execute("INSERT INTO u (id, name, t_id) VALUES (1, 'ua', 1)"); + await connection + .execute("INSERT INTO u (id, name, t_id) VALUES (2, 'ub', 1)"); + await connection + .execute("INSERT INTO u (id, name, t_id) VALUES (3, 'uc', 2)"); }); tearDown(() async { @@ -30,7 +34,8 @@ void main() { }); test("Get row map without specifying columns", () async { - final results = await connection.mappedResultsQuery("SELECT * from t ORDER BY id ASC"); + final results = + await connection.mappedResultsQuery("SELECT * from t ORDER BY id ASC"); expect(results, [ { "t": {"id": 1, "name": "a"} @@ -45,7 +50,8 @@ void main() { }); test("Get row map by with specified columns", () async { - final results = await connection.mappedResultsQuery("SELECT name, id from t ORDER BY id ASC"); + final results = await connection + .mappedResultsQuery("SELECT name, id from t ORDER BY id ASC"); expect(results, [ { "t": {"id": 1, "name": "a"} @@ -58,7 +64,8 @@ void main() { }, ]); - final nextResults = await connection.mappedResultsQuery("SELECT name from t ORDER BY name DESC"); + final nextResults = await connection + .mappedResultsQuery("SELECT name from t ORDER BY name DESC"); expect(nextResults, [ { "t": {"name": "c"} @@ -96,7 +103,8 @@ void main() { }); test("Table names get cached", () async { - final regex = new RegExp("SELECT relname FROM pg_class WHERE relkind='r' AND oid IN \\(([0-9]*)\\) ORDER BY oid ASC"); + final regex = new RegExp( + "SELECT relname FROM pg_class WHERE relkind='r' AND oid IN \\(([0-9]*)\\) ORDER BY oid ASC"); final oids = []; await connection.mappedResultsQuery("SELECT id FROM t"); @@ -108,7 +116,8 @@ void main() { await connection.mappedResultsQuery("SELECT id FROM t"); expect(connection.queries.length, 0); - await connection.mappedResultsQuery("SELECT t.id, u.id FROM t LEFT OUTER JOIN u ON t.id=u.t_id"); + await connection.mappedResultsQuery( + "SELECT t.id, u.id FROM t LEFT OUTER JOIN u ON t.id=u.t_id"); expect(connection.queries.length, 1); match = regex.firstMatch(connection.queries.first); expect(oids.contains(match.group(1)), false); @@ -121,20 +130,28 @@ void main() { test("Non-table mappedResultsQuery succeeds", () async { final result = await connection.mappedResultsQuery("SELECT 1"); - expect(result, [{null: {"?column?": 1}}]); + expect(result, [ + { + null: {"?column?": 1} + } + ]); }); } class InterceptingConnection extends PostgreSQLConnection { - InterceptingConnection(String host, int port, String databaseName, {String username: null, String password: null}) + InterceptingConnection(String host, int port, String databaseName, + {String username: null, String password: null}) : super(host, port, databaseName, username: username, password: password); List queries = []; @override Future>> query(String fmtString, - {Map substitutionValues: null, bool allowReuse: true, int timeoutInSeconds}) { + {Map substitutionValues: null, + bool allowReuse: true, + int timeoutInSeconds}) { queries.add(fmtString); - return super.query(fmtString, substitutionValues: substitutionValues, allowReuse: allowReuse); + return super.query(fmtString, + substitutionValues: substitutionValues, allowReuse: allowReuse); } } diff --git a/test/notification_test.dart b/test/notification_test.dart index 1aa6017..fbe32e7 100644 --- a/test/notification_test.dart +++ b/test/notification_test.dart @@ -22,12 +22,10 @@ void main() { var channel = 'virtual'; var payload = 'This is the payload'; var futureMsg = connection.notifications.first; - await connection - .execute("LISTEN $channel;" - "NOTIFY $channel, '$payload';"); + await connection.execute("LISTEN $channel;" + "NOTIFY $channel, '$payload';"); - var msg = await futureMsg - .timeout(new Duration(milliseconds: 200)); + var msg = await futureMsg.timeout(new Duration(milliseconds: 200)); expect(msg.channel, channel); expect(msg.payload, payload); }); @@ -35,12 +33,10 @@ void main() { test("Notification Response empty payload", () async { var channel = 'virtual'; var futureMsg = connection.notifications.first; - await connection - .execute("LISTEN $channel;" - "NOTIFY $channel;"); + await connection.execute("LISTEN $channel;" + "NOTIFY $channel;"); - var msg = await futureMsg - .timeout(new Duration(milliseconds: 200)); + var msg = await futureMsg.timeout(new Duration(milliseconds: 200)); expect(msg.channel, channel); expect(msg.payload, ''); }); @@ -49,27 +45,22 @@ void main() { var channel = 'virtual'; var payload = 'This is the payload'; var futureMsg = connection.notifications.first; - await connection - .execute("LISTEN $channel;" + await connection.execute("LISTEN $channel;" "NOTIFY $channel, '$payload';"); - var msg = await futureMsg - .timeout(new Duration(milliseconds: 200)); + var msg = await futureMsg.timeout(new Duration(milliseconds: 200)); expect(msg.channel, channel); expect(msg.payload, payload); - await connection - .execute("UNLISTEN $channel;"); + await connection.execute("UNLISTEN $channel;"); futureMsg = connection.notifications.first; try { - await connection - .execute("NOTIFY $channel, '$payload';"); + await connection.execute("NOTIFY $channel, '$payload';"); - await futureMsg - .timeout(new Duration(milliseconds: 200)); + await futureMsg.timeout(new Duration(milliseconds: 200)); fail('There should be no notification'); } on TimeoutException catch (_) {} @@ -79,12 +70,11 @@ void main() { Map countResponse = new Map(); int totalCountResponse = 0; Completer finishExecute = new Completer(); - connection.notifications.listen((msg){ + connection.notifications.listen((msg) { int count = countResponse[msg.channel]; countResponse[msg.channel] = (count ?? 0) + 1; totalCountResponse++; - if(totalCountResponse == 20) - finishExecute.complete(); + if (totalCountResponse == 20) finishExecute.complete(); }); var channel1 = 'virtual1'; @@ -92,30 +82,24 @@ void main() { var notifier = () async { for (int i = 0; i < 5; i++) { - await connection - .execute("NOTIFY $channel1;" + await connection.execute("NOTIFY $channel1;" "NOTIFY $channel2;"); } }; - await connection - .execute("LISTEN $channel1;"); + await connection.execute("LISTEN $channel1;"); await notifier(); - await connection - .execute("LISTEN $channel2;"); + await connection.execute("LISTEN $channel2;"); await notifier(); - await connection - .execute("UNLISTEN $channel1;"); + await connection.execute("UNLISTEN $channel1;"); await notifier(); - await connection - .execute("UNLISTEN $channel2;"); + await connection.execute("UNLISTEN $channel2;"); await notifier(); - await finishExecute.future - .timeout(new Duration(milliseconds: 200)); + await finishExecute.future.timeout(new Duration(milliseconds: 200)); expect(countResponse[channel1], 10); expect(countResponse[channel2], 10); diff --git a/test/query_reuse_test.dart b/test/query_reuse_test.dart index 8469592..77127ae 100644 --- a/test/query_reuse_test.dart +++ b/test/query_reuse_test.dart @@ -561,8 +561,10 @@ void main() { Map cachedQueryMap(PostgreSQLConnection connection) { var cacheMirror = reflect(connection).type.declarations.values.firstWhere( (DeclarationMirror dm) => dm.simpleName.toString().contains("_cache")); - return reflect(connection).getField(cacheMirror.simpleName).getField(#queries).reflectee - as Map; + return reflect(connection) + .getField(cacheMirror.simpleName) + .getField(#queries) + .reflectee as Map; } bool hasCachedQueryNamed(PostgreSQLConnection connection, String name) { diff --git a/test/query_test.dart b/test/query_test.dart index 4c2e883..cdc87f0 100644 --- a/test/query_test.dart +++ b/test/query_test.dart @@ -11,12 +11,11 @@ void main() { connection = new PostgreSQLConnection("localhost", 5432, "dart_test", username: "dart", password: "dart"); await connection.open(); - await connection.execute( - "CREATE TEMPORARY TABLE t " - "(i int, s serial, bi bigint, " - "bs bigserial, bl boolean, si smallint, " - "t text, f real, d double precision, " - "dt date, ts timestamp, tsz timestamptz, j jsonb, u uuid)"); + await connection.execute("CREATE TEMPORARY TABLE t " + "(i int, s serial, bi bigint, " + "bs bigserial, bl boolean, si smallint, " + "t text, f real, d double precision, " + "dt date, ts timestamp, tsz timestamptz, j jsonb, u uuid)"); await connection.execute( "CREATE TEMPORARY TABLE u (i1 int not null, i2 int not null);"); await connection @@ -57,7 +56,7 @@ void main() { test("UTF16 strings in value with escape characters", () async { await connection.execute( "INSERT INTO t (t) values " - "(${PostgreSQLFormat.id("t", type: PostgreSQLDataType.text)})", + "(${PostgreSQLFormat.id("t", type: PostgreSQLDataType.text)})", substitutionValues: { "t": "'©™®'", }); @@ -71,7 +70,7 @@ void main() { test("UTF16 strings in value with backslash", () async { await connection.execute( "INSERT INTO t (t) values " - "(${PostgreSQLFormat.id("t", type: PostgreSQLDataType.text)})", + "(${PostgreSQLFormat.id("t", type: PostgreSQLDataType.text)})", substitutionValues: { "t": "°\\'©™®'", }); @@ -133,7 +132,7 @@ void main() { "dt": new DateTime.utc(2000), "ts": new DateTime.utc(2000, 2), "tsz": new DateTime.utc(2000, 3), - "j": {"a":"b"}, + "j": {"a": "b"}, "u": "01234567-89ab-cdef-0123-0123456789ab" }); @@ -150,12 +149,12 @@ void main() { new DateTime.utc(2000), new DateTime.utc(2000, 2), new DateTime.utc(2000, 3), - {"a":"b"}, + {"a": "b"}, "01234567-89ab-cdef-0123-0123456789ab" ]; expect(result, [expectedRow]); - result = await connection - .query("select i,s, bi, bs, bl, si, t, f, d, dt, ts, tsz, j, u from t"); + result = await connection.query( + "select i,s, bi, bs, bl, si, t, f, d, dt, ts, tsz, j, u from t"); expect(result, [expectedRow]); }); @@ -174,7 +173,7 @@ void main() { "${PostgreSQLFormat.id("tsz", type: PostgreSQLDataType.timestampWithTimezone)}," "${PostgreSQLFormat.id("j", type: PostgreSQLDataType.json)}," "${PostgreSQLFormat.id("u", type: PostgreSQLDataType.uuid)})" - " returning i,s, bi, bs, bl, si, t, f, d, dt, ts, tsz, j, u", + " returning i,s, bi, bs, bl, si, t, f, d, dt, ts, tsz, j, u", substitutionValues: { "i": 1, "bi": 2, @@ -208,8 +207,8 @@ void main() { ]; expect(result, [expectedRow]); - result = await connection - .query("select i,s, bi, bs, bl, si, t, f, d, dt, ts, tsz, j, u from t"); + result = await connection.query( + "select i,s, bi, bs, bl, si, t, f, d, dt, ts, tsz, j, u from t"); expect(result, [expectedRow]); }); @@ -315,11 +314,8 @@ void main() { test("Can cast text to int on db server", () async { var results = await connection.query( - "INSERT INTO u (i1, i2) VALUES (@i1::int4, @i2::int4) RETURNING i1, i2", - substitutionValues: { - "i1": "0", - "i2": "1" - }); + "INSERT INTO u (i1, i2) VALUES (@i1::int4, @i2::int4) RETURNING i1, i2", + substitutionValues: {"i1": "0", "i2": "1"}); expect(results, [ [0, 1] @@ -327,7 +323,6 @@ void main() { }); }); - group("Unsuccesful queries", () { var connection = new PostgreSQLConnection("localhost", 5432, "dart_test", username: "dart", password: "dart"); @@ -382,10 +377,7 @@ void main() { try { await connection.query( "INSERT INTO t (i1, i2) values (@i1:int4, @i2:int4)", - substitutionValues: { - "i1": "1", - "i2": 1 - }); + substitutionValues: {"i1": "1", "i2": 1}); expect(true, false); } on FormatException catch (e) { expect(e.toString(), contains("Invalid type for parameter value")); @@ -396,10 +388,7 @@ void main() { try { await connection.query( "INSERT INTO t (i1, i2) values (@i1:qwerty, @i2:int4)", - substitutionValues: { - "i1": "1", - "i2": 1 - }); + substitutionValues: {"i1": "1", "i2": 1}); expect(true, false); } on FormatException catch (e) { expect(e.toString(), contains("Invalid type code")); diff --git a/test/timeout_test.dart b/test/timeout_test.dart index 2d2b40e..1d94f9e 100644 --- a/test/timeout_test.dart +++ b/test/timeout_test.dart @@ -6,7 +6,8 @@ void main() { PostgreSQLConnection conn; setUp(() async { - conn = new PostgreSQLConnection("localhost", 5432, "dart_test", username: "dart", password: "dart"); + conn = new PostgreSQLConnection("localhost", 5432, "dart_test", + username: "dart", password: "dart"); await conn.open(); await conn.execute("CREATE TEMPORARY TABLE t (id INT UNIQUE)"); }); @@ -15,7 +16,9 @@ void main() { await conn?.close(); }); - test("Timeout fires on query while in queue does not execute query, query throws exception", () async { + test( + "Timeout fires on query while in queue does not execute query, query throws exception", + () async { //ignore: unawaited_futures final f = conn.query("SELECT pg_sleep(2)"); try { @@ -38,7 +41,9 @@ void main() { expect(await conn.query("SELECT * from t"), hasLength(0)); }); - test("Query on parent context for transaction completes (with error) after timeout", () async { + test( + "Query on parent context for transaction completes (with error) after timeout", + () async { try { await conn.transaction((ctx) async { await conn.query("SELECT 1", timeoutInSeconds: 1); @@ -50,7 +55,9 @@ void main() { expect(await conn.query("SELECT * from t"), hasLength(0)); }); - test("If query is already on the wire and times out, safely throws timeoutexception and nothing else", () async { + test( + "If query is already on the wire and times out, safely throws timeoutexception and nothing else", + () async { try { await conn.query("SELECT pg_sleep(2)", timeoutInSeconds: 1); fail('unreachable'); @@ -59,9 +66,13 @@ void main() { test("Query times out, next query in the queue runs", () async { //ignore: unawaited_futures - conn.query("SELECT pg_sleep(2)", timeoutInSeconds: 1).catchError((_) => null); + conn + .query("SELECT pg_sleep(2)", timeoutInSeconds: 1) + .catchError((_) => null); - expect(await conn.query("SELECT 1"), [[1]]); + expect(await conn.query("SELECT 1"), [ + [1] + ]); }); test("Query that succeeds does not timeout", () async { @@ -70,7 +81,9 @@ void main() { }); test("Query that fails does not timeout", () async { - await conn.query("INSERT INTO t (id) VALUES ('foo')", timeoutInSeconds: 1).catchError((_) => null); + await conn + .query("INSERT INTO t (id) VALUES ('foo')", timeoutInSeconds: 1) + .catchError((_) => null); expect(new Future.delayed(new Duration(seconds: 2)), completes); }); } diff --git a/test/transaction_test.dart b/test/transaction_test.dart index b9d8a8e..8cc8785 100644 --- a/test/transaction_test.dart +++ b/test/transaction_test.dart @@ -9,7 +9,8 @@ void main() { PostgreSQLConnection conn = null; setUp(() async { - conn = new PostgreSQLConnection("localhost", 5432, "dart_test", username: "dart", password: "dart"); + conn = new PostgreSQLConnection("localhost", 5432, "dart_test", + username: "dart", password: "dart"); await conn.open(); await conn.execute("CREATE TEMPORARY TABLE t (id INT UNIQUE)"); }); @@ -22,7 +23,8 @@ void main() { await conn.execute("INSERT INTO t (id) VALUES (1)"); final List> outValue = await conn.transaction((ctx) async { - return await ctx.query('SELECT * FROM t WHERE id = @id LIMIT 1', substitutionValues: {'id': 1}); + return await ctx.query('SELECT * FROM t WHERE id = @id LIMIT 1', + substitutionValues: {'id': 1}); }); expect(outValue.length, 1); @@ -31,7 +33,8 @@ void main() { expect(outValue.first.first, 1); }); - test("Send successful transaction succeeds, returns returned value", () async { + test("Send successful transaction succeeds, returns returned value", + () async { var outResult = await conn.transaction((c) async { await c.query("INSERT INTO t (id) VALUES (1)"); @@ -47,7 +50,8 @@ void main() { ]); }); - test("Query during transaction must wait until transaction is finished", () async { + test("Query during transaction must wait until transaction is finished", + () async { var orderEnsurer = []; var nextCompleter = new Completer.sync(); var outResult = conn.transaction((c) async { @@ -80,7 +84,8 @@ void main() { ]); }); - test("Make sure two simultaneous transactions cannot be interwoven", () async { + test("Make sure two simultaneous transactions cannot be interwoven", + () async { var orderEnsurer = []; var firstTransactionFuture = conn.transaction((c) async { @@ -138,7 +143,8 @@ void main() { expect(result, []); }); - test("Intentional rollback from outside of a transaction has no impact", () async { + test("Intentional rollback from outside of a transaction has no impact", + () async { var orderEnsurer = []; var nextCompleter = new Completer.sync(); var outResult = conn.transaction((c) async { @@ -229,12 +235,15 @@ void main() { }).catchError((e) => transactionError = e); expect(transactionError, isNotNull); expect(failingQueryError.toString(), contains("invalid input")); - expect(pendingQueryError.toString(), contains("failed prior to execution")); + expect( + pendingQueryError.toString(), contains("failed prior to execution")); var total = await conn.query("SELECT id FROM t"); expect(total, []); }); - test("A transaction with a rollback and non-await queries rolls back transaction", () async { + test( + "A transaction with a rollback and non-await queries rolls back transaction", + () async { var errs = []; await conn.transaction((ctx) async { ctx.query("INSERT INTO t (id) VALUES (1)").catchError((e) { @@ -253,7 +262,8 @@ void main() { expect(errs.length, 2); }); - test("A transaction that mixes awaiting and non-awaiting queries fails gracefully when an awaited query fails", + test( + "A transaction that mixes awaiting and non-awaiting queries fails gracefully when an awaited query fails", () async { var transactionError; await conn.transaction((ctx) async { @@ -267,7 +277,8 @@ void main() { expect(total, []); }); - test("A transaction that mixes awaiting and non-awaiting queries fails gracefully when an unawaited query fails", + test( + "A transaction that mixes awaiting and non-awaiting queries fails gracefully when an unawaited query fails", () async { var transactionError; await conn.transaction((ctx) async { @@ -289,7 +300,8 @@ void main() { PostgreSQLConnection conn = null; setUp(() async { - conn = new PostgreSQLConnection("localhost", 5432, "dart_test", username: "dart", password: "dart"); + conn = new PostgreSQLConnection("localhost", 5432, "dart_test", + username: "dart", password: "dart"); await conn.open(); await conn.execute("CREATE TEMPORARY TABLE t (id INT UNIQUE)"); }); @@ -387,7 +399,8 @@ void main() { PostgreSQLConnection conn = null; setUp(() async { - conn = new PostgreSQLConnection("localhost", 5432, "dart_test", username: "dart", password: "dart"); + conn = new PostgreSQLConnection("localhost", 5432, "dart_test", + username: "dart", password: "dart"); await conn.open(); await conn.execute("CREATE TEMPORARY TABLE t (id INT UNIQUE)"); }); @@ -462,12 +475,15 @@ void main() { expect(result, []); }); - test("If exception thrown while preparing query, transaction gets rolled back", () async { + test( + "If exception thrown while preparing query, transaction gets rolled back", + () async { try { await conn.transaction((c) async { await c.query("INSERT INTO t (id) VALUES (1)"); - c.query("INSERT INTO t (id) VALUES (@id:int4)", substitutionValues: {"id": "foobar"}).catchError((_) => null); + c.query("INSERT INTO t (id) VALUES (@id:int4)", + substitutionValues: {"id": "foobar"}).catchError((_) => null); await c.query("INSERT INTO t (id) VALUES (2)"); }); expect(true, false); @@ -498,11 +514,15 @@ void main() { expect(res, []); }); - test("When exception thrown in unawaited on future, transaction is rolled back", () async { + test( + "When exception thrown in unawaited on future, transaction is rolled back", + () async { try { await conn.transaction((c) async { await c.query("INSERT INTO t (id) VALUES (1)"); - c.query("INSERT INTO t (id) VALUE ('foo') RETURNING id").catchError((_) => null); + c + .query("INSERT INTO t (id) VALUE ('foo') RETURNING id") + .catchError((_) => null); await c.query("INSERT INTO t (id) VALUES (2)"); }); fail('unreachable'); @@ -517,7 +537,8 @@ void main() { PostgreSQLConnection conn = null; setUp(() async { - conn = new PostgreSQLConnection("localhost", 5432, "dart_test", username: "dart", password: "dart"); + conn = new PostgreSQLConnection("localhost", 5432, "dart_test", + username: "dart", password: "dart"); await conn.open(); await conn.execute("CREATE TEMPORARY TABLE t (id INT UNIQUE)"); }); From e02d0afe1fda504d64c1d823cec444ee1689ff43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Istv=C3=A1n=20So=C3=B3s?= Date: Thu, 21 Mar 2019 00:47:16 +0100 Subject: [PATCH 04/20] Upgrade sources: Dart 2.2 format, pedantic + other strict linter rules. (#81) Upgrade sources: Dart 2.2 format, pedantic + other strict linter rules. --- analysis_options.yaml | 64 ++++- lib/postgres.dart | 4 +- lib/src/binary_codec.dart | 181 ++++++------- lib/src/client_messages.dart | 68 +++-- lib/src/connection.dart | 180 ++++++------- lib/src/connection_fsm.dart | 85 +++--- lib/src/exceptions.dart | 24 +- lib/src/execution_context.dart | 17 +- lib/src/message_window.dart | 40 +-- lib/src/query.dart | 140 +++++----- lib/src/query_cache.dart | 8 +- lib/src/query_queue.dart | 9 +- lib/src/server_messages.dart | 104 +++++--- lib/src/substituter.dart | 106 ++++---- lib/src/text_codec.dart | 52 ++-- lib/src/transaction_proxy.dart | 31 ++- lib/src/utf8_backed_string.dart | 8 +- pubspec.yaml | 5 +- test/connection_test.dart | 411 +++++++++++++++-------------- test/decode_test.dart | 78 +++--- test/encoding_test.dart | 303 +++++++++++---------- test/framer_test.dart | 165 ++++++------ test/interpolation_test.dart | 93 +++---- test/json_test.dart | 84 +++--- test/map_return_test.dart | 84 +++--- test/notification_test.dart | 78 +++--- test/query_reuse_test.dart | 449 ++++++++++++++++---------------- test/query_test.dart | 380 +++++++++++++-------------- test/timeout_test.dart | 54 ++-- test/transaction_test.dart | 380 ++++++++++++++------------- 30 files changed, 1887 insertions(+), 1798 deletions(-) diff --git a/analysis_options.yaml b/analysis_options.yaml index 2c63e94..ab0596b 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -3,19 +3,61 @@ # The commented part below is just for inspiration. Read the guide here: # https://github.com/dart-lang/sdk/tree/master/pkg/analyzer#configuring-the-analyzer +include: package:pedantic/analysis_options.yaml + analyzer: -# strong-mode: -# implicit-casts: false -# excludes: -# - path/to/excluded/files/** + errors: + override_on_non_overriding_method: error + unused_element: error + unused_import: error + unused_local_variable: error + dead_code: error + strong-mode: + implicit-casts: false linter: rules: # see catalogue here: http://dart-lang.github.io/linter/lints/ - - camel_case_types - - hash_and_equals - - iterable_contains_unrelated_type - - list_remove_unrelated_type - - unawaited_futures - - unrelated_type_equality_checks - - valid_regexps + - annotate_overrides + - avoid_unused_constructor_parameters + - await_only_futures + - camel_case_types + - cancel_subscriptions + - directives_ordering +# - empty_catches + - empty_statements + - hash_and_equals + - iterable_contains_unrelated_type + - list_remove_unrelated_type + - no_adjacent_strings_in_list + - no_duplicate_case_values + - non_constant_identifier_names + - only_throw_errors + - overridden_fields + - prefer_collection_literals + - prefer_conditional_assignment + - prefer_contains + - prefer_final_fields + - prefer_final_locals + - prefer_initializing_formals + - prefer_interpolation_to_compose_strings + - prefer_is_empty + - prefer_is_not_empty + - prefer_single_quotes + - prefer_typing_uninitialized_variables + - recursive_getters + - slash_for_doc_comments + - test_types_in_equals + - throw_in_finally + - type_init_formals + - unawaited_futures + - unnecessary_brace_in_string_interps + - unnecessary_getters_setters + - unnecessary_lambdas + - unnecessary_new + - unnecessary_null_aware_assignments + - unnecessary_statements + - unnecessary_this + - unrelated_type_equality_checks + - use_rethrow_when_possible + - valid_regexps diff --git a/lib/postgres.dart b/lib/postgres.dart index 050dc89..01c9235 100644 --- a/lib/postgres.dart +++ b/lib/postgres.dart @@ -1,6 +1,6 @@ library postgres; export 'src/connection.dart'; -export 'src/types.dart'; -export 'src/substituter.dart'; export 'src/execution_context.dart'; +export 'src/substituter.dart'; +export 'src/types.dart'; diff --git a/lib/src/binary_codec.dart b/lib/src/binary_codec.dart index 1b061f5..2108663 100644 --- a/lib/src/binary_codec.dart +++ b/lib/src/binary_codec.dart @@ -1,9 +1,10 @@ import 'dart:convert'; - import 'dart:typed_data'; -import 'package:postgres/postgres.dart'; -import 'package:postgres/src/types.dart'; +import 'package:buffer/buffer.dart'; + +import '../postgres.dart'; +import 'types.dart'; class PostgresBinaryEncoder extends Converter { const PostgresBinaryEncoder(this.dataType); @@ -19,125 +20,114 @@ class PostgresBinaryEncoder extends Converter { switch (dataType) { case PostgreSQLDataType.boolean: { - if (value is! bool) { - throw new FormatException( - "Invalid type for parameter value. Expected: bool Got: ${value.runtimeType}"); + if (value is bool) { + final bd = ByteData(1); + bd.setUint8(0, value ? 1 : 0); + return bd.buffer.asUint8List(); } - - var bd = new ByteData(1); - bd.setUint8(0, value ? 1 : 0); - return bd.buffer.asUint8List(); + throw FormatException( + 'Invalid type for parameter value. Expected: bool Got: ${value.runtimeType}'); } case PostgreSQLDataType.bigSerial: case PostgreSQLDataType.bigInteger: { - if (value is! int) { - throw new FormatException( - "Invalid type for parameter value. Expected: int Got: ${value.runtimeType}"); + if (value is int) { + final bd = ByteData(8); + bd.setInt64(0, value); + return bd.buffer.asUint8List(); } - - var bd = new ByteData(8); - bd.setInt64(0, value); - return bd.buffer.asUint8List(); + throw FormatException( + 'Invalid type for parameter value. Expected: int Got: ${value.runtimeType}'); } case PostgreSQLDataType.serial: case PostgreSQLDataType.integer: { - if (value is! int) { - throw new FormatException( - "Invalid type for parameter value. Expected: int Got: ${value.runtimeType}"); + if (value is int) { + final bd = ByteData(4); + bd.setInt32(0, value); + return bd.buffer.asUint8List(); } - - var bd = new ByteData(4); - bd.setInt32(0, value); - return bd.buffer.asUint8List(); + throw FormatException( + 'Invalid type for parameter value. Expected: int Got: ${value.runtimeType}'); } case PostgreSQLDataType.smallInteger: { - if (value is! int) { - throw new FormatException( - "Invalid type for parameter value. Expected: int Got: ${value.runtimeType}"); + if (value is int) { + final bd = ByteData(2); + bd.setInt16(0, value); + return bd.buffer.asUint8List(); } - - var bd = new ByteData(2); - bd.setInt16(0, value); - return bd.buffer.asUint8List(); + throw FormatException( + 'Invalid type for parameter value. Expected: int Got: ${value.runtimeType}'); } case PostgreSQLDataType.name: case PostgreSQLDataType.text: { - if (value is! String) { - throw new FormatException( - "Invalid type for parameter value. Expected: String Got: ${value.runtimeType}"); + if (value is String) { + return castBytes(utf8.encode(value)); } - - return utf8.encode(value); + throw FormatException( + 'Invalid type for parameter value. Expected: String Got: ${value.runtimeType}'); } case PostgreSQLDataType.real: { - if (value is! double) { - throw new FormatException( - "Invalid type for parameter value. Expected: double Got: ${value.runtimeType}"); + if (value is double) { + final bd = ByteData(4); + bd.setFloat32(0, value); + return bd.buffer.asUint8List(); } - - var bd = new ByteData(4); - bd.setFloat32(0, value); - return bd.buffer.asUint8List(); + throw FormatException( + 'Invalid type for parameter value. Expected: double Got: ${value.runtimeType}'); } case PostgreSQLDataType.double: { - if (value is! double) { - throw new FormatException( - "Invalid type for parameter value. Expected: double Got: ${value.runtimeType}"); + if (value is double) { + final bd = ByteData(8); + bd.setFloat64(0, value); + return bd.buffer.asUint8List(); } - - var bd = new ByteData(8); - bd.setFloat64(0, value); - return bd.buffer.asUint8List(); + throw FormatException( + 'Invalid type for parameter value. Expected: double Got: ${value.runtimeType}'); } case PostgreSQLDataType.date: { - if (value is! DateTime) { - throw new FormatException( - "Invalid type for parameter value. Expected: DateTime Got: ${value.runtimeType}"); + if (value is DateTime) { + final bd = ByteData(4); + bd.setInt32(0, value.toUtc().difference(DateTime.utc(2000)).inDays); + return bd.buffer.asUint8List(); } - - var bd = new ByteData(4); - bd.setInt32( - 0, value.toUtc().difference(new DateTime.utc(2000)).inDays); - return bd.buffer.asUint8List(); + throw FormatException( + 'Invalid type for parameter value. Expected: DateTime Got: ${value.runtimeType}'); } case PostgreSQLDataType.timestampWithoutTimezone: { - if (value is! DateTime) { - throw new FormatException( - "Invalid type for parameter value. Expected: DateTime Got: ${value.runtimeType}"); + if (value is DateTime) { + final bd = ByteData(8); + final diff = value.toUtc().difference(DateTime.utc(2000)); + bd.setInt64(0, diff.inMicroseconds); + return bd.buffer.asUint8List(); } - - var bd = new ByteData(8); - var diff = value.toUtc().difference(new DateTime.utc(2000)); - bd.setInt64(0, diff.inMicroseconds); - return bd.buffer.asUint8List(); + throw FormatException( + 'Invalid type for parameter value. Expected: DateTime Got: ${value.runtimeType}'); } case PostgreSQLDataType.timestampWithTimezone: { - if (value is! DateTime) { - throw new FormatException( - "Invalid type for parameter value. Expected: DateTime Got: ${value.runtimeType}"); + if (value is DateTime) { + final bd = ByteData(8); + bd.setInt64( + 0, value.toUtc().difference(DateTime.utc(2000)).inMicroseconds); + return bd.buffer.asUint8List(); } - - var bd = new ByteData(8); - bd.setInt64(0, - value.toUtc().difference(new DateTime.utc(2000)).inMicroseconds); - return bd.buffer.asUint8List(); + throw FormatException( + 'Invalid type for parameter value. Expected: DateTime Got: ${value.runtimeType}'); } case PostgreSQLDataType.json: { - var jsonBytes = utf8.encode(json.encode(value)); - final outBuffer = new Uint8List(jsonBytes.length + 1); + final jsonBytes = utf8.encode(json.encode(value)); + final outBuffer = Uint8List(jsonBytes.length + 1); outBuffer[0] = 1; for (var i = 0; i < jsonBytes.length; i++) { outBuffer[i + 1] = jsonBytes[i]; @@ -148,28 +138,28 @@ class PostgresBinaryEncoder extends Converter { case PostgreSQLDataType.byteArray: { - if (value is! List) { - throw new FormatException( - "Invalid type for parameter value. Expected: List Got: ${value.runtimeType}"); + if (value is List) { + return castBytes(value); } - return new Uint8List.fromList(value); + throw FormatException( + 'Invalid type for parameter value. Expected: List Got: ${value.runtimeType}'); } case PostgreSQLDataType.uuid: { if (value is! String) { - throw new FormatException( - "Invalid type for parameter value. Expected: String Got: ${value.runtimeType}"); + throw FormatException( + 'Invalid type for parameter value. Expected: String Got: ${value.runtimeType}'); } - final dashUnit = "-".codeUnits.first; + final dashUnit = '-'.codeUnits.first; final hexBytes = (value as String) .toLowerCase() .codeUnits .where((c) => c != dashUnit) .toList(); if (hexBytes.length != 32) { - throw new FormatException( + throw FormatException( "Invalid UUID string. There must be exactly 32 hexadecimal (0-9 and a-f) characters and any number of '-' characters."); } @@ -180,11 +170,11 @@ class PostgresBinaryEncoder extends Converter { return charCode - 87; } - throw new FormatException( - "Invalid UUID string. Contains non-hexadecimal character (0-9 and a-f)."); + throw FormatException( + 'Invalid UUID string. Contains non-hexadecimal character (0-9 and a-f).'); }; - final outBuffer = new Uint8List(16); + final outBuffer = Uint8List(16); for (var i = 0; i < hexBytes.length; i += 2) { final upperByte = byteConvert(hexBytes[i]); final lowerByte = byteConvert(hexBytes[i + 1]); @@ -195,7 +185,7 @@ class PostgresBinaryEncoder extends Converter { } } - throw new PostgreSQLException("Unsupported datatype"); + throw PostgreSQLException('Unsupported datatype'); } } @@ -212,8 +202,8 @@ class PostgresBinaryDecoder extends Converter { return null; } - final buffer = new ByteData.view( - value.buffer, value.offsetInBytes, value.lengthInBytes); + final buffer = + ByteData.view(value.buffer, value.offsetInBytes, value.lengthInBytes); switch (dataType) { case PostgreSQLDataType.name: @@ -236,12 +226,11 @@ class PostgresBinaryDecoder extends Converter { return buffer.getFloat64(0); case PostgreSQLDataType.timestampWithoutTimezone: case PostgreSQLDataType.timestampWithTimezone: - return new DateTime.utc(2000) - .add(new Duration(microseconds: buffer.getInt64(0))); + return DateTime.utc(2000) + .add(Duration(microseconds: buffer.getInt64(0))); case PostgreSQLDataType.date: - return new DateTime.utc(2000) - .add(new Duration(days: buffer.getInt32(0))); + return DateTime.utc(2000).add(Duration(days: buffer.getInt32(0))); case PostgreSQLDataType.json: { @@ -257,7 +246,7 @@ class PostgresBinaryDecoder extends Converter { case PostgreSQLDataType.uuid: { - final codeDash = "-".codeUnitAt(0); + final codeDash = '-'.codeUnitAt(0); final cipher = [ '0', @@ -281,7 +270,7 @@ class PostgresBinaryDecoder extends Converter { return cipher[value]; }; - final buf = new StringBuffer(); + final buf = StringBuffer(); for (var i = 0; i < buffer.lengthInBytes; i++) { final byteValue = buffer.getUint8(i); final upperByteValue = byteValue ~/ 16; diff --git a/lib/src/client_messages.dart b/lib/src/client_messages.dart index 88f9db8..f46a49f 100644 --- a/lib/src/client_messages.dart +++ b/lib/src/client_messages.dart @@ -1,11 +1,12 @@ -import 'utf8_backed_string.dart'; import 'dart:typed_data'; -import 'query.dart'; -import 'constants.dart'; import 'package:buffer/buffer.dart'; import 'package:crypto/crypto.dart'; +import 'constants.dart'; +import 'query.dart'; +import 'utf8_backed_string.dart'; + abstract class ClientMessage { static const int FormatText = 0; static const int FormatBinary = 1; @@ -34,13 +35,13 @@ abstract class ClientMessage { void applyToBuffer(ByteDataWriter buffer); Uint8List asBytes() { - var buffer = new ByteDataWriter(); + final buffer = ByteDataWriter(); applyToBuffer(buffer); return buffer.toBytes(); } static Uint8List aggregateBytes(List messages) { - var buffer = new ByteDataWriter(); + final buffer = ByteDataWriter(); messages.forEach((cm) => cm.applyToBuffer(buffer)); return buffer.toBytes(); } @@ -48,22 +49,23 @@ abstract class ClientMessage { class StartupMessage extends ClientMessage { StartupMessage(String databaseName, String timeZone, {String username}) { - this.databaseName = new UTF8BackedString(databaseName); - this.timeZone = new UTF8BackedString(timeZone); + this.databaseName = UTF8BackedString(databaseName); + this.timeZone = UTF8BackedString(timeZone); if (username != null) { - this.username = new UTF8BackedString(username); + this.username = UTF8BackedString(username); } } - UTF8BackedString username = null; + UTF8BackedString username; UTF8BackedString databaseName; UTF8BackedString timeZone; ByteData buffer; + @override int get length { - var fixedLength = 53; - var variableLength = (username?.utf8Length ?? 0) + + final fixedLength = 53; + final variableLength = (username?.utf8Length ?? 0) + databaseName.utf8Length + timeZone.utf8Length + 3; @@ -71,6 +73,7 @@ class StartupMessage extends ClientMessage { return fixedLength + variableLength; } + @override void applyToBuffer(ByteDataWriter buffer) { buffer.writeInt32(length); buffer.writeInt32(ClientMessage.ProtocolVersion); @@ -95,19 +98,21 @@ class StartupMessage extends ClientMessage { class AuthMD5Message extends ClientMessage { AuthMD5Message(String username, String password, List saltBytes) { - var passwordHash = - md5.convert("${password}${username}".codeUnits).toString(); - var saltString = new String.fromCharCodes(saltBytes); - hashedAuthString = new UTF8BackedString( - "md5" + md5.convert("$passwordHash$saltString".codeUnits).toString()); + final passwordHash = md5.convert('$password$username'.codeUnits).toString(); + final saltString = String.fromCharCodes(saltBytes); + final md5Hash = + md5.convert('$passwordHash$saltString'.codeUnits).toString(); + hashedAuthString = UTF8BackedString('md5$md5Hash'); } UTF8BackedString hashedAuthString; + @override int get length { return 6 + hashedAuthString.utf8Length; } + @override void applyToBuffer(ByteDataWriter buffer) { buffer.writeUint8(ClientMessage.PasswordIdentifier); buffer.writeUint32(length - 1); @@ -117,15 +122,17 @@ class AuthMD5Message extends ClientMessage { class QueryMessage extends ClientMessage { QueryMessage(String queryString) { - this.queryString = new UTF8BackedString(queryString); + this.queryString = UTF8BackedString(queryString); } UTF8BackedString queryString; + @override int get length { return 6 + queryString.utf8Length; } + @override void applyToBuffer(ByteDataWriter buffer) { buffer.writeUint8(ClientMessage.QueryIdentifier); buffer.writeUint32(length - 1); @@ -134,18 +141,20 @@ class QueryMessage extends ClientMessage { } class ParseMessage extends ClientMessage { - ParseMessage(String statement, {String statementName: ""}) { - this.statement = new UTF8BackedString(statement); - this.statementName = new UTF8BackedString(statementName); + ParseMessage(String statement, {String statementName = ''}) { + this.statement = UTF8BackedString(statement); + this.statementName = UTF8BackedString(statementName); } UTF8BackedString statementName; UTF8BackedString statement; + @override int get length { return 9 + statement.utf8Length + statementName.utf8Length; } + @override void applyToBuffer(ByteDataWriter buffer) { buffer.writeUint8(ClientMessage.ParseIdentifier); buffer.writeUint32(length - 1); @@ -157,16 +166,18 @@ class ParseMessage extends ClientMessage { } class DescribeMessage extends ClientMessage { - DescribeMessage({String statementName: ""}) { - this.statementName = new UTF8BackedString(statementName); + DescribeMessage({String statementName = ''}) { + this.statementName = UTF8BackedString(statementName); } UTF8BackedString statementName; + @override int get length { return 7 + statementName.utf8Length; } + @override void applyToBuffer(ByteDataWriter buffer) { buffer.writeUint8(ClientMessage.DescribeIdentifier); buffer.writeUint32(length - 1); @@ -176,9 +187,9 @@ class DescribeMessage extends ClientMessage { } class BindMessage extends ClientMessage { - BindMessage(this.parameters, {String statementName: ""}) { + BindMessage(this.parameters, {String statementName = ''}) { typeSpecCount = parameters.where((p) => p.isBinary).length; - this.statementName = new UTF8BackedString(statementName); + this.statementName = UTF8BackedString(statementName); } List parameters; @@ -187,6 +198,7 @@ class BindMessage extends ClientMessage { int typeSpecCount; int _cachedLength; + @override int get length { if (_cachedLength == null) { var inputParameterElementCount = parameters.length; @@ -197,7 +209,8 @@ class BindMessage extends ClientMessage { _cachedLength = 15; _cachedLength += statementName.utf8Length; _cachedLength += inputParameterElementCount * 2; - _cachedLength += parameters.fold(0, (len, ParameterValue paramValue) { + _cachedLength += + parameters.fold(0, (len, ParameterValue paramValue) { if (paramValue.bytes == null) { return len + 4; } else { @@ -208,6 +221,7 @@ class BindMessage extends ClientMessage { return _cachedLength; } + @override void applyToBuffer(ByteDataWriter buffer) { buffer.writeUint8(ClientMessage.BindIdentifier); buffer.writeUint32(length - 1); @@ -256,10 +270,12 @@ class BindMessage extends ClientMessage { class ExecuteMessage extends ClientMessage { ExecuteMessage(); + @override int get length { return 10; } + @override void applyToBuffer(ByteDataWriter buffer) { buffer.writeUint8(ClientMessage.ExecuteIdentifier); buffer.writeUint32(length - 1); @@ -271,10 +287,12 @@ class ExecuteMessage extends ClientMessage { class SyncMessage extends ClientMessage { SyncMessage(); + @override int get length { return 5; } + @override void applyToBuffer(ByteDataWriter buffer) { buffer.writeUint8(ClientMessage.SyncIdentifier); buffer.writeUint32(4); diff --git a/lib/src/connection.dart b/lib/src/connection.dart index 72dfe81..3b1eefd 100644 --- a/lib/src/connection.dart +++ b/lib/src/connection.dart @@ -4,15 +4,15 @@ import 'dart:async'; import 'dart:io'; import 'dart:typed_data'; -import 'package:postgres/src/query_cache.dart'; -import 'package:postgres/src/execution_context.dart'; -import 'package:postgres/src/query_queue.dart'; +import 'package:buffer/buffer.dart'; +import 'client_messages.dart'; +import 'execution_context.dart'; import 'message_window.dart'; import 'query.dart'; - +import 'query_cache.dart'; +import 'query_queue.dart'; import 'server_messages.dart'; -import 'client_messages.dart'; part 'connection_fsm.dart'; @@ -38,18 +38,18 @@ class PostgreSQLConnection extends Object /// [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: null, - this.password: null, - this.timeoutInSeconds: 30, - this.queryTimeoutInSeconds: 30, - this.timeZone: "UTC", - this.useSSL: false}) { - _connectionState = new _PostgreSQLConnectionStateClosed(); + {this.username, + this.password, + this.timeoutInSeconds = 30, + this.queryTimeoutInSeconds = 30, + this.timeZone = 'UTC', + this.useSSL = false}) { + _connectionState = _PostgreSQLConnectionStateClosed(); _connectionState.connection = this; } final StreamController _notifications = - new StreamController.broadcast(); + StreamController.broadcast(); /// Hostname of database this connection refers to. final String host; @@ -92,7 +92,7 @@ class PostgreSQLConnection extends Object /// Whether or not this connection is open or not. /// /// This is `true` when this instance is first created and after it has been closed or encountered an unrecoverable error. - /// If a connection has already been opened and this value is now true, the connection cannot be reopened and a new instance + /// If a connection has already been opened and this value is now true, the connection cannot be reopened and a instance /// must be created. bool get isClosed => _connectionState is _PostgreSQLConnectionStateClosed; @@ -102,9 +102,9 @@ class PostgreSQLConnection extends Object /// Prior to connection, it is the empty map. final Map settings = {}; - QueryCache _cache = new QueryCache(); + final _cache = QueryCache(); Socket _socket; - MessageFramer _framer = new MessageFramer(); + MessageFramer _framer = MessageFramer(); int _processID; // ignore: unused_field int _secretKey; @@ -113,8 +113,10 @@ class PostgreSQLConnection extends Object bool _hasConnectedPreviously = false; _PostgreSQLConnectionState _connectionState; + @override PostgreSQLExecutionContext get _transaction => null; + @override PostgreSQLConnection get _connection => this; /// Establishes a connection with a PostgreSQL database. @@ -127,32 +129,31 @@ class PostgreSQLConnection extends Object /// opened and this method is called, an exception will be thrown. Future open() async { if (_hasConnectedPreviously) { - throw new PostgreSQLException( - "Attempting to reopen a closed connection. Create a new instance instead."); + throw PostgreSQLException( + 'Attempting to reopen a closed connection. Create a instance instead.'); } try { _hasConnectedPreviously = true; _socket = await Socket.connect(host, port) - .timeout(new Duration(seconds: timeoutInSeconds)); + .timeout(Duration(seconds: timeoutInSeconds)); - _framer = new MessageFramer(); + _framer = MessageFramer(); if (useSSL) { _socket = await _upgradeSocketToSSL(_socket, timeout: timeoutInSeconds); } - var connectionComplete = new Completer(); - _socket.listen(_readData, - onError: (err, st) => _close(err, st), onDone: () => _close()); + final connectionComplete = Completer(); + _socket.listen(_readData, onError: _close, onDone: _close); _transitionToState( - new _PostgreSQLConnectionStateSocketConnected(connectionComplete)); + _PostgreSQLConnectionStateSocketConnected(connectionComplete)); await connectionComplete.future - .timeout(new Duration(seconds: timeoutInSeconds)); + .timeout(Duration(seconds: timeoutInSeconds)); } on TimeoutException catch (e, st) { - final err = new PostgreSQLException( - "Failed to connect to database $host:$port/$databaseName failed to connect."); + final err = PostgreSQLException( + 'Failed to connect to database $host:$port/$databaseName failed to connect.'); await _close(err, st); rethrow; } catch (e, st) { @@ -195,22 +196,22 @@ class PostgreSQLConnection extends Object /// If specified, the final `"COMMIT"` query of the transaction will use /// [commitTimeoutInSeconds] as its timeout, otherwise the connection's /// default query timeout will be used. - Future transaction( - Future queryBlock(PostgreSQLExecutionContext connection), + Future transaction(Future queryBlock(PostgreSQLExecutionContext connection), {int commitTimeoutInSeconds}) async { if (isClosed) { - throw new PostgreSQLException( - "Attempting to execute query, but connection is not open."); + throw PostgreSQLException( + 'Attempting to execute query, but connection is not open.'); } - var proxy = new _TransactionProxy(this, queryBlock, commitTimeoutInSeconds); + final proxy = _TransactionProxy(this, queryBlock, commitTimeoutInSeconds); await _enqueue(proxy.beginQuery); return await proxy.completer.future; } - void cancelTransaction({String reason: null}) { + @override + void cancelTransaction({String reason}) { // Default is no-op } @@ -231,7 +232,7 @@ class PostgreSQLConnection extends Object } Future _close([dynamic error, StackTrace trace]) async { - _connectionState = new _PostgreSQLConnectionStateClosed(); + _connectionState = _PostgreSQLConnectionStateClosed(); await _socket?.close(); await _notifications?.close(); @@ -245,15 +246,15 @@ class PostgreSQLConnection extends Object // and the state node managing delivering data to the query no longer exists. Therefore, // as soon as a close occurs, we detach the data stream from anything that actually does // anything with that data. - _framer.addBytes(bytes); + _framer.addBytes(castBytes(bytes)); while (_framer.hasMessage) { - var msg = _framer.popMessage().message; + final msg = _framer.popMessage().message; try { if (msg is ErrorResponseMessage) { _transitionToState(_connectionState.onErrorResponse(msg)); } else if (msg is NotificationResponseMessage) { _notifications - .add(new Notification(msg.processID, msg.channel, msg.payload)); + .add(Notification(msg.processID, msg.channel, msg.payload)); } else { _transitionToState(_connectionState.onMessage(msg)); } @@ -263,41 +264,39 @@ class PostgreSQLConnection extends Object } } - Future _upgradeSocketToSSL(Socket originalSocket, {int timeout: 30}) { - var sslCompleter = new Completer(); - - originalSocket.listen( - (data) { - if (data.length != 1) { - sslCompleter.completeError(new PostgreSQLException( - "Could not initalize SSL connection, received unknown byte stream.")); - return; - } - - sslCompleter.complete(data.first); - }, - onDone: () => sslCompleter.completeError(new PostgreSQLException( - "Could not initialize SSL connection, connection closed during handshake.")), - onError: (err) { - sslCompleter.completeError(err); - }); - - var byteBuffer = new ByteData(8); + Future _upgradeSocketToSSL(Socket originalSocket, + {int timeout = 30}) { + final sslCompleter = Completer(); + + originalSocket.listen((data) { + if (data.length != 1) { + sslCompleter.completeError(PostgreSQLException( + 'Could not initalize SSL connection, received unknown byte stream.')); + return; + } + + sslCompleter.complete(data.first); + }, + onDone: () => sslCompleter.completeError(PostgreSQLException( + 'Could not initialize SSL connection, connection closed during handshake.')), + onError: sslCompleter.completeError); + + final byteBuffer = ByteData(8); byteBuffer.setUint32(0, 8); byteBuffer.setUint32(4, 80877103); originalSocket.add(byteBuffer.buffer.asUint8List()); return sslCompleter.future - .timeout(new Duration(seconds: timeout)) + .timeout(Duration(seconds: timeout)) .then((responseByte) { if (responseByte != 83) { - throw new PostgreSQLException( - "The database server is not accepting SSL connections."); + throw PostgreSQLException( + 'The database server is not accepting SSL connections.'); } return SecureSocket.secure(originalSocket, onBadCertificate: (certificate) => true) - .timeout(new Duration(seconds: timeout)); + .timeout(Duration(seconds: timeout)); }); } } @@ -327,26 +326,28 @@ class Notification { abstract class _PostgreSQLExecutionContextMixin implements PostgreSQLExecutionContext { - Map _tableOIDNameMap = {}; - QueryQueue _queue = new QueryQueue(); + final _tableOIDNameMap = {}; + final _queue = QueryQueue(); PostgreSQLConnection get _connection; PostgreSQLExecutionContext get _transaction; + @override int get queueSize => _queue.length; + @override Future>> query(String fmtString, - {Map substitutionValues: null, - bool allowReuse: true, + {Map substitutionValues, + bool allowReuse = true, int timeoutInSeconds}) async { timeoutInSeconds ??= _connection.queryTimeoutInSeconds; if (_connection.isClosed) { - throw new PostgreSQLException( - "Attempting to execute query, but connection is not open."); + throw PostgreSQLException( + 'Attempting to execute query, but connection is not open.'); } - var query = new Query>>( + final query = Query>>( fmtString, substitutionValues, _connection, _transaction); if (allowReuse) { query.statementIdentifier = _connection._cache.identifierForQuery(query); @@ -355,18 +356,19 @@ abstract class _PostgreSQLExecutionContextMixin return _enqueue(query, timeoutInSeconds: timeoutInSeconds); } + @override Future>>> mappedResultsQuery( String fmtString, - {Map substitutionValues: null, - bool allowReuse: true, + {Map substitutionValues, + bool allowReuse = true, int timeoutInSeconds}) async { timeoutInSeconds ??= _connection.queryTimeoutInSeconds; if (_connection.isClosed) { - throw new PostgreSQLException( - "Attempting to execute query, but connection is not open."); + throw PostgreSQLException( + 'Attempting to execute query, but connection is not open.'); } - var query = new Query>>( + final query = Query>>( fmtString, substitutionValues, _connection, _transaction); if (allowReuse) { query.statementIdentifier = _connection._cache.identifierForQuery(query); @@ -377,29 +379,31 @@ abstract class _PostgreSQLExecutionContextMixin return _mapifyRows(rows, query.fieldDescriptions); } + @override Future execute(String fmtString, - {Map substitutionValues: null, int timeoutInSeconds}) { + {Map substitutionValues, int timeoutInSeconds}) { timeoutInSeconds ??= _connection.queryTimeoutInSeconds; if (_connection.isClosed) { - throw new PostgreSQLException( - "Attempting to execute query, but connection is not open."); + throw PostgreSQLException( + 'Attempting to execute query, but connection is not open.'); } - var query = - new Query(fmtString, substitutionValues, _connection, _transaction) + final query = + Query(fmtString, substitutionValues, _connection, _transaction) ..onlyReturnAffectedRowCount = true; return _enqueue(query, timeoutInSeconds: timeoutInSeconds); } - void cancelTransaction({String reason: null}); + @override + void cancelTransaction({String reason}); Future>>> _mapifyRows( List> rows, List columns) async { //todo (joeconwaystk): If this was a cached query, resolving is table oids is unnecessary. // It's not a significant impact here, but an area for optimization. This includes // assigning resolvedTableName - final tableOIDs = new Set.from(columns.map((f) => f.tableID)); + final tableOIDs = Set.from(columns.map((f) => f.tableID)); final List unresolvedTableOIDs = tableOIDs .where((oid) => oid != null && oid > 0 && !_tableOIDNameMap.containsKey(oid)) @@ -416,10 +420,8 @@ abstract class _PostgreSQLExecutionContextMixin final tableNames = tableOIDs.map((oid) => _tableOIDNameMap[oid]).toList(); return rows.map((row) { - var rowMap = new Map>.fromIterable( - tableNames, - key: (name) => name, - value: (_) => {}); + final rowMap = Map>.fromIterable(tableNames, + key: (name) => name as String, value: (_) => {}); final iterator = columns.iterator; row.forEach((column) { @@ -433,7 +435,7 @@ abstract class _PostgreSQLExecutionContextMixin } Future _resolveTableOIDs(List oids) async { - final unresolvedIDString = oids.join(","); + final unresolvedIDString = oids.join(','); final orderedTableNames = await query( "SELECT relname FROM pg_class WHERE relkind='r' AND oid IN ($unresolvedIDString) ORDER BY oid ASC"); @@ -441,18 +443,18 @@ abstract class _PostgreSQLExecutionContextMixin orderedTableNames.forEach((tableName) { iterator.moveNext(); if (tableName.first != null) { - _tableOIDNameMap[iterator.current] = tableName.first; + _tableOIDNameMap[iterator.current] = tableName.first as String; } }); } - Future _enqueue(Query query, {int timeoutInSeconds: 30}) async { + Future _enqueue(Query query, {int timeoutInSeconds = 30}) async { if (_queue.add(query)) { _connection._transitionToState(_connection._connectionState.awake()); try { final result = - await query.future.timeout(new Duration(seconds: timeoutInSeconds)); + await query.future.timeout(Duration(seconds: timeoutInSeconds)); _connection._cache.add(query); _queue.remove(query); return result; @@ -466,7 +468,7 @@ abstract class _PostgreSQLExecutionContextMixin // the caller behaves correctly in this condition. otherwise, // the caller would complete synchronously. This future // will always complete as a cancellation error. - return new Future(() async => query.future); + return Future(() async => query.future); } } diff --git a/lib/src/connection_fsm.dart b/lib/src/connection_fsm.dart index 27409ad..3dbb2b4 100644 --- a/lib/src/connection_fsm.dart +++ b/lib/src/connection_fsm.dart @@ -16,11 +16,11 @@ abstract class _PostgreSQLConnectionState { } _PostgreSQLConnectionState onErrorResponse(ErrorResponseMessage message) { - var exception = new PostgreSQLException._(message.fields); + final exception = PostgreSQLException._(message.fields); if (exception.severity == PostgreSQLSeverity.fatal || exception.severity == PostgreSQLSeverity.panic) { - return new _PostgreSQLConnectionStateClosed(); + return _PostgreSQLConnectionStateClosed(); } return this; @@ -45,8 +45,9 @@ class _PostgreSQLConnectionStateSocketConnected Completer completer; + @override _PostgreSQLConnectionState onEnter() { - var startupMessage = new StartupMessage( + final startupMessage = StartupMessage( connection.databaseName, connection.timeZone, username: connection.username); @@ -55,30 +56,32 @@ class _PostgreSQLConnectionStateSocketConnected return this; } + @override _PostgreSQLConnectionState onErrorResponse(ErrorResponseMessage message) { - var exception = new PostgreSQLException._(message.fields); + final exception = PostgreSQLException._(message.fields); completer.completeError(exception); - return new _PostgreSQLConnectionStateClosed(); + return _PostgreSQLConnectionStateClosed(); } + @override _PostgreSQLConnectionState onMessage(ServerMessage message) { - AuthenticationMessage authMessage = message; + final authMessage = message as AuthenticationMessage; // Pass on the pending op to subsequent stages if (authMessage.type == AuthenticationMessage.KindOK) { - return new _PostgreSQLConnectionStateAuthenticated(completer); + return _PostgreSQLConnectionStateAuthenticated(completer); } else if (authMessage.type == AuthenticationMessage.KindMD5Password) { connection._salt = authMessage.salt; - return new _PostgreSQLConnectionStateAuthenticating(completer); + return _PostgreSQLConnectionStateAuthenticating(completer); } - completer.completeError(new PostgreSQLException( - "Unsupported authentication type ${authMessage.type}, closing connection.")); + completer.completeError(PostgreSQLException( + 'Unsupported authentication type ${authMessage.type}, closing connection.')); - return new _PostgreSQLConnectionStateClosed(); + return _PostgreSQLConnectionStateClosed(); } } @@ -92,8 +95,9 @@ class _PostgreSQLConnectionStateAuthenticating Completer completer; + @override _PostgreSQLConnectionState onEnter() { - var authMessage = new AuthMD5Message( + final authMessage = AuthMD5Message( connection.username, connection.password, connection._salt); connection._socket.add(authMessage.asBytes()); @@ -101,14 +105,16 @@ class _PostgreSQLConnectionStateAuthenticating return this; } + @override _PostgreSQLConnectionState onErrorResponse(ErrorResponseMessage message) { - var exception = new PostgreSQLException._(message.fields); + final exception = PostgreSQLException._(message.fields); completer.completeError(exception); - return new _PostgreSQLConnectionStateClosed(); + return _PostgreSQLConnectionStateClosed(); } + @override _PostgreSQLConnectionState onMessage(ServerMessage message) { if (message is ParameterStatusMessage) { connection.settings[message.name] = message.value; @@ -117,7 +123,7 @@ class _PostgreSQLConnectionStateAuthenticating connection._secretKey = message.secretKey; } else if (message is ReadyForQueryMessage) { if (message.state == ReadyForQueryMessage.StateIdle) { - return new _PostgreSQLConnectionStateIdle(openCompleter: completer); + return _PostgreSQLConnectionStateIdle(openCompleter: completer); } } @@ -135,14 +141,16 @@ class _PostgreSQLConnectionStateAuthenticated Completer completer; + @override _PostgreSQLConnectionState onErrorResponse(ErrorResponseMessage message) { - var exception = new PostgreSQLException._(message.fields); + final exception = PostgreSQLException._(message.fields); completer.completeError(exception); - return new _PostgreSQLConnectionStateClosed(); + return _PostgreSQLConnectionStateClosed(); } + @override _PostgreSQLConnectionState onMessage(ServerMessage message) { if (message is ParameterStatusMessage) { connection.settings[message.name] = message.value; @@ -151,7 +159,7 @@ class _PostgreSQLConnectionStateAuthenticated connection._secretKey = message.secretKey; } else if (message is ReadyForQueryMessage) { if (message.state == ReadyForQueryMessage.StateIdle) { - return new _PostgreSQLConnectionStateIdle(openCompleter: completer); + return _PostgreSQLConnectionStateIdle(openCompleter: completer); } } @@ -168,8 +176,9 @@ class _PostgreSQLConnectionStateIdle extends _PostgreSQLConnectionState { Completer openCompleter; + @override _PostgreSQLConnectionState awake() { - var pendingQuery = connection._queue.pending; + final pendingQuery = connection._queue.pending; if (pendingQuery != null) { return processQuery(pendingQuery); } @@ -181,29 +190,31 @@ class _PostgreSQLConnectionStateIdle extends _PostgreSQLConnectionState { try { if (q.onlyReturnAffectedRowCount) { q.sendSimple(connection._socket); - return new _PostgreSQLConnectionStateBusy(q); + return _PostgreSQLConnectionStateBusy(q); } final cached = connection._cache[q.statement]; q.sendExtended(connection._socket, cacheQuery: cached); - return new _PostgreSQLConnectionStateBusy(q); + return _PostgreSQLConnectionStateBusy(q); } catch (e, st) { scheduleMicrotask(() { q.completeError(e, st); - connection._transitionToState(new _PostgreSQLConnectionStateIdle()); + connection._transitionToState(_PostgreSQLConnectionStateIdle()); }); - return new _PostgreSQLConnectionStateDeferredFailure(); + return _PostgreSQLConnectionStateDeferredFailure(); } } + @override _PostgreSQLConnectionState onEnter() { openCompleter?.complete(); return awake(); } + @override _PostgreSQLConnectionState onMessage(ServerMessage message) { return this; } @@ -217,24 +228,26 @@ class _PostgreSQLConnectionStateBusy extends _PostgreSQLConnectionState { _PostgreSQLConnectionStateBusy(this.query); Query query; - PostgreSQLException returningException = null; + PostgreSQLException returningException; int rowsAffected = 0; + @override _PostgreSQLConnectionState onErrorResponse(ErrorResponseMessage message) { // If we get an error here, then we should eat the rest of the messages // and we are always confirmed to get a _ReadyForQueryMessage to finish up. // We should only report the error once that is done. - var exception = new PostgreSQLException._(message.fields); + final exception = PostgreSQLException._(message.fields); returningException ??= exception; if (exception.severity == PostgreSQLSeverity.fatal || exception.severity == PostgreSQLSeverity.panic) { - return new _PostgreSQLConnectionStateClosed(); + return _PostgreSQLConnectionStateClosed(); } return this; } + @override _PostgreSQLConnectionState onMessage(ServerMessage message) { // We ignore NoData, as it doesn't tell us anything we don't already know // or care about. @@ -244,8 +257,8 @@ class _PostgreSQLConnectionStateBusy extends _PostgreSQLConnectionState { if (message is ReadyForQueryMessage) { if (message.state == ReadyForQueryMessage.StateTransactionError) { query.completeError(returningException); - return new _PostgreSQLConnectionStateReadyInTransaction( - query.transaction); + return _PostgreSQLConnectionStateReadyInTransaction( + query.transaction as _TransactionProxy); } if (returningException != null) { @@ -255,11 +268,11 @@ class _PostgreSQLConnectionStateBusy extends _PostgreSQLConnectionState { } if (message.state == ReadyForQueryMessage.StateTransaction) { - return new _PostgreSQLConnectionStateReadyInTransaction( - query.transaction); + return _PostgreSQLConnectionStateReadyInTransaction( + query.transaction as _TransactionProxy); } - return new _PostgreSQLConnectionStateIdle(); + return _PostgreSQLConnectionStateIdle(); } else if (message is CommandCompleteMessage) { rowsAffected = message.rowsAffected; } else if (message is RowDescriptionMessage) { @@ -267,7 +280,7 @@ class _PostgreSQLConnectionStateBusy extends _PostgreSQLConnectionState { } else if (message is DataRowMessage) { query.addRow(message.values); } else if (message is ParameterDescriptionMessage) { - var validationException = + final validationException = query.validateParameters(message.parameterTypeIDs); if (validationException != null) { query.cache = null; @@ -287,12 +300,14 @@ class _PostgreSQLConnectionStateReadyInTransaction _TransactionProxy transaction; + @override _PostgreSQLConnectionState onEnter() { return awake(); } + @override _PostgreSQLConnectionState awake() { - var pendingQuery = transaction._queue.pending; + final pendingQuery = transaction._queue.pending; if (pendingQuery != null) { return processQuery(pendingQuery); } @@ -304,13 +319,13 @@ class _PostgreSQLConnectionStateReadyInTransaction try { if (q.onlyReturnAffectedRowCount) { q.sendSimple(connection._socket); - return new _PostgreSQLConnectionStateBusy(q); + return _PostgreSQLConnectionStateBusy(q); } final cached = connection._cache[q.statement]; q.sendExtended(connection._socket, cacheQuery: cached); - return new _PostgreSQLConnectionStateBusy(q); + return _PostgreSQLConnectionStateBusy(q); } catch (e, st) { scheduleMicrotask(() { q.completeError(e, st); diff --git a/lib/src/exceptions.dart b/lib/src/exceptions.dart index e0a5bc7..44e6a00 100644 --- a/lib/src/exceptions.dart +++ b/lib/src/exceptions.dart @@ -34,16 +34,13 @@ enum PostgreSQLSeverity { /// Exception thrown by [PostgreSQLConnection] instances. class PostgreSQLException implements Exception { - PostgreSQLException(String message, - {PostgreSQLSeverity severity: PostgreSQLSeverity.error, - this.stackTrace}) { - this.severity = severity; - this.message = message; - code = ""; + PostgreSQLException(this.message, + {this.severity = PostgreSQLSeverity.error, this.stackTrace}) { + code = ''; } PostgreSQLException._(List errorFields, {this.stackTrace}) { - var finder = (int identifer) => (errorFields.firstWhere( + final finder = (int identifer) => (errorFields.firstWhere( (ErrorField e) => e.identificationToken == identifer, orElse: () => null)); @@ -111,27 +108,28 @@ class PostgreSQLException implements Exception { /// A [StackTrace] if available. StackTrace stackTrace; + @override String toString() { - var buff = new StringBuffer("$severity $code: $message "); + final buff = StringBuffer('$severity $code: $message '); if (detail != null) { - buff.write("Detail: $detail "); + buff.write('Detail: $detail '); } if (hint != null) { - buff.write("Hint: $hint "); + buff.write('Hint: $hint '); } if (tableName != null) { - buff.write("Table: $tableName "); + buff.write('Table: $tableName '); } if (columnName != null) { - buff.write("Column: $columnName "); + buff.write('Column: $columnName '); } if (constraintName != null) { - buff.write("Constraint $constraintName "); + buff.write('Constraint $constraintName '); } return buff.toString(); diff --git a/lib/src/execution_context.dart b/lib/src/execution_context.dart index 3ec8bed..e526625 100644 --- a/lib/src/execution_context.dart +++ b/lib/src/execution_context.dart @@ -1,8 +1,9 @@ import 'dart:async'; + +import 'connection.dart'; import 'query.dart'; -import 'types.dart'; import 'substituter.dart'; -import 'connection.dart'; +import 'types.dart'; abstract class PostgreSQLExecutionContext { /// Returns this context queue size @@ -29,8 +30,8 @@ abstract class PostgreSQLExecutionContext { /// anything to opt in to this behavior, this connection will track the necessary information required to reuse queries without intervention. (The [fmtString] is /// the unique identifier to look up reuse information.) You can disable reuse by passing false for [allowReuse]. Future>> query(String fmtString, - {Map substitutionValues: null, - bool allowReuse: true, + {Map substitutionValues, + bool allowReuse = true, int timeoutInSeconds}); /// Executes a query on this context. @@ -41,13 +42,13 @@ abstract class PostgreSQLExecutionContext { /// for executing queries in the PostgreSQL protocol; [query] is preferred for queries that will be executed more than once, will contain user input, /// or return rows. Future execute(String fmtString, - {Map substitutionValues: null, int timeoutInSeconds}); + {Map substitutionValues, int timeoutInSeconds}); /// Cancels a transaction on this context. /// /// If this context is an instance of [PostgreSQLConnection], this method has no effect. If the context is a transaction context (passed as the argument /// to [PostgreSQLConnection.transaction]), this will rollback the transaction. - void cancelTransaction({String reason: null}); + void cancelTransaction({String reason}); /// Executes a query on this connection and returns each row as a [Map]. /// @@ -81,7 +82,7 @@ abstract class PostgreSQLExecutionContext { /// ] Future>>> mappedResultsQuery( String fmtString, - {Map substitutionValues: null, - bool allowReuse: true, + {Map substitutionValues, + bool allowReuse = true, int timeoutInSeconds}); } diff --git a/lib/src/message_window.dart b/lib/src/message_window.dart index 130f044..3c0d193 100644 --- a/lib/src/message_window.dart +++ b/lib/src/message_window.dart @@ -8,19 +8,19 @@ import 'server_messages.dart'; class MessageFrame { static const int HeaderByteSize = 5; static Map messageTypeMap = { - 49: () => new ParseCompleteMessage(), - 50: () => new BindCompleteMessage(), - 65: () => new NotificationResponseMessage(), - 67: () => new CommandCompleteMessage(), - 68: () => new DataRowMessage(), - 69: () => new ErrorResponseMessage(), - 75: () => new BackendKeyMessage(), - 82: () => new AuthenticationMessage(), - 83: () => new ParameterStatusMessage(), - 84: () => new RowDescriptionMessage(), - 90: () => new ReadyForQueryMessage(), - 110: () => new NoDataMessage(), - 116: () => new ParameterDescriptionMessage() + 49: () => ParseCompleteMessage(), + 50: () => BindCompleteMessage(), + 65: () => NotificationResponseMessage(), + 67: () => CommandCompleteMessage(), + 68: () => DataRowMessage(), + 69: () => ErrorResponseMessage(), + 75: () => BackendKeyMessage(), + 82: () => AuthenticationMessage(), + 83: () => ParameterStatusMessage(), + 84: () => RowDescriptionMessage(), + 90: () => ReadyForQueryMessage(), + 110: () => NoDataMessage(), + 116: () => ParameterDescriptionMessage() }; bool get hasReadHeader => type != null; @@ -31,19 +31,19 @@ class MessageFrame { Uint8List data; ServerMessage get message { - var msgMaker = - messageTypeMap[type] ?? () => new UnknownMessage()..code = type; + final msgMaker = + messageTypeMap[type] ?? () => UnknownMessage()..code = type; - ServerMessage msg = msgMaker(); + final msg = msgMaker() as ServerMessage; msg.readBytes(data); return msg; } } class MessageFramer { - final _reader = new ByteDataReader(); - MessageFrame messageInProgress = new MessageFrame(); - final messageQueue = new Queue(); + final _reader = ByteDataReader(); + MessageFrame messageInProgress = MessageFrame(); + final messageQueue = Queue(); void addBytes(Uint8List bytes) { _reader.add(bytes); @@ -65,7 +65,7 @@ class MessageFramer { if (messageInProgress.isComplete) { messageQueue.add(messageInProgress); - messageInProgress = new MessageFrame(); + messageInProgress = MessageFrame(); evaluateNextMessage = true; } } diff --git a/lib/src/query.dart b/lib/src/query.dart index d99d455..b76247b 100644 --- a/lib/src/query.dart +++ b/lib/src/query.dart @@ -3,14 +3,15 @@ import 'dart:convert'; import 'dart:io'; import 'dart:typed_data'; -import 'package:postgres/src/binary_codec.dart'; -import 'package:postgres/src/execution_context.dart'; +import 'package:buffer/buffer.dart'; -import 'package:postgres/src/text_codec.dart'; -import 'types.dart'; +import 'binary_codec.dart'; +import 'client_messages.dart'; import 'connection.dart'; +import 'execution_context.dart'; import 'substituter.dart'; -import 'client_messages.dart'; +import 'text_codec.dart'; +import 'types.dart'; class Query { Query(this.statement, this.substitutionValues, this.connection, @@ -32,24 +33,25 @@ class Query { CachedQuery cache; - Completer _onComplete = new Completer.sync(); + final _onComplete = Completer.sync(); List _fieldDescriptions; List get fieldDescriptions => _fieldDescriptions; - void set fieldDescriptions(List fds) { + set fieldDescriptions(List fds) { _fieldDescriptions = fds; cache?.fieldDescriptions = fds; } void sendSimple(Socket socket) { - var sqlString = PostgreSQLFormat.substitute(statement, substitutionValues); - var queryMessage = new QueryMessage(sqlString); + final sqlString = + PostgreSQLFormat.substitute(statement, substitutionValues); + final queryMessage = QueryMessage(sqlString); socket.add(queryMessage.asBytes()); } - void sendExtended(Socket socket, {CachedQuery cacheQuery: null}) { + void sendExtended(Socket socket, {CachedQuery cacheQuery}) { if (cacheQuery != null) { fieldDescriptions = cacheQuery.fieldDescriptions; sendCachedQuery(socket, cacheQuery, substitutionValues); @@ -57,31 +59,31 @@ class Query { return; } - String statementName = (statementIdentifier ?? ""); - var formatIdentifiers = []; - var sqlString = PostgreSQLFormat.substitute(statement, substitutionValues, + final statementName = statementIdentifier ?? ''; + final formatIdentifiers = []; + final sqlString = PostgreSQLFormat.substitute(statement, substitutionValues, replace: (PostgreSQLFormatIdentifier identifier, int index) { formatIdentifiers.add(identifier); - return "\$$index"; + return '\$$index'; }); specifiedParameterTypeCodes = formatIdentifiers.map((i) => i.type).toList(); - var parameterList = formatIdentifiers - .map((id) => new ParameterValue(id, substitutionValues)) + final parameterList = formatIdentifiers + .map((id) => ParameterValue(id, substitutionValues)) .toList(); - var messages = [ - new ParseMessage(sqlString, statementName: statementName), - new DescribeMessage(statementName: statementName), - new BindMessage(parameterList, statementName: statementName), - new ExecuteMessage(), - new SyncMessage() + final messages = [ + ParseMessage(sqlString, statementName: statementName), + DescribeMessage(statementName: statementName), + BindMessage(parameterList, statementName: statementName), + ExecuteMessage(), + SyncMessage(), ]; if (statementIdentifier != null) { - cache = new CachedQuery(statementIdentifier, formatIdentifiers); + cache = CachedQuery(statementIdentifier, formatIdentifiers); } socket.add(ClientMessage.aggregateBytes(messages)); @@ -89,23 +91,23 @@ class Query { void sendCachedQuery(Socket socket, CachedQuery cacheQuery, Map substitutionValues) { - var statementName = cacheQuery.preparedStatementName; - var parameterList = cacheQuery.orderedParameters - .map((identifier) => new ParameterValue(identifier, substitutionValues)) + final statementName = cacheQuery.preparedStatementName; + final parameterList = cacheQuery.orderedParameters + .map((identifier) => ParameterValue(identifier, substitutionValues)) .toList(); - var bytes = ClientMessage.aggregateBytes([ - new BindMessage(parameterList, statementName: statementName), - new ExecuteMessage(), - new SyncMessage() + final bytes = ClientMessage.aggregateBytes([ + BindMessage(parameterList, statementName: statementName), + ExecuteMessage(), + SyncMessage() ]); socket.add(bytes); } PostgreSQLException validateParameters(List parameterTypeIDs) { - var actualParameterTypeCodeIterator = parameterTypeIDs.iterator; - var parametersAreMismatched = + final actualParameterTypeCodeIterator = parameterTypeIDs.iterator; + final parametersAreMismatched = specifiedParameterTypeCodes.map((specifiedType) { actualParameterTypeCodeIterator.moveNext(); @@ -119,8 +121,8 @@ class Query { }).any((v) => v == false); if (parametersAreMismatched) { - return new PostgreSQLException( - "Specified parameter types do not match column parameter types in query ${statement}"); + return PostgreSQLException( + 'Specified parameter types do not match column parameter types in query $statement'); } return null; @@ -131,8 +133,8 @@ class Query { return; } - var iterator = fieldDescriptions.iterator; - var lazyDecodedData = rawRowData.map((bd) { + final iterator = fieldDescriptions.iterator; + final lazyDecodedData = rawRowData.map((bd) { iterator.moveNext(); return iterator.current.converter @@ -163,6 +165,7 @@ class Query { _onComplete.completeError(error, stackTrace); } + @override String toString() => statement; } @@ -184,24 +187,24 @@ class ParameterValue { factory ParameterValue(PostgreSQLFormatIdentifier identifier, Map substitutionValues) { if (identifier.type == null) { - return new ParameterValue.text(substitutionValues[identifier.name]); + return ParameterValue.text(substitutionValues[identifier.name]); } - return new ParameterValue.binary( + return ParameterValue.binary( substitutionValues[identifier.name], identifier.type); } ParameterValue.binary(dynamic value, PostgreSQLDataType postgresType) : isBinary = true { - final converter = new PostgresBinaryEncoder(postgresType); + final converter = PostgresBinaryEncoder(postgresType); bytes = converter.convert(value); length = bytes?.length ?? 0; } ParameterValue.text(dynamic value) : isBinary = false { if (value != null) { - final converter = new PostgresTextEncoder(false); - bytes = utf8.encode(converter.convert(value)); + final converter = PostgresTextEncoder(false); + bytes = castBytes(utf8.encode(converter.convert(value))); } length = bytes?.length; } @@ -225,9 +228,9 @@ class FieldDescription { String resolvedTableName; int parse(ByteData byteData, int initialOffset) { - var offset = initialOffset; - var buf = new StringBuffer(); - var byte = 0; + int offset = initialOffset; + final buf = StringBuffer(); + int byte = 0; do { byte = byteData.getUint8(offset); offset += 1; @@ -251,13 +254,14 @@ class FieldDescription { formatCode = byteData.getUint16(offset); offset += 2; - converter = new PostgresBinaryDecoder(typeID); + converter = PostgresBinaryDecoder(typeID); return offset; } + @override String toString() { - return "$fieldName $tableID $columnID $typeID $dataTypeSize $typeModifier $formatCode"; + return '$fieldName $tableID $columnID $typeID $dataTypeSize $typeModifier $formatCode'; } } @@ -270,50 +274,50 @@ class PostgreSQLFormatToken { PostgreSQLFormatToken(this.type); PostgreSQLFormatTokenType type; - StringBuffer buffer = new StringBuffer(); + StringBuffer buffer = StringBuffer(); } class PostgreSQLFormatIdentifier { static Map typeStringToCodeMap = { - "text": PostgreSQLDataType.text, - "int2": PostgreSQLDataType.smallInteger, - "int4": PostgreSQLDataType.integer, - "int8": PostgreSQLDataType.bigInteger, - "float4": PostgreSQLDataType.real, - "float8": PostgreSQLDataType.double, - "boolean": PostgreSQLDataType.boolean, - "date": PostgreSQLDataType.date, - "timestamp": PostgreSQLDataType.timestampWithoutTimezone, - "timestamptz": PostgreSQLDataType.timestampWithTimezone, - "jsonb": PostgreSQLDataType.json, - "bytea": PostgreSQLDataType.byteArray, - "name": PostgreSQLDataType.name, - "uuid": PostgreSQLDataType.uuid + 'text': PostgreSQLDataType.text, + 'int2': PostgreSQLDataType.smallInteger, + 'int4': PostgreSQLDataType.integer, + 'int8': PostgreSQLDataType.bigInteger, + 'float4': PostgreSQLDataType.real, + 'float8': PostgreSQLDataType.double, + 'boolean': PostgreSQLDataType.boolean, + 'date': PostgreSQLDataType.date, + 'timestamp': PostgreSQLDataType.timestampWithoutTimezone, + 'timestamptz': PostgreSQLDataType.timestampWithTimezone, + 'jsonb': PostgreSQLDataType.json, + 'bytea': PostgreSQLDataType.byteArray, + 'name': PostgreSQLDataType.name, + 'uuid': PostgreSQLDataType.uuid }; PostgreSQLFormatIdentifier(String t) { - var components = t.split("::"); + final components = t.split('::'); if (components.length > 1) { - typeCast = components.sublist(1).join(""); + typeCast = components.sublist(1).join(''); } - var variableComponents = components.first.split(":"); + final variableComponents = components.first.split(':'); if (variableComponents.length == 1) { name = variableComponents.first; } else if (variableComponents.length == 2) { name = variableComponents.first; - var dataTypeString = variableComponents.last; + final dataTypeString = variableComponents.last; if (dataTypeString != null) { type = typeStringToCodeMap[dataTypeString]; if (type == null) { - throw new FormatException( + throw FormatException( "Invalid type code in substitution variable '$t'"); } } } else { - throw new FormatException( - "Invalid format string identifier, must contain identifier name and optionally one data type in format '@identifier:dataType' (offending identifier: ${t})"); + throw FormatException( + "Invalid format string identifier, must contain identifier name and optionally one data type in format '@identifier:dataType' (offending identifier: $t)"); } // Strip @ diff --git a/lib/src/query_cache.dart b/lib/src/query_cache.dart index c7fef2c..221c8f9 100644 --- a/lib/src/query_cache.dart +++ b/lib/src/query_cache.dart @@ -1,4 +1,4 @@ -import 'package:postgres/src/query.dart'; +import 'query.dart'; class QueryCache { final Map queries = {}; @@ -14,7 +14,7 @@ class QueryCache { } } - operator [](String statementId) { + CachedQuery operator [](String statementId) { if (statementId == null) { return null; } @@ -23,12 +23,12 @@ class QueryCache { } String identifierForQuery(Query query) { - var existing = queries[query.statement]; + final existing = queries[query.statement]; if (existing != null) { return existing.preparedStatementName; } - var string = "$idCounter".padLeft(12, "0"); + final string = '$idCounter'.padLeft(12, '0'); idCounter++; diff --git a/lib/src/query_queue.dart b/lib/src/query_queue.dart index 53d12d0..1007d84 100644 --- a/lib/src/query_queue.dart +++ b/lib/src/query_queue.dart @@ -1,16 +1,17 @@ import 'dart:async'; import 'dart:collection'; -import 'package:postgres/postgres.dart'; -import 'package:postgres/src/query.dart'; +import '../postgres.dart'; + +import 'query.dart'; class QueryQueue extends ListBase> implements List> { List> _inner = []; bool _isCancelled = false; - PostgreSQLException get _cancellationException => new PostgreSQLException( - "Query cancelled due to the database connection closing."); + PostgreSQLException get _cancellationException => PostgreSQLException( + 'Query cancelled due to the database connection closing.'); Query get pending { if (_inner.isEmpty) { diff --git a/lib/src/server_messages.dart b/lib/src/server_messages.dart index a50bcab..2cfbc47 100644 --- a/lib/src/server_messages.dart +++ b/lib/src/server_messages.dart @@ -9,11 +9,12 @@ abstract class ServerMessage { } class ErrorResponseMessage implements ServerMessage { - List fields = [new ErrorField()]; + List fields = [ErrorField()]; + @override void readBytes(Uint8List bytes) { - var lastByteRemovedList = - new Uint8List.view(bytes.buffer, bytes.offsetInBytes, bytes.length - 1); + final lastByteRemovedList = + Uint8List.view(bytes.buffer, bytes.offsetInBytes, bytes.length - 1); lastByteRemovedList.forEach((byte) { if (byte != 0) { @@ -21,7 +22,7 @@ class ErrorResponseMessage implements ServerMessage { return; } - fields.add(new ErrorField()); + fields.add(ErrorField()); }); } } @@ -40,12 +41,13 @@ class AuthenticationMessage implements ServerMessage { List salt; + @override void readBytes(Uint8List bytes) { - var view = new ByteData.view(bytes.buffer, bytes.offsetInBytes); + final view = ByteData.view(bytes.buffer, bytes.offsetInBytes); type = view.getUint32(0); if (type == KindMD5Password) { - salt = new List(4); + salt = List(4); for (var i = 0; i < 4; i++) { salt[i] = view.getUint8(4 + i); } @@ -57,6 +59,7 @@ class ParameterStatusMessage extends ServerMessage { String name; String value; + @override void readBytes(Uint8List bytes) { name = utf8.decode(bytes.sublist(0, bytes.indexOf(0))); value = @@ -65,12 +68,13 @@ class ParameterStatusMessage extends ServerMessage { } class ReadyForQueryMessage extends ServerMessage { - static const String StateIdle = "I"; - static const String StateTransaction = "T"; - static const String StateTransactionError = "E"; + static const String StateIdle = 'I'; + static const String StateTransaction = 'T'; + static const String StateTransactionError = 'E'; String state; + @override void readBytes(Uint8List bytes) { state = utf8.decode(bytes); } @@ -80,8 +84,9 @@ class BackendKeyMessage extends ServerMessage { int processID; int secretKey; + @override void readBytes(Uint8List bytes) { - var view = new ByteData.view(bytes.buffer, bytes.offsetInBytes); + final view = ByteData.view(bytes.buffer, bytes.offsetInBytes); processID = view.getUint32(0); secretKey = view.getUint32(4); } @@ -90,15 +95,16 @@ class BackendKeyMessage extends ServerMessage { class RowDescriptionMessage extends ServerMessage { List fieldDescriptions; + @override void readBytes(Uint8List bytes) { - var view = new ByteData.view(bytes.buffer, bytes.offsetInBytes); - var offset = 0; - var fieldCount = view.getInt16(offset); + final view = ByteData.view(bytes.buffer, bytes.offsetInBytes); + int offset = 0; + final fieldCount = view.getInt16(offset); offset += 2; fieldDescriptions = []; for (var i = 0; i < fieldCount; i++) { - var rowDesc = new FieldDescription(); + final rowDesc = FieldDescription(); offset = rowDesc.parse(view, offset); fieldDescriptions.add(rowDesc); } @@ -108,30 +114,32 @@ class RowDescriptionMessage extends ServerMessage { class DataRowMessage extends ServerMessage { List values = []; + @override void readBytes(Uint8List bytes) { - var view = new ByteData.view(bytes.buffer, bytes.offsetInBytes); - var offset = 0; - var fieldCount = view.getInt16(offset); + final view = ByteData.view(bytes.buffer, bytes.offsetInBytes); + int offset = 0; + final fieldCount = view.getInt16(offset); offset += 2; for (var i = 0; i < fieldCount; i++) { - var dataSize = view.getInt32(offset); + final dataSize = view.getInt32(offset); offset += 4; if (dataSize == 0) { - values.add(new ByteData(0)); + values.add(ByteData(0)); } else if (dataSize == -1) { values.add(null); } else { - var rawBytes = new ByteData.view( - bytes.buffer, bytes.offsetInBytes + offset, dataSize); + final rawBytes = + ByteData.view(bytes.buffer, bytes.offsetInBytes + offset, dataSize); values.add(rawBytes); offset += dataSize; } } } - String toString() => "Data Row Message: ${values}"; + @override + String toString() => 'Data Row Message: $values'; } class NotificationResponseMessage extends ServerMessage { @@ -139,8 +147,9 @@ class NotificationResponseMessage extends ServerMessage { String channel; String payload; + @override void readBytes(Uint8List bytes) { - var view = new ByteData.view(bytes.buffer, bytes.offsetInBytes); + final view = ByteData.view(bytes.buffer, bytes.offsetInBytes); processID = view.getUint32(0); channel = utf8.decode(bytes.sublist(4, bytes.indexOf(0, 4))); payload = utf8 @@ -151,14 +160,15 @@ class NotificationResponseMessage extends ServerMessage { class CommandCompleteMessage extends ServerMessage { int rowsAffected; - static RegExp identifierExpression = new RegExp(r"[A-Z ]*"); + static RegExp identifierExpression = RegExp(r'[A-Z ]*'); + @override void readBytes(Uint8List bytes) { - var str = utf8.decode(bytes.sublist(0, bytes.length - 1)); + final str = utf8.decode(bytes.sublist(0, bytes.length - 1)); - var match = identifierExpression.firstMatch(str); + final match = identifierExpression.firstMatch(str); if (match.end < str.length) { - rowsAffected = int.parse(str.split(" ").last); + rowsAffected = int.parse(str.split(' ').last); } else { rowsAffected = 0; } @@ -166,30 +176,35 @@ class CommandCompleteMessage extends ServerMessage { } class ParseCompleteMessage extends ServerMessage { + @override void readBytes(Uint8List bytes) {} - String toString() => "Parse Complete Message"; + @override + String toString() => 'Parse Complete Message'; } class BindCompleteMessage extends ServerMessage { + @override void readBytes(Uint8List bytes) {} - String toString() => "Bind Complete Message"; + @override + String toString() => 'Bind Complete Message'; } class ParameterDescriptionMessage extends ServerMessage { List parameterTypeIDs; + @override void readBytes(Uint8List bytes) { - var view = new ByteData.view(bytes.buffer, bytes.offsetInBytes); + final view = ByteData.view(bytes.buffer, bytes.offsetInBytes); - var offset = 0; - var count = view.getUint16(0); + int offset = 0; + final count = view.getUint16(0); offset += 2; parameterTypeIDs = []; for (var i = 0; i < count; i++) { - var v = view.getUint32(offset); + final v = view.getUint32(offset); offset += 4; parameterTypeIDs.add(v); } @@ -197,15 +212,18 @@ class ParameterDescriptionMessage extends ServerMessage { } class NoDataMessage extends ServerMessage { + @override void readBytes(Uint8List bytes) {} - String toString() => "No Data Message"; + @override + String toString() => 'No Data Message'; } class UnknownMessage extends ServerMessage { Uint8List bytes; int code; + @override void readBytes(Uint8List bytes) { this.bytes = bytes; } @@ -256,21 +274,21 @@ class ErrorField { static PostgreSQLSeverity severityFromString(String str) { switch (str) { - case "ERROR": + case 'ERROR': return PostgreSQLSeverity.error; - case "FATAL": + case 'FATAL': return PostgreSQLSeverity.fatal; - case "PANIC": + case 'PANIC': return PostgreSQLSeverity.panic; - case "WARNING": + case 'WARNING': return PostgreSQLSeverity.warning; - case "NOTICE": + case 'NOTICE': return PostgreSQLSeverity.notice; - case "DEBUG": + case 'DEBUG': return PostgreSQLSeverity.debug; - case "INFO": + case 'INFO': return PostgreSQLSeverity.info; - case "LOG": + case 'LOG': return PostgreSQLSeverity.log; } @@ -280,7 +298,7 @@ class ErrorField { int identificationToken; String get text => _buffer.toString(); - StringBuffer _buffer = new StringBuffer(); + final _buffer = StringBuffer(); void add(int byte) { if (identificationToken == null) { diff --git a/lib/src/substituter.dart b/lib/src/substituter.dart index 4682fca..41505f7 100644 --- a/lib/src/substituter.dart +++ b/lib/src/substituter.dart @@ -1,99 +1,98 @@ -import 'package:postgres/src/text_codec.dart'; -import 'types.dart'; import 'query.dart'; +import 'text_codec.dart'; +import 'types.dart'; class PostgreSQLFormat { - static int _AtSignCodeUnit = "@".codeUnitAt(0); + static final int _atSignCodeUnit = '@'.codeUnitAt(0); - static String id(String name, {PostgreSQLDataType type: null}) { + static String id(String name, {PostgreSQLDataType type}) { if (type != null) { - return "@$name:${dataTypeStringForDataType(type)}"; + return '@$name:${dataTypeStringForDataType(type)}'; } - return "@$name"; + return '@$name'; } static String dataTypeStringForDataType(PostgreSQLDataType dt) { switch (dt) { case PostgreSQLDataType.text: - return "text"; + return 'text'; case PostgreSQLDataType.integer: - return "int4"; + return 'int4'; case PostgreSQLDataType.smallInteger: - return "int2"; + return 'int2'; case PostgreSQLDataType.bigInteger: - return "int8"; + return 'int8'; case PostgreSQLDataType.serial: - return "int4"; + return 'int4'; case PostgreSQLDataType.bigSerial: - return "int8"; + return 'int8'; case PostgreSQLDataType.real: - return "float4"; + return 'float4'; case PostgreSQLDataType.double: - return "float8"; + return 'float8'; case PostgreSQLDataType.boolean: - return "boolean"; + return 'boolean'; case PostgreSQLDataType.timestampWithoutTimezone: - return "timestamp"; + return 'timestamp'; case PostgreSQLDataType.timestampWithTimezone: - return "timestamptz"; + return 'timestamptz'; case PostgreSQLDataType.date: - return "date"; + return 'date'; case PostgreSQLDataType.json: - return "jsonb"; + return 'jsonb'; case PostgreSQLDataType.byteArray: - return "bytea"; + return 'bytea'; case PostgreSQLDataType.name: - return "name"; + return 'name'; case PostgreSQLDataType.uuid: - return "uuid"; + return 'uuid'; } return null; } static String substitute(String fmtString, Map values, - {SQLReplaceIdentifierFunction replace: null}) { - final converter = new PostgresTextEncoder(true); + {SQLReplaceIdentifierFunction replace}) { + final converter = PostgresTextEncoder(true); values ??= {}; replace ??= (spec, index) => converter.convert(values[spec.name]); - var items = []; - PostgreSQLFormatToken currentPtr = null; - var iterator = new RuneIterator(fmtString); + final items = []; + PostgreSQLFormatToken currentPtr; + final iterator = RuneIterator(fmtString); iterator.moveNext(); while (iterator.current != null) { if (currentPtr == null) { - if (iterator.current == _AtSignCodeUnit) { + if (iterator.current == _atSignCodeUnit) { currentPtr = - new PostgreSQLFormatToken(PostgreSQLFormatTokenType.variable); + PostgreSQLFormatToken(PostgreSQLFormatTokenType.variable); currentPtr.buffer.writeCharCode(iterator.current); items.add(currentPtr); } else { - currentPtr = - new PostgreSQLFormatToken(PostgreSQLFormatTokenType.text); + currentPtr = PostgreSQLFormatToken(PostgreSQLFormatTokenType.text); currentPtr.buffer.writeCharCode(iterator.current); items.add(currentPtr); } } else if (currentPtr.type == PostgreSQLFormatTokenType.text) { - if (iterator.current == _AtSignCodeUnit) { + if (iterator.current == _atSignCodeUnit) { currentPtr = - new PostgreSQLFormatToken(PostgreSQLFormatTokenType.variable); + PostgreSQLFormatToken(PostgreSQLFormatTokenType.variable); currentPtr.buffer.writeCharCode(iterator.current); items.add(currentPtr); } else { currentPtr.buffer.writeCharCode(iterator.current); } } else if (currentPtr.type == PostgreSQLFormatTokenType.variable) { - if (iterator.current == _AtSignCodeUnit) { + if (iterator.current == _atSignCodeUnit) { iterator.movePrevious(); - if (iterator.current == _AtSignCodeUnit) { + if (iterator.current == _atSignCodeUnit) { currentPtr.buffer.writeCharCode(iterator.current); currentPtr.type = PostgreSQLFormatTokenType.text; } else { currentPtr = - new PostgreSQLFormatToken(PostgreSQLFormatTokenType.variable); + PostgreSQLFormatToken(PostgreSQLFormatTokenType.variable); currentPtr.buffer.writeCharCode(iterator.current); items.add(currentPtr); } @@ -101,8 +100,7 @@ class PostgreSQLFormat { } else if (_isIdentifier(iterator.current)) { currentPtr.buffer.writeCharCode(iterator.current); } else { - currentPtr = - new PostgreSQLFormatToken(PostgreSQLFormatTokenType.text); + currentPtr = PostgreSQLFormatToken(PostgreSQLFormatTokenType.text); currentPtr.buffer.writeCharCode(iterator.current); items.add(currentPtr); } @@ -118,40 +116,40 @@ class PostgreSQLFormat { } else if (t.buffer.length == 1 && t.buffer.toString() == '@') { return t.buffer; } else { - var identifier = new PostgreSQLFormatIdentifier(t.buffer.toString()); + final identifier = PostgreSQLFormatIdentifier(t.buffer.toString()); if (!values.containsKey(identifier.name)) { - throw new FormatException( - "Format string specified identifier with name ${identifier.name}, but key was not present in values. Format string: $fmtString"); + throw FormatException( + 'Format string specified identifier with name ${identifier.name}, but key was not present in values. Format string: $fmtString'); } - var val = replace(identifier, idx); + final val = replace(identifier, idx); idx++; if (identifier.typeCast != null) { - return val + "::" + identifier.typeCast; + return '$val::${identifier.typeCast}'; } return val; } - }).join(""); + }).join(''); } - static int _lowercaseACodeUnit = "a".codeUnitAt(0); - static int _uppercaseACodeUnit = "A".codeUnitAt(0); - static int _lowercaseZCodeUnit = "z".codeUnitAt(0); - static int _uppercaseZCodeUnit = "Z".codeUnitAt(0); - static int _0CodeUnit = "0".codeUnitAt(0); - static int _9CodeUnit = "9".codeUnitAt(0); - static int _underscoreCodeUnit = "_".codeUnitAt(0); - static int _ColonCodeUnit = ":".codeUnitAt(0); + static final int _lowercaseACodeUnit = 'a'.codeUnitAt(0); + static final int _uppercaseACodeUnit = 'A'.codeUnitAt(0); + static final int _lowercaseZCodeUnit = 'z'.codeUnitAt(0); + static final int _uppercaseZCodeUnit = 'Z'.codeUnitAt(0); + static final int _codeUnit0 = '0'.codeUnitAt(0); + static final int _codeUnit9 = '9'.codeUnitAt(0); + static final int _underscoreCodeUnit = '_'.codeUnitAt(0); + static final int _colonCodeUnit = ':'.codeUnitAt(0); static bool _isIdentifier(int charCode) { return (charCode >= _lowercaseACodeUnit && charCode <= _lowercaseZCodeUnit) || (charCode >= _uppercaseACodeUnit && charCode <= _uppercaseZCodeUnit) || - (charCode >= _0CodeUnit && charCode <= _9CodeUnit) || + (charCode >= _codeUnit0 && charCode <= _codeUnit9) || (charCode == _underscoreCodeUnit) || - (charCode == _ColonCodeUnit); + (charCode == _colonCodeUnit); } } diff --git a/lib/src/text_codec.dart b/lib/src/text_codec.dart index 92f914b..f85516e 100644 --- a/lib/src/text_codec.dart +++ b/lib/src/text_codec.dart @@ -10,7 +10,7 @@ class PostgresTextEncoder extends Converter { @override String convert(dynamic value) { if (value == null) { - return "null"; + return 'null'; } if (value is int) { @@ -37,7 +37,7 @@ class PostgresTextEncoder extends Converter { return encodeJSON(value); } - throw new PostgreSQLException("Could not infer type of value '$value'."); + throw PostgreSQLException("Could not infer type of value '$value'."); } String encodeString(String text, bool escapeStrings) { @@ -45,12 +45,12 @@ class PostgresTextEncoder extends Converter { return text; } - var backslashCodeUnit = r"\".codeUnitAt(0); - var quoteCodeUnit = r"'".codeUnitAt(0); + final backslashCodeUnit = r'\'.codeUnitAt(0); + final quoteCodeUnit = r"'".codeUnitAt(0); - var quoteCount = 0; - var backslashCount = 0; - var it = new RuneIterator(text); + int quoteCount = 0; + int backslashCount = 0; + final it = RuneIterator(text); while (it.moveNext()) { if (it.current == backslashCodeUnit) { backslashCount++; @@ -59,10 +59,10 @@ class PostgresTextEncoder extends Converter { } } - var buf = new StringBuffer(); + final buf = StringBuffer(); if (backslashCount > 0) { - buf.write(" E"); + buf.write(' E'); } buf.write("'"); @@ -110,37 +110,37 @@ class PostgresTextEncoder extends Converter { } String encodeBoolean(bool value) { - return value ? "TRUE" : "FALSE"; + return value ? 'TRUE' : 'FALSE'; } - String encodeDateTime(DateTime value, {bool isDateOnly: false}) { + String encodeDateTime(DateTime value, {bool isDateOnly}) { var string = value.toIso8601String(); if (isDateOnly) { - string = string.split("T").first; + string = string.split('T').first; } else { if (!value.isUtc) { - var timezoneHourOffset = value.timeZoneOffset.inHours; - var timezoneMinuteOffset = value.timeZoneOffset.inMinutes % 60; + final timezoneHourOffset = value.timeZoneOffset.inHours; + final timezoneMinuteOffset = value.timeZoneOffset.inMinutes % 60; - var hourComponent = timezoneHourOffset.abs().toString().padLeft(2, "0"); - var minuteComponent = - timezoneMinuteOffset.abs().toString().padLeft(2, "0"); + var hourComponent = timezoneHourOffset.abs().toString().padLeft(2, '0'); + final minuteComponent = + timezoneMinuteOffset.abs().toString().padLeft(2, '0'); if (timezoneHourOffset >= 0) { - hourComponent = "+${hourComponent}"; + hourComponent = '+$hourComponent'; } else { - hourComponent = "-${hourComponent}"; + hourComponent = '-$hourComponent'; } - var timezoneString = [hourComponent, minuteComponent].join(":"); - string = [string, timezoneString].join(""); + final timezoneString = [hourComponent, minuteComponent].join(':'); + string = [string, timezoneString].join(''); } } - if (string.substring(0, 1) == "-") { - string = string.substring(1) + " BC"; - } else if (string.substring(0, 1) == "+") { + if (string.substring(0, 1) == '-') { + string = '${string.substring(1)} BC'; + } else if (string.substring(0, 1) == '+') { string = string.substring(1); } @@ -149,13 +149,13 @@ class PostgresTextEncoder extends Converter { String encodeJSON(dynamic value) { if (value == null) { - return "null"; + return 'null'; } if (value is String) { return "'${json.encode(value)}'"; } - return "${json.encode(value)}"; + return json.encode(value); } } diff --git a/lib/src/transaction_proxy.dart b/lib/src/transaction_proxy.dart index a661afe..df530c3 100644 --- a/lib/src/transaction_proxy.dart +++ b/lib/src/transaction_proxy.dart @@ -8,23 +8,25 @@ class _TransactionProxy extends Object implements PostgreSQLExecutionContext { _TransactionProxy( this._connection, this.executionBlock, this.commitTimeoutInSeconds) { - beginQuery = new Query("BEGIN", {}, _connection, this) + beginQuery = Query('BEGIN', {}, _connection, this) ..onlyReturnAffectedRowCount = true; - beginQuery.future.then(startTransaction).catchError((err, st) { - new Future(() { + beginQuery.future.then(startTransaction).catchError((err, StackTrace st) { + Future(() { completer.completeError(err, st); }); }); } Query beginQuery; - Completer completer = new Completer(); + Completer completer = Completer(); Future get future => completer.future; + @override final PostgreSQLConnection _connection; + @override PostgreSQLExecutionContext get _transaction => this; final _TransactionQuerySignature executionBlock; @@ -32,18 +34,19 @@ class _TransactionProxy extends Object bool _hasFailed = false; bool _hasRolledBack = false; - void cancelTransaction({String reason: null}) { - throw new _TransactionRollbackException(reason); + @override + void cancelTransaction({String reason}) { + throw _TransactionRollbackException(reason); } Future startTransaction(dynamic _) async { - var result; + dynamic result; try { result = await executionBlock(this); // Place another event in the queue so that any non-awaited futures // in the executionBlock are given a chance to run - await new Future(() => null); + await Future(() => null); } on _TransactionRollbackException catch (rollback) { await _cancelAndRollback(rollback); @@ -62,7 +65,7 @@ class _TransactionProxy extends Object } if (!_hasRolledBack && !_hasFailed) { - await execute("COMMIT", timeoutInSeconds: commitTimeoutInSeconds); + await execute('COMMIT', timeoutInSeconds: commitTimeoutInSeconds); completer.complete(result); } } @@ -79,25 +82,25 @@ class _TransactionProxy extends Object q.future.catchError((_) {}); }); - final err = new PostgreSQLException("Query failed prior to execution. " + final err = PostgreSQLException('Query failed prior to execution. ' "This query's transaction encountered an error earlier in the transaction " - "that prevented this query from executing."); + 'that prevented this query from executing.'); _queue.cancel(err); - var rollback = new Query("ROLLBACK", {}, _connection, _transaction) + final rollback = Query('ROLLBACK', {}, _connection, _transaction) ..onlyReturnAffectedRowCount = true; _queue.addEvenIfCancelled(rollback); _connection._transitionToState(_connection._connectionState.awake()); try { - await rollback.future.timeout(new Duration(seconds: 30)); + await rollback.future.timeout(Duration(seconds: 30)); } finally { _queue.remove(rollback); } if (object is _TransactionRollbackException) { - completer.complete(new PostgreSQLRollback._(object.reason)); + completer.complete(PostgreSQLRollback._(object.reason)); } else { completer.completeError(object, trace); } diff --git a/lib/src/utf8_backed_string.dart b/lib/src/utf8_backed_string.dart index 08bac39..360344d 100644 --- a/lib/src/utf8_backed_string.dart +++ b/lib/src/utf8_backed_string.dart @@ -10,16 +10,12 @@ class UTF8BackedString { final String string; int get utf8Length { - if (_cachedUTF8Bytes == null) { - _cachedUTF8Bytes = utf8.encode(string); - } + _cachedUTF8Bytes ??= utf8.encode(string); return _cachedUTF8Bytes.length; } List get utf8Bytes { - if (_cachedUTF8Bytes == null) { - _cachedUTF8Bytes = utf8.encode(string); - } + _cachedUTF8Bytes ??= utf8.encode(string); return _cachedUTF8Bytes; } } diff --git a/pubspec.yaml b/pubspec.yaml index 326ae3b..3d853df 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -6,12 +6,13 @@ homepage: https://github.com/stablekernel/postgresql-dart documentation: environment: - sdk: ">=2.0.0 <3.0.0" + sdk: ">=2.2.0 <3.0.0" dependencies: - buffer: ^1.0.5 + buffer: ^1.0.6 crypto: ^2.0.0 dev_dependencies: + pedantic: ^1.0.0 test: ^1.3.0 coverage: any diff --git a/test/connection_test.dart b/test/connection_test.dart index 9e6fd3c..b1c2b52 100644 --- a/test/connection_test.dart +++ b/test/connection_test.dart @@ -1,155 +1,158 @@ // ignore_for_file: unawaited_futures -import 'package:postgres/postgres.dart'; -import 'package:test/test.dart'; -import 'dart:io'; + import 'dart:async'; +import 'dart:io'; import 'dart:mirrors'; +import 'package:test/test.dart'; + +import 'package:postgres/postgres.dart'; + void main() { - group("Connection lifecycle", () { - PostgreSQLConnection conn = null; + group('Connection lifecycle', () { + PostgreSQLConnection conn; tearDown(() async { await conn?.close(); }); - test("Connect with md5 auth required", () async { - conn = new PostgreSQLConnection("localhost", 5432, "dart_test", - username: "dart", password: "dart"); + test('Connect with md5 auth required', () async { + conn = PostgreSQLConnection('localhost', 5432, 'dart_test', + username: 'dart', password: 'dart'); await conn.open(); - expect(await conn.execute("select 1"), equals(1)); + expect(await conn.execute('select 1'), equals(1)); }); - test("SSL Connect with md5 auth required", () async { - conn = new PostgreSQLConnection("localhost", 5432, "dart_test", - username: "dart", password: "dart", useSSL: true); + test('SSL Connect with md5 auth required', () async { + conn = PostgreSQLConnection('localhost', 5432, 'dart_test', + username: 'dart', password: 'dart', useSSL: true); await conn.open(); - expect(await conn.execute("select 1"), equals(1)); - var socketMirror = reflect(conn).type.declarations.values.firstWhere( + expect(await conn.execute('select 1'), equals(1)); + final socketMirror = reflect(conn).type.declarations.values.firstWhere( (DeclarationMirror dm) => - dm.simpleName.toString().contains("_socket")); - var underlyingSocket = + dm.simpleName.toString().contains('_socket')); + final underlyingSocket = reflect(conn).getField(socketMirror.simpleName).reflectee; expect(underlyingSocket is SecureSocket, true); }); - test("Connect with no auth required", () async { - conn = new PostgreSQLConnection("localhost", 5432, "dart_test", - username: "darttrust"); + test('Connect with no auth required', () async { + conn = PostgreSQLConnection('localhost', 5432, 'dart_test', + username: 'darttrust'); await conn.open(); - expect(await conn.execute("select 1"), equals(1)); + expect(await conn.execute('select 1'), equals(1)); }); - test("SSL Connect with no auth required", () async { - conn = new PostgreSQLConnection("localhost", 5432, "dart_test", - username: "darttrust", useSSL: true); + test('SSL Connect with no auth required', () async { + conn = PostgreSQLConnection('localhost', 5432, 'dart_test', + username: 'darttrust', useSSL: true); await conn.open(); - expect(await conn.execute("select 1"), equals(1)); + expect(await conn.execute('select 1'), equals(1)); }); - test("Closing idle connection succeeds, closes underlying socket", + test('Closing idle connection succeeds, closes underlying socket', () async { - conn = new PostgreSQLConnection("localhost", 5432, "dart_test", - username: "darttrust"); + conn = PostgreSQLConnection('localhost', 5432, 'dart_test', + username: 'darttrust'); await conn.open(); await conn.close(); - var socketMirror = reflect(conn).type.declarations.values.firstWhere( + final socketMirror = reflect(conn).type.declarations.values.firstWhere( (DeclarationMirror dm) => - dm.simpleName.toString().contains("_socket")); - Socket underlyingSocket = - reflect(conn).getField(socketMirror.simpleName).reflectee; + dm.simpleName.toString().contains('_socket')); + final underlyingSocket = + reflect(conn).getField(socketMirror.simpleName).reflectee as Socket; expect(await underlyingSocket.done, isNotNull); conn = null; }); - test("SSL Closing idle connection succeeds, closes underlying socket", + test('SSL Closing idle connection succeeds, closes underlying socket', () async { - conn = new PostgreSQLConnection("localhost", 5432, "dart_test", - username: "darttrust", useSSL: true); + conn = PostgreSQLConnection('localhost', 5432, 'dart_test', + username: 'darttrust', useSSL: true); await conn.open(); await conn.close(); - var socketMirror = reflect(conn).type.declarations.values.firstWhere( + final socketMirror = reflect(conn).type.declarations.values.firstWhere( (DeclarationMirror dm) => - dm.simpleName.toString().contains("_socket")); - Socket underlyingSocket = - reflect(conn).getField(socketMirror.simpleName).reflectee; + dm.simpleName.toString().contains('_socket')); + final underlyingSocket = + reflect(conn).getField(socketMirror.simpleName).reflectee as Socket; expect(await underlyingSocket.done, isNotNull); conn = null; }); test( - "Closing connection while busy succeeds, queued queries are all accounted for (canceled), closes underlying socket", + 'Closing connection while busy succeeds, queued queries are all accounted for (canceled), closes underlying socket', () async { - conn = new PostgreSQLConnection("localhost", 5432, "dart_test", - username: "darttrust"); + conn = PostgreSQLConnection('localhost', 5432, 'dart_test', + username: 'darttrust'); await conn.open(); - var errors = []; + final errors = []; final catcher = (e) { errors.add(e); return null; }; - var futures = [ - conn.query("select 1", allowReuse: false).catchError(catcher), - conn.query("select 2", allowReuse: false).catchError(catcher), - conn.query("select 3", allowReuse: false).catchError(catcher), - conn.query("select 4", allowReuse: false).catchError(catcher), - conn.query("select 5", allowReuse: false).catchError(catcher), + final futures = [ + conn.query('select 1', allowReuse: false).catchError(catcher), + conn.query('select 2', allowReuse: false).catchError(catcher), + conn.query('select 3', allowReuse: false).catchError(catcher), + conn.query('select 4', allowReuse: false).catchError(catcher), + conn.query('select 5', allowReuse: false).catchError(catcher), ]; await conn.close(); await Future.wait(futures); expect(errors.length, 5); expect(errors.map((e) => e.message), - everyElement(contains("Query cancelled"))); + everyElement(contains('Query cancelled'))); }); test( - "SSL Closing connection while busy succeeds, queued queries are all accounted for (canceled), closes underlying socket", + 'SSL Closing connection while busy succeeds, queued queries are all accounted for (canceled), closes underlying socket', () async { - conn = new PostgreSQLConnection("localhost", 5432, "dart_test", - username: "darttrust", useSSL: true); + conn = PostgreSQLConnection('localhost', 5432, 'dart_test', + username: 'darttrust', useSSL: true); await conn.open(); - var errors = []; + final errors = []; final catcher = (e) { errors.add(e); return null; }; - var futures = [ - conn.query("select 1", allowReuse: false).catchError(catcher), - conn.query("select 2", allowReuse: false).catchError(catcher), - conn.query("select 3", allowReuse: false).catchError(catcher), - conn.query("select 4", allowReuse: false).catchError(catcher), - conn.query("select 5", allowReuse: false).catchError(catcher), + final futures = [ + conn.query('select 1', allowReuse: false).catchError(catcher), + conn.query('select 2', allowReuse: false).catchError(catcher), + conn.query('select 3', allowReuse: false).catchError(catcher), + conn.query('select 4', allowReuse: false).catchError(catcher), + conn.query('select 5', allowReuse: false).catchError(catcher), ]; await conn.close(); await Future.wait(futures); expect(errors.length, 5); expect(errors.map((e) => e.message), - everyElement(contains("Query cancelled"))); + everyElement(contains('Query cancelled'))); }); }); - group("Successful queries over time", () { - PostgreSQLConnection conn = null; + group('Successful queries over time', () { + PostgreSQLConnection conn; setUp(() async { - conn = new PostgreSQLConnection("localhost", 5432, "dart_test", - username: "darttrust"); + conn = PostgreSQLConnection('localhost', 5432, 'dart_test', + username: 'darttrust'); await conn.open(); }); @@ -158,47 +161,47 @@ void main() { }); test( - "Issuing multiple queries and awaiting between each one successfully returns the right value", + 'Issuing multiple queries and awaiting between each one successfully returns the right value', () async { expect( - await conn.query("select 1", allowReuse: false), + await conn.query('select 1', allowReuse: false), equals([ [1] ])); expect( - await conn.query("select 2", allowReuse: false), + await conn.query('select 2', allowReuse: false), equals([ [2] ])); expect( - await conn.query("select 3", allowReuse: false), + await conn.query('select 3', allowReuse: false), equals([ [3] ])); expect( - await conn.query("select 4", allowReuse: false), + await conn.query('select 4', allowReuse: false), equals([ [4] ])); expect( - await conn.query("select 5", allowReuse: false), + await conn.query('select 5', allowReuse: false), equals([ [5] ])); }); test( - "Issuing multiple queries without awaiting are returned with appropriate values", + 'Issuing multiple queries without awaiting are returned with appropriate values', () async { - var futures = [ - conn.query("select 1", allowReuse: false), - conn.query("select 2", allowReuse: false), - conn.query("select 3", allowReuse: false), - conn.query("select 4", allowReuse: false), - conn.query("select 5", allowReuse: false) + final futures = [ + conn.query('select 1', allowReuse: false), + conn.query('select 2', allowReuse: false), + conn.query('select 3', allowReuse: false), + conn.query('select 4', allowReuse: false), + conn.query('select 5', allowReuse: false) ]; - var results = await Future.wait(futures); + final results = await Future.wait(futures); expect(results, [ [ @@ -220,8 +223,8 @@ void main() { }); }); - group("Unintended user-error situations", () { - PostgreSQLConnection conn = null; + group('Unintended user-error situations', () { + PostgreSQLConnection conn; Future openFuture; tearDown(() async { @@ -229,133 +232,133 @@ void main() { await conn?.close(); }); - test("Sending queries to opening connection triggers error", () async { - conn = new PostgreSQLConnection("localhost", 5432, "dart_test", - username: "darttrust"); + test('Sending queries to opening connection triggers error', () async { + conn = PostgreSQLConnection('localhost', 5432, 'dart_test', + username: 'darttrust'); openFuture = conn.open(); try { - await conn.execute("select 1"); + await conn.execute('select 1'); expect(true, false); } on PostgreSQLException catch (e) { - expect(e.message, contains("connection is not open")); + expect(e.message, contains('connection is not open')); } }); - test("SSL Sending queries to opening connection triggers error", () async { - conn = new PostgreSQLConnection("localhost", 5432, "dart_test", - username: "darttrust", useSSL: true); + test('SSL Sending queries to opening connection triggers error', () async { + conn = PostgreSQLConnection('localhost', 5432, 'dart_test', + username: 'darttrust', useSSL: true); openFuture = conn.open(); try { - await conn.execute("select 1"); + await conn.execute('select 1'); expect(true, false); } on PostgreSQLException catch (e) { - expect(e.message, contains("connection is not open")); + expect(e.message, contains('connection is not open')); } }); - test("Starting transaction while opening connection triggers error", + test('Starting transaction while opening connection triggers error', () async { - conn = new PostgreSQLConnection("localhost", 5432, "dart_test", - username: "darttrust"); + conn = PostgreSQLConnection('localhost', 5432, 'dart_test', + username: 'darttrust'); openFuture = conn.open(); try { await conn.transaction((ctx) async { - await ctx.execute("select 1"); + await ctx.execute('select 1'); }); expect(true, false); } on PostgreSQLException catch (e) { - expect(e.message, contains("connection is not open")); + expect(e.message, contains('connection is not open')); } }); - test("SSL Starting transaction while opening connection triggers error", + test('SSL Starting transaction while opening connection triggers error', () async { - conn = new PostgreSQLConnection("localhost", 5432, "dart_test", - username: "darttrust", useSSL: true); + conn = PostgreSQLConnection('localhost', 5432, 'dart_test', + username: 'darttrust', useSSL: true); openFuture = conn.open(); try { await conn.transaction((ctx) async { - await ctx.execute("select 1"); + await ctx.execute('select 1'); }); expect(true, false); } on PostgreSQLException catch (e) { - expect(e.message, contains("connection is not open")); + expect(e.message, contains('connection is not open')); } }); - test("Invalid password reports error, conn is closed, disables conn", + test('Invalid password reports error, conn is closed, disables conn', () async { - conn = new PostgreSQLConnection("localhost", 5432, "dart_test", - username: "dart", password: "notdart"); + conn = PostgreSQLConnection('localhost', 5432, 'dart_test', + username: 'dart', password: 'notdart'); try { await conn.open(); expect(true, false); } on PostgreSQLException catch (e) { - expect(e.message, contains("password authentication failed")); + expect(e.message, contains('password authentication failed')); } await expectConnectionIsInvalid(conn); }); - test("SSL Invalid password reports error, conn is closed, disables conn", + test('SSL Invalid password reports error, conn is closed, disables conn', () async { - conn = new PostgreSQLConnection("localhost", 5432, "dart_test", - username: "dart", password: "notdart", useSSL: true); + conn = PostgreSQLConnection('localhost', 5432, 'dart_test', + username: 'dart', password: 'notdart', useSSL: true); try { await conn.open(); expect(true, false); } on PostgreSQLException catch (e) { - expect(e.message, contains("password authentication failed")); + expect(e.message, contains('password authentication failed')); } await expectConnectionIsInvalid(conn); }); - test("A query error maintains connectivity, allows future queries", + test('A query error maintains connectivity, allows future queries', () async { - conn = new PostgreSQLConnection("localhost", 5432, "dart_test", - username: "darttrust"); + conn = PostgreSQLConnection('localhost', 5432, 'dart_test', + username: 'darttrust'); await conn.open(); - await conn.execute("CREATE TEMPORARY TABLE t (i int unique)"); - await conn.execute("INSERT INTO t (i) VALUES (1)"); + await conn.execute('CREATE TEMPORARY TABLE t (i int unique)'); + await conn.execute('INSERT INTO t (i) VALUES (1)'); try { - await conn.execute("INSERT INTO t (i) VALUES (1)"); + await conn.execute('INSERT INTO t (i) VALUES (1)'); expect(true, false); } on PostgreSQLException catch (e) { - expect(e.message, contains("duplicate key value violates")); + expect(e.message, contains('duplicate key value violates')); } - await conn.execute("INSERT INTO t (i) VALUES (2)"); + await conn.execute('INSERT INTO t (i) VALUES (2)'); }); test( - "A query error maintains connectivity, continues processing pending queries", + 'A query error maintains connectivity, continues processing pending queries', () async { - conn = new PostgreSQLConnection("localhost", 5432, "dart_test", - username: "darttrust"); + conn = PostgreSQLConnection('localhost', 5432, 'dart_test', + username: 'darttrust'); await conn.open(); - await conn.execute("CREATE TEMPORARY TABLE t (i int unique)"); + await conn.execute('CREATE TEMPORARY TABLE t (i int unique)'); - await conn.execute("INSERT INTO t (i) VALUES (1)"); + await conn.execute('INSERT INTO t (i) VALUES (1)'); //ignore: unawaited_futures - conn.execute("INSERT INTO t (i) VALUES (1)").catchError((err) { + conn.execute('INSERT INTO t (i) VALUES (1)').catchError((err) { // ignore }); - var futures = [ - conn.query("select 1", allowReuse: false), - conn.query("select 2", allowReuse: false), - conn.query("select 3", allowReuse: false), + final futures = [ + conn.query('select 1', allowReuse: false), + conn.query('select 2', allowReuse: false), + conn.query('select 3', allowReuse: false), ]; - var results = await Future.wait(futures); + final results = await Future.wait(futures); expect(results, [ [ @@ -369,37 +372,37 @@ void main() { ] ]); - var queueMirror = reflect(conn).type.instanceMembers.values.firstWhere( + final queueMirror = reflect(conn).type.instanceMembers.values.firstWhere( (DeclarationMirror dm) => - dm.simpleName.toString().contains("_queue")); - List queue = - reflect(conn).getField(queueMirror.simpleName).reflectee; + dm.simpleName.toString().contains('_queue')); + final queue = + reflect(conn).getField(queueMirror.simpleName).reflectee as List; expect(queue, isEmpty); }); test( - "A query error maintains connectivity, continues processing pending transactions", + 'A query error maintains connectivity, continues processing pending transactions', () async { - conn = new PostgreSQLConnection("localhost", 5432, "dart_test", - username: "darttrust"); + conn = PostgreSQLConnection('localhost', 5432, 'dart_test', + username: 'darttrust'); await conn.open(); - await conn.execute("CREATE TEMPORARY TABLE t (i int unique)"); - await conn.execute("INSERT INTO t (i) VALUES (1)"); + await conn.execute('CREATE TEMPORARY TABLE t (i int unique)'); + await conn.execute('INSERT INTO t (i) VALUES (1)'); final orderEnsurer = []; // this will emit a query error //ignore: unawaited_futures - conn.execute("INSERT INTO t (i) VALUES (1)").catchError((err) { + conn.execute('INSERT INTO t (i) VALUES (1)').catchError((err) { orderEnsurer.add(1); // ignore }); orderEnsurer.add(2); - var res = await conn.transaction((ctx) async { + final res = await conn.transaction((ctx) async { orderEnsurer.add(3); - return await ctx.query("SELECT i FROM t"); + return await ctx.query('SELECT i FROM t'); }); orderEnsurer.add(4); @@ -410,22 +413,22 @@ void main() { }); test( - "Building query throws error, connection continues processing pending queries", + 'Building query throws error, connection continues processing pending queries', () async { - conn = new PostgreSQLConnection("localhost", 5432, "dart_test", - username: "darttrust"); + conn = PostgreSQLConnection('localhost', 5432, 'dart_test', + username: 'darttrust'); await conn.open(); // Make some async queries that'll exit the event loop, but then fail on a query that'll die early - conn.execute("askdl").catchError((err, st) {}); - conn.execute("abdef").catchError((err, st) {}); - conn.execute("select @a").catchError((err, st) {}); + conn.execute('askdl').catchError((err, st) {}); + conn.execute('abdef').catchError((err, st) {}); + conn.execute('select @a').catchError((err, st) {}); - var futures = [ - conn.query("select 1", allowReuse: false), - conn.query("select 2", allowReuse: false), + final futures = [ + conn.query('select 1', allowReuse: false), + conn.query('select 2', allowReuse: false), ]; - var results = await Future.wait(futures); + final results = await Future.wait(futures); expect(results, [ [ @@ -436,18 +439,18 @@ void main() { ] ]); - var queueMirror = reflect(conn).type.instanceMembers.values.firstWhere( + final queueMirror = reflect(conn).type.instanceMembers.values.firstWhere( (DeclarationMirror dm) => - dm.simpleName.toString().contains("_queue")); - List queue = - reflect(conn).getField(queueMirror.simpleName).reflectee; + dm.simpleName.toString().contains('_queue')); + final queue = + reflect(conn).getField(queueMirror.simpleName).reflectee as List; expect(queue, isEmpty); }); }); - group("Network error situations", () { - ServerSocket serverSocket = null; - Socket socket = null; + group('Network error situations', () { + ServerSocket serverSocket; + Socket socket; tearDown(() async { await serverSocket?.close(); @@ -455,9 +458,9 @@ void main() { }); test( - "Socket fails to connect reports error, disables connection for future use", + 'Socket fails to connect reports error, disables connection for future use', () async { - var conn = new PostgreSQLConnection("localhost", 5431, "dart_test"); + final conn = PostgreSQLConnection('localhost', 5431, 'dart_test'); try { await conn.open(); @@ -468,10 +471,10 @@ void main() { }); test( - "SSL Socket fails to connect reports error, disables connection for future use", + 'SSL Socket fails to connect reports error, disables connection for future use', () async { - var conn = new PostgreSQLConnection("localhost", 5431, "dart_test", - useSSL: true); + final conn = + PostgreSQLConnection('localhost', 5431, 'dart_test', useSSL: true); try { await conn.open(); @@ -482,7 +485,7 @@ void main() { }); test( - "Connection that times out throws appropriate error and cannot be reused", + 'Connection that times out throws appropriate error and cannot be reused', () async { serverSocket = await ServerSocket.bind(InternetAddress.loopbackIPv4, 5433); @@ -492,7 +495,7 @@ void main() { s.listen((bytes) {}); }); - var conn = new PostgreSQLConnection("localhost", 5433, "dart_test", + final conn = PostgreSQLConnection('localhost', 5433, 'dart_test', timeoutInSeconds: 2); try { @@ -504,7 +507,7 @@ void main() { }); test( - "SSL Connection that times out throws appropriate error and cannot be reused", + 'SSL Connection that times out throws appropriate error and cannot be reused', () async { serverSocket = await ServerSocket.bind(InternetAddress.loopbackIPv4, 5433); @@ -514,7 +517,7 @@ void main() { s.listen((bytes) {}); }); - var conn = new PostgreSQLConnection("localhost", 5433, "dart_test", + final conn = PostgreSQLConnection('localhost', 5433, 'dart_test', timeoutInSeconds: 2, useSSL: true); try { @@ -525,49 +528,45 @@ void main() { await expectConnectionIsInvalid(conn); }); - test("Connection that times out triggers future for pending queries", + test('Connection that times out triggers future for pending queries', () async { - var openCompleter = new Completer(); + final openCompleter = Completer(); serverSocket = await ServerSocket.bind(InternetAddress.loopbackIPv4, 5433); serverSocket.listen((s) { socket = s; // Don't respond on purpose s.listen((bytes) {}); - new Future.delayed(new Duration(milliseconds: 100), () { - openCompleter.complete(); - }); + Future.delayed(Duration(milliseconds: 100), openCompleter.complete); }); - var conn = new PostgreSQLConnection("localhost", 5433, "dart_test", + final conn = PostgreSQLConnection('localhost', 5433, 'dart_test', timeoutInSeconds: 2); conn.open().catchError((e) {}); await openCompleter.future; try { - await conn.execute("select 1"); + await conn.execute('select 1'); expect(true, false); } on PostgreSQLException catch (e) { - expect(e.message, contains("Failed to connect")); + expect(e.message, contains('Failed to connect')); } }); - test("SSL Connection that times out triggers future for pending queries", + test('SSL Connection that times out triggers future for pending queries', () async { - var openCompleter = new Completer(); + final openCompleter = Completer(); serverSocket = await ServerSocket.bind(InternetAddress.loopbackIPv4, 5433); serverSocket.listen((s) { socket = s; // Don't respond on purpose s.listen((bytes) {}); - new Future.delayed(new Duration(milliseconds: 100), () { - openCompleter.complete(); - }); + Future.delayed(Duration(milliseconds: 100), openCompleter.complete); }); - var conn = new PostgreSQLConnection("localhost", 5433, "dart_test", + final conn = PostgreSQLConnection('localhost', 5433, 'dart_test', timeoutInSeconds: 2, useSSL: true); conn.open().catchError((e) { return null; @@ -576,10 +575,10 @@ void main() { await openCompleter.future; try { - await conn.execute("select 1"); + await conn.execute('select 1'); expect(true, false); } on PostgreSQLException catch (e) { - expect(e.message, contains("but connection is not open")); + expect(e.message, contains('but connection is not open')); } try { @@ -589,51 +588,51 @@ void main() { }); }); - test("If connection is closed, do not allow .execute", () async { - final conn = new PostgreSQLConnection("localhost", 5432, "dart_test", - username: "dart", password: "dart"); + test('If connection is closed, do not allow .execute', () async { + final conn = PostgreSQLConnection('localhost', 5432, 'dart_test', + username: 'dart', password: 'dart'); try { - await conn.execute("SELECT 1"); + await conn.execute('SELECT 1'); fail('unreachable'); } on PostgreSQLException catch (e) { - expect(e.toString(), contains("connection is not open")); + expect(e.toString(), contains('connection is not open')); } }); - test("If connection is closed, do not allow .query", () async { - final conn = new PostgreSQLConnection("localhost", 5432, "dart_test", - username: "dart", password: "dart"); + test('If connection is closed, do not allow .query', () async { + final conn = PostgreSQLConnection('localhost', 5432, 'dart_test', + username: 'dart', password: 'dart'); try { - await conn.query("SELECT 1"); + await conn.query('SELECT 1'); fail('unreachable'); } on PostgreSQLException catch (e) { - expect(e.toString(), contains("connection is not open")); + expect(e.toString(), contains('connection is not open')); } }); - test("If connection is closed, do not allow .mappedResultsQuery", () async { - final conn = new PostgreSQLConnection("localhost", 5432, "dart_test", - username: "dart", password: "dart"); + test('If connection is closed, do not allow .mappedResultsQuery', () async { + final conn = PostgreSQLConnection('localhost', 5432, 'dart_test', + username: 'dart', password: 'dart'); try { - await conn.mappedResultsQuery("SELECT 1"); + await conn.mappedResultsQuery('SELECT 1'); fail('unreachable'); } on PostgreSQLException catch (e) { - expect(e.toString(), contains("connection is not open")); + expect(e.toString(), contains('connection is not open')); } }); test( - "Queue size, should be 0 on open, >0 if queries added and 0 again after queries executed", + 'Queue size, should be 0 on open, >0 if queries added and 0 again after queries executed', () async { - final conn = new PostgreSQLConnection("localhost", 5432, "dart_test", - username: "dart", password: "dart"); + final conn = PostgreSQLConnection('localhost', 5432, 'dart_test', + username: 'dart', password: 'dart'); await conn.open(); expect(conn.queueSize, 0); - var futures = [ - conn.query("select 1", allowReuse: false), - conn.query("select 2", allowReuse: false), - conn.query("select 3", allowReuse: false) + final futures = [ + conn.query('select 1', allowReuse: false), + conn.query('select 2', allowReuse: false), + conn.query('select 3', allowReuse: false) ]; expect(conn.queueSize, 3); @@ -644,16 +643,16 @@ void main() { Future expectConnectionIsInvalid(PostgreSQLConnection conn) async { try { - await conn.execute("select 1"); + await conn.execute('select 1'); expect(true, false); } on PostgreSQLException catch (e) { - expect(e.message, contains("connection is not open")); + expect(e.message, contains('connection is not open')); } try { await conn.open(); expect(true, false); } on PostgreSQLException catch (e) { - expect(e.message, contains("Attempting to reopen a closed connection")); + expect(e.message, contains('Attempting to reopen a closed connection')); } } diff --git a/test/decode_test.dart b/test/decode_test.dart index 7d255fc..27096fb 100644 --- a/test/decode_test.dart +++ b/test/decode_test.dart @@ -4,45 +4,45 @@ import 'package:test/test.dart'; void main() { PostgreSQLConnection connection; setUp(() async { - connection = new PostgreSQLConnection("localhost", 5432, "dart_test", - username: "dart", password: "dart"); + connection = PostgreSQLConnection('localhost', 5432, 'dart_test', + username: 'dart', password: 'dart'); await connection.open(); - await connection.execute(""" + await connection.execute(''' CREATE TEMPORARY TABLE t ( i int, s serial, bi bigint, bs bigserial, bl boolean, si smallint, t text, f real, d double precision, dt date, ts timestamp, tsz timestamptz, j jsonb, ba bytea, u uuid) - """); + '''); await connection.execute( - "INSERT INTO t (i, bi, bl, si, t, f, d, dt, ts, tsz, j, ba, u) " - "VALUES (-2147483648, -9223372036854775808, TRUE, -32768, " + 'INSERT INTO t (i, bi, bl, si, t, f, d, dt, ts, tsz, j, ba, u) ' + 'VALUES (-2147483648, -9223372036854775808, TRUE, -32768, ' "'string', 10.0, 10.0, '1983-11-06', " "'1983-11-06 06:00:00.000000', '1983-11-06 06:00:00.000000', " "'{\"key\":\"value\"}', E'\\\\000', '00000000-0000-0000-0000-000000000000')"); await connection.execute( - "INSERT INTO t (i, bi, bl, si, t, f, d, dt, ts, tsz, j, ba, u) " - "VALUES (2147483647, 9223372036854775807, FALSE, 32767, " + 'INSERT INTO t (i, bi, bl, si, t, f, d, dt, ts, tsz, j, ba, u) ' + 'VALUES (2147483647, 9223372036854775807, FALSE, 32767, ' "'a significantly longer string to the point where i doubt this actually matters', " "10.25, 10.125, '2183-11-06', '2183-11-06 00:00:00.111111', " "'2183-11-06 00:00:00.999999', " "'[{\"key\":1}]', E'\\\\377', 'FFFFFFFF-ffff-ffff-ffff-ffffffffffff')"); await connection.execute( - "INSERT INTO t (i, bi, bl, si, t, f, d, dt, ts, tsz, j, ba, u) " - "VALUES (null, null, null, null, null, null, null, null, null, null, null, null, null)"); + 'INSERT INTO t (i, bi, bl, si, t, f, d, dt, ts, tsz, j, ba, u) ' + 'VALUES (null, null, null, null, null, null, null, null, null, null, null, null, null)'); }); tearDown(() async { await connection?.close(); }); - test("Fetch em", () async { - var res = await connection.query("select * from t"); + test('Fetch em', () async { + final res = await connection.query('select * from t'); - var row1 = res[0]; - var row2 = res[1]; - var row3 = res[2]; + final row1 = res[0]; + final row2 = res[1]; + final row3 = res[2]; // lower bound row expect(row1[0], equals(-2147483648)); @@ -51,17 +51,17 @@ void main() { expect(row1[3], equals(1)); expect(row1[4], equals(true)); expect(row1[5], equals(-32768)); - expect(row1[6], equals("string")); + expect(row1[6], equals('string')); expect(row1[7] is double, true); expect(row1[7], equals(10.0)); expect(row1[8] is double, true); expect(row1[8], equals(10.0)); - expect(row1[9], equals(new DateTime.utc(1983, 11, 6))); - expect(row1[10], equals(new DateTime.utc(1983, 11, 6, 6))); - expect(row1[11], equals(new DateTime.utc(1983, 11, 6, 6))); - expect(row1[12], equals({"key": "value"})); + expect(row1[9], equals(DateTime.utc(1983, 11, 6))); + expect(row1[10], equals(DateTime.utc(1983, 11, 6, 6))); + expect(row1[11], equals(DateTime.utc(1983, 11, 6, 6))); + expect(row1[12], equals({'key': 'value'})); expect(row1[13], equals([0])); - expect(row1[14], equals("00000000-0000-0000-0000-000000000000")); + expect(row1[14], equals('00000000-0000-0000-0000-000000000000')); // upper bound row expect(row2[0], equals(2147483647)); @@ -73,21 +73,21 @@ void main() { expect( row2[6], equals( - "a significantly longer string to the point where i doubt this actually matters")); + 'a significantly longer string to the point where i doubt this actually matters')); expect(row2[7] is double, true); expect(row2[7], equals(10.25)); expect(row2[8] is double, true); expect(row2[8], equals(10.125)); - expect(row2[9], equals(new DateTime.utc(2183, 11, 6))); - expect(row2[10], equals(new DateTime.utc(2183, 11, 6, 0, 0, 0, 111, 111))); - expect(row2[11], equals(new DateTime.utc(2183, 11, 6, 0, 0, 0, 999, 999))); + expect(row2[9], equals(DateTime.utc(2183, 11, 6))); + expect(row2[10], equals(DateTime.utc(2183, 11, 6, 0, 0, 0, 111, 111))); + expect(row2[11], equals(DateTime.utc(2183, 11, 6, 0, 0, 0, 999, 999))); expect( row2[12], equals([ - {"key": 1} + {'key': 1} ])); expect(row2[13], equals([255])); - expect(row2[14], equals("ffffffff-ffff-ffff-ffff-ffffffffffff")); + expect(row2[14], equals('ffffffff-ffff-ffff-ffff-ffffffffffff')); // all null row expect(row3[0], isNull); @@ -107,31 +107,31 @@ void main() { expect(row3[14], isNull); }); - test("Fetch/insert empty string", () async { - await connection.execute("CREATE TEMPORARY TABLE u (t text)"); + test('Fetch/insert empty string', () async { + await connection.execute('CREATE TEMPORARY TABLE u (t text)'); var results = await connection.query( - "INSERT INTO u (t) VALUES (@t:text) returning t", - substitutionValues: {"t": ""}); + 'INSERT INTO u (t) VALUES (@t:text) returning t', + substitutionValues: {'t': ''}); expect(results, [ - [""] + [''] ]); - results = await connection.query("select * from u"); + results = await connection.query('select * from u'); expect(results, [ - [""] + [''] ]); }); - test("Fetch/insert null value", () async { - await connection.execute("CREATE TEMPORARY TABLE u (t text)"); + test('Fetch/insert null value', () async { + await connection.execute('CREATE TEMPORARY TABLE u (t text)'); var results = await connection.query( - "INSERT INTO u (t) VALUES (@t:text) returning t", - substitutionValues: {"t": null}); + 'INSERT INTO u (t) VALUES (@t:text) returning t', + substitutionValues: {'t': null}); expect(results, [ [null] ]); - results = await connection.query("select * from u"); + results = await connection.query('select * from u'); expect(results, [ [null] ]); diff --git a/test/encoding_test.dart b/test/encoding_test.dart index d736d12..877f1cf 100644 --- a/test/encoding_test.dart +++ b/test/encoding_test.dart @@ -12,10 +12,10 @@ import 'package:postgres/src/utf8_backed_string.dart'; PostgreSQLConnection conn; void main() { - group("Binary encoders", () { + group('Binary encoders', () { setUp(() async { - conn = new PostgreSQLConnection("localhost", 5432, "dart_test", - username: "dart", password: "dart"); + conn = PostgreSQLConnection('localhost', 5432, 'dart_test', + username: 'dart', password: 'dart'); await conn.open(); }); @@ -28,215 +28,212 @@ void main() { // 1. encoder/decoder is reversible // 2. can actually encode and decode a real pg query // it also creates a table named t with column v of type being tested - test("bool", () async { + test('bool', () async { await expectInverse(true, PostgreSQLDataType.boolean); await expectInverse(false, PostgreSQLDataType.boolean); try { - await conn.query("INSERT INTO t (v) VALUES (@v:boolean)", - substitutionValues: {"v": "not-bool"}); + await conn.query('INSERT INTO t (v) VALUES (@v:boolean)', + substitutionValues: {'v': 'not-bool'}); fail('unreachable'); } on FormatException catch (e) { - expect(e.toString(), contains("Expected: bool")); + expect(e.toString(), contains('Expected: bool')); } }); - test("smallint", () async { + test('smallint', () async { await expectInverse(-1, PostgreSQLDataType.smallInteger); await expectInverse(0, PostgreSQLDataType.smallInteger); await expectInverse(1, PostgreSQLDataType.smallInteger); try { - await conn.query("INSERT INTO t (v) VALUES (@v:int2)", - substitutionValues: {"v": "not-int2"}); + await conn.query('INSERT INTO t (v) VALUES (@v:int2)', + substitutionValues: {'v': 'not-int2'}); fail('unreachable'); } on FormatException catch (e) { - expect(e.toString(), contains("Expected: int")); + expect(e.toString(), contains('Expected: int')); } }); - test("integer", () async { + test('integer', () async { await expectInverse(-1, PostgreSQLDataType.integer); await expectInverse(0, PostgreSQLDataType.integer); await expectInverse(1, PostgreSQLDataType.integer); try { - await conn.query("INSERT INTO t (v) VALUES (@v:int4)", - substitutionValues: {"v": "not-int4"}); + await conn.query('INSERT INTO t (v) VALUES (@v:int4)', + substitutionValues: {'v': 'not-int4'}); fail('unreachable'); } on FormatException catch (e) { - expect(e.toString(), contains("Expected: int")); + expect(e.toString(), contains('Expected: int')); } }); - test("serial", () async { + test('serial', () async { await expectInverse(0, PostgreSQLDataType.serial); await expectInverse(1, PostgreSQLDataType.serial); try { - await conn.query("INSERT INTO t (v) VALUES (@v:int4)", - substitutionValues: {"v": "not-serial"}); + await conn.query('INSERT INTO t (v) VALUES (@v:int4)', + substitutionValues: {'v': 'not-serial'}); fail('unreachable'); } on FormatException catch (e) { - expect(e.toString(), contains("Expected: int")); + expect(e.toString(), contains('Expected: int')); } }); - test("bigint", () async { + test('bigint', () async { await expectInverse(-1, PostgreSQLDataType.bigInteger); await expectInverse(0, PostgreSQLDataType.bigInteger); await expectInverse(1, PostgreSQLDataType.bigInteger); try { - await conn.query("INSERT INTO t (v) VALUES (@v:int8)", - substitutionValues: {"v": "not-int8"}); + await conn.query('INSERT INTO t (v) VALUES (@v:int8)', + substitutionValues: {'v': 'not-int8'}); fail('unreachable'); } on FormatException catch (e) { - expect(e.toString(), contains("Expected: int")); + expect(e.toString(), contains('Expected: int')); } }); - test("bigserial", () async { + test('bigserial', () async { await expectInverse(0, PostgreSQLDataType.bigSerial); await expectInverse(1, PostgreSQLDataType.bigSerial); try { - await conn.query("INSERT INTO t (v) VALUES (@v:int8)", - substitutionValues: {"v": "not-bigserial"}); + await conn.query('INSERT INTO t (v) VALUES (@v:int8)', + substitutionValues: {'v': 'not-bigserial'}); fail('unreachable'); } on FormatException catch (e) { - expect(e.toString(), contains("Expected: int")); + expect(e.toString(), contains('Expected: int')); } }); - test("text", () async { - await expectInverse("", PostgreSQLDataType.text); - await expectInverse("foo", PostgreSQLDataType.text); - await expectInverse("foo\n", PostgreSQLDataType.text); - await expectInverse("foo\nbar;s", PostgreSQLDataType.text); + test('text', () async { + await expectInverse('', PostgreSQLDataType.text); + await expectInverse('foo', PostgreSQLDataType.text); + await expectInverse('foo\n', PostgreSQLDataType.text); + await expectInverse('foo\nbar;s', PostgreSQLDataType.text); try { - await conn.query("INSERT INTO t (v) VALUES (@v:text)", - substitutionValues: {"v": 0}); + await conn.query('INSERT INTO t (v) VALUES (@v:text)', + substitutionValues: {'v': 0}); fail('unreachable'); } on FormatException catch (e) { - expect(e.toString(), contains("Expected: String")); + expect(e.toString(), contains('Expected: String')); } }); - test("real", () async { + test('real', () async { await expectInverse(-1.0, PostgreSQLDataType.real); await expectInverse(0.0, PostgreSQLDataType.real); await expectInverse(1.0, PostgreSQLDataType.real); try { - await conn.query("INSERT INTO t (v) VALUES (@v:float4)", - substitutionValues: {"v": "not-real"}); + await conn.query('INSERT INTO t (v) VALUES (@v:float4)', + substitutionValues: {'v': 'not-real'}); fail('unreachable'); } on FormatException catch (e) { - expect(e.toString(), contains("Expected: double")); + expect(e.toString(), contains('Expected: double')); } }); - test("double", () async { + test('double', () async { await expectInverse(-1.0, PostgreSQLDataType.double); await expectInverse(0.0, PostgreSQLDataType.double); await expectInverse(1.0, PostgreSQLDataType.double); try { - await conn.query("INSERT INTO t (v) VALUES (@v:float8)", - substitutionValues: {"v": "not-double"}); + await conn.query('INSERT INTO t (v) VALUES (@v:float8)', + substitutionValues: {'v': 'not-double'}); fail('unreachable'); } on FormatException catch (e) { - expect(e.toString(), contains("Expected: double")); + expect(e.toString(), contains('Expected: double')); } }); - test("date", () async { - await expectInverse( - new DateTime.utc(1920, 10, 1), PostgreSQLDataType.date); - await expectInverse( - new DateTime.utc(2120, 10, 5), PostgreSQLDataType.date); - await expectInverse( - new DateTime.utc(2016, 10, 1), PostgreSQLDataType.date); + test('date', () async { + await expectInverse(DateTime.utc(1920, 10, 1), PostgreSQLDataType.date); + await expectInverse(DateTime.utc(2120, 10, 5), PostgreSQLDataType.date); + await expectInverse(DateTime.utc(2016, 10, 1), PostgreSQLDataType.date); try { - await conn.query("INSERT INTO t (v) VALUES (@v:date)", - substitutionValues: {"v": "not-date"}); + await conn.query('INSERT INTO t (v) VALUES (@v:date)', + substitutionValues: {'v': 'not-date'}); fail('unreachable'); } on FormatException catch (e) { - expect(e.toString(), contains("Expected: DateTime")); + expect(e.toString(), contains('Expected: DateTime')); } }); - test("timestamp", () async { - await expectInverse(new DateTime.utc(1920, 10, 1), + test('timestamp', () async { + await expectInverse(DateTime.utc(1920, 10, 1), PostgreSQLDataType.timestampWithoutTimezone); - await expectInverse(new DateTime.utc(2120, 10, 5), + await expectInverse(DateTime.utc(2120, 10, 5), PostgreSQLDataType.timestampWithoutTimezone); try { - await conn.query("INSERT INTO t (v) VALUES (@v:timestamp)", - substitutionValues: {"v": "not-timestamp"}); + await conn.query('INSERT INTO t (v) VALUES (@v:timestamp)', + substitutionValues: {'v': 'not-timestamp'}); fail('unreachable'); } on FormatException catch (e) { - expect(e.toString(), contains("Expected: DateTime")); + expect(e.toString(), contains('Expected: DateTime')); } }); - test("timestamptz", () async { - await expectInverse(new DateTime.utc(1920, 10, 1), - PostgreSQLDataType.timestampWithTimezone); - await expectInverse(new DateTime.utc(2120, 10, 5), - PostgreSQLDataType.timestampWithTimezone); + test('timestamptz', () async { + await expectInverse( + DateTime.utc(1920, 10, 1), PostgreSQLDataType.timestampWithTimezone); + await expectInverse( + DateTime.utc(2120, 10, 5), PostgreSQLDataType.timestampWithTimezone); try { - await conn.query("INSERT INTO t (v) VALUES (@v:timestamptz)", - substitutionValues: {"v": "not-timestamptz"}); + await conn.query('INSERT INTO t (v) VALUES (@v:timestamptz)', + substitutionValues: {'v': 'not-timestamptz'}); fail('unreachable'); } on FormatException catch (e) { - expect(e.toString(), contains("Expected: DateTime")); + expect(e.toString(), contains('Expected: DateTime')); } }); - test("jsonb", () async { - await expectInverse("string", PostgreSQLDataType.json); + test('jsonb', () async { + await expectInverse('string', PostgreSQLDataType.json); await expectInverse(2, PostgreSQLDataType.json); - await expectInverse(["foo"], PostgreSQLDataType.json); + await expectInverse(['foo'], PostgreSQLDataType.json); await expectInverse({ - "key": "val", - "key1": 1, - "array": ["foo"] + 'key': 'val', + 'key1': 1, + 'array': ['foo'] }, PostgreSQLDataType.json); try { - await conn.query("INSERT INTO t (v) VALUES (@v:jsonb)", - substitutionValues: {"v": new DateTime.now()}); + await conn.query('INSERT INTO t (v) VALUES (@v:jsonb)', + substitutionValues: {'v': DateTime.now()}); fail('unreachable'); } on JsonUnsupportedObjectError catch (_) {} }); - test("bytea", () async { + test('bytea', () async { await expectInverse([0], PostgreSQLDataType.byteArray); await expectInverse([1, 2, 3, 4, 5], PostgreSQLDataType.byteArray); await expectInverse([255, 254, 253], PostgreSQLDataType.byteArray); try { - await conn.query("INSERT INTO t (v) VALUES (@v:bytea)", - substitutionValues: {"v": new DateTime.now()}); + await conn.query('INSERT INTO t (v) VALUES (@v:bytea)', + substitutionValues: {'v': DateTime.now()}); fail('unreachable'); } on FormatException catch (e) { - expect(e.toString(), contains("Expected: List")); + expect(e.toString(), contains('Expected: List')); } }); - test("uuid", () async { + test('uuid', () async { await expectInverse( - "00000000-0000-0000-0000-000000000000", PostgreSQLDataType.uuid); + '00000000-0000-0000-0000-000000000000', PostgreSQLDataType.uuid); await expectInverse( - "12345678-abcd-efab-cdef-012345678901", PostgreSQLDataType.uuid); + '12345678-abcd-efab-cdef-012345678901', PostgreSQLDataType.uuid); try { - await conn.query("INSERT INTO t (v) VALUES (@v:uuid)", - substitutionValues: {"v": new DateTime.now()}); + await conn.query('INSERT INTO t (v) VALUES (@v:uuid)', + substitutionValues: {'v': DateTime.now()}); fail('unreachable'); } on FormatException catch (e) { - expect(e.toString(), contains("Expected: String")); + expect(e.toString(), contains('Expected: String')); } }); }); - group("Text encoders", () { - test("Escape strings", () { - final encoder = new PostgresTextEncoder(true); + group('Text encoders', () { + test('Escape strings', () { + final encoder = PostgresTextEncoder(true); // ' b o b ' expect( utf8.encode(encoder.convert('bob')), equals([39, 98, 111, 98, 39])); @@ -273,91 +270,91 @@ void main() { equals([32, 69, 39, 92, 92, 39, 39, 39])); }); - test("Encode DateTime", () { + test('Encode DateTime', () { // Get users current timezone - var tz = new DateTime(2001, 2, 3).timeZoneOffset; - var tzOffsetDelimiter = "${tz.isNegative ? '-' : '+'}" - "${tz.abs().inHours.toString().padLeft(2, '0')}" - ":${(tz.inSeconds % 60).toString().padLeft(2, '0')}"; - - var pairs = { - "2001-02-03T00:00:00.000$tzOffsetDelimiter": - new DateTime(2001, DateTime.february, 3), - "2001-02-03T04:05:06.000$tzOffsetDelimiter": - new DateTime(2001, DateTime.february, 3, 4, 5, 6, 0), - "2001-02-03T04:05:06.999$tzOffsetDelimiter": - new DateTime(2001, DateTime.february, 3, 4, 5, 6, 999), - "0010-02-03T04:05:06.123$tzOffsetDelimiter BC": - new DateTime(-10, DateTime.february, 3, 4, 5, 6, 123), - "0010-02-03T04:05:06.000$tzOffsetDelimiter BC": - new DateTime(-10, DateTime.february, 3, 4, 5, 6, 0), - "012345-02-03T04:05:06.000$tzOffsetDelimiter BC": - new DateTime(-12345, DateTime.february, 3, 4, 5, 6, 0), - "012345-02-03T04:05:06.000$tzOffsetDelimiter": - new DateTime(12345, DateTime.february, 3, 4, 5, 6, 0) + final tz = DateTime(2001, 2, 3).timeZoneOffset; + final tzOffsetDelimiter = '${tz.isNegative ? '-' : '+'}' + '${tz.abs().inHours.toString().padLeft(2, '0')}' + ':${(tz.inSeconds % 60).toString().padLeft(2, '0')}'; + + final pairs = { + '2001-02-03T00:00:00.000$tzOffsetDelimiter': + DateTime(2001, DateTime.february, 3), + '2001-02-03T04:05:06.000$tzOffsetDelimiter': + DateTime(2001, DateTime.february, 3, 4, 5, 6, 0), + '2001-02-03T04:05:06.999$tzOffsetDelimiter': + DateTime(2001, DateTime.february, 3, 4, 5, 6, 999), + '0010-02-03T04:05:06.123$tzOffsetDelimiter BC': + DateTime(-10, DateTime.february, 3, 4, 5, 6, 123), + '0010-02-03T04:05:06.000$tzOffsetDelimiter BC': + DateTime(-10, DateTime.february, 3, 4, 5, 6, 0), + '012345-02-03T04:05:06.000$tzOffsetDelimiter BC': + DateTime(-12345, DateTime.february, 3, 4, 5, 6, 0), + '012345-02-03T04:05:06.000$tzOffsetDelimiter': + DateTime(12345, DateTime.february, 3, 4, 5, 6, 0) }; - final encoder = new PostgresTextEncoder(false); + final encoder = PostgresTextEncoder(false); pairs.forEach((k, v) { expect(encoder.convert(v), "'$k'"); }); }); - test("Encode Double", () { - var pairs = { + test('Encode Double', () { + final pairs = { "'nan'": double.nan, "'infinity'": double.infinity, "'-infinity'": double.negativeInfinity, - "1.7976931348623157e+308": double.maxFinite, - "5e-324": double.minPositive, - "-0.0": -0.0, - "0.0": 0.0 + '1.7976931348623157e+308': double.maxFinite, + '5e-324': double.minPositive, + '-0.0': -0.0, + '0.0': 0.0 }; - final encoder = new PostgresTextEncoder(false); + final encoder = PostgresTextEncoder(false); pairs.forEach((k, v) { - expect(encoder.convert(v), "$k"); + expect(encoder.convert(v), '$k'); }); }); - test("Encode Int", () { - final encoder = new PostgresTextEncoder(false); + test('Encode Int', () { + final encoder = PostgresTextEncoder(false); - expect(encoder.convert(1), "1"); - expect(encoder.convert(1234324323), "1234324323"); - expect(encoder.convert(-1234324323), "-1234324323"); + expect(encoder.convert(1), '1'); + expect(encoder.convert(1234324323), '1234324323'); + expect(encoder.convert(-1234324323), '-1234324323'); }); - test("Encode Bool", () { - final encoder = new PostgresTextEncoder(false); + test('Encode Bool', () { + final encoder = PostgresTextEncoder(false); - expect(encoder.convert(true), "TRUE"); - expect(encoder.convert(false), "FALSE"); + expect(encoder.convert(true), 'TRUE'); + expect(encoder.convert(false), 'FALSE'); }); - test("Encode JSONB", () { - final encoder = new PostgresTextEncoder(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}"); + 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 = new PostgresTextEncoder(false); + test('Attempt to infer unknown type throws exception', () { + final encoder = PostgresTextEncoder(false); try { encoder.convert([]); fail('unreachable'); } on PostgreSQLException catch (e) { - expect(e.toString(), contains("Could not infer type")); + expect(e.toString(), contains('Could not infer type')); } }); }); - test("UTF8String caches string regardless of which method is called first", + test('UTF8String caches string regardless of which method is called first', () { - var u = new UTF8BackedString("abcd"); - var v = new UTF8BackedString("abcd"); + final u = UTF8BackedString('abcd'); + final v = UTF8BackedString('abcd'); u.utf8Length; v.utf8Bytes; @@ -366,34 +363,34 @@ void main() { expect(v.hasCachedBytes, true); }); - test("Invalid UUID encoding", () { - final converter = new PostgresBinaryEncoder(PostgreSQLDataType.uuid); + test('Invalid UUID encoding', () { + final converter = PostgresBinaryEncoder(PostgreSQLDataType.uuid); try { - converter.convert("z0000000-0000-0000-0000-000000000000"); + converter.convert('z0000000-0000-0000-0000-000000000000'); fail('unreachable'); } on FormatException catch (e) { - expect(e.toString(), contains("Invalid UUID string")); + expect(e.toString(), contains('Invalid UUID string')); } try { converter.convert(123123); fail('unreachable'); } on FormatException catch (e) { - expect(e.toString(), contains("Invalid type for parameter")); + expect(e.toString(), contains('Invalid type for parameter')); } try { - converter.convert("0000000-0000-0000-0000-000000000000"); + converter.convert('0000000-0000-0000-0000-000000000000'); fail('unreachable'); } on FormatException catch (e) { - expect(e.toString(), contains("Invalid UUID string")); + expect(e.toString(), contains('Invalid UUID string')); } try { - converter.convert("00000000-0000-0000-0000-000000000000f"); + converter.convert('00000000-0000-0000-0000-000000000000f'); fail('unreachable'); } on FormatException catch (e) { - expect(e.toString(), contains("Invalid UUID string")); + expect(e.toString(), contains('Invalid UUID string')); } }); } @@ -401,13 +398,13 @@ void main() { Future expectInverse(dynamic value, PostgreSQLDataType dataType) async { final type = PostgreSQLFormat.dataTypeStringForDataType(dataType); - await conn.execute("CREATE TEMPORARY TABLE IF NOT EXISTS t (v $type)"); + await conn.execute('CREATE TEMPORARY TABLE IF NOT EXISTS t (v $type)'); final result = await conn.query( - "INSERT INTO t (v) VALUES (${PostgreSQLFormat.id("v", type: dataType)}) RETURNING v", - substitutionValues: {"v": value}); + 'INSERT INTO t (v) VALUES (${PostgreSQLFormat.id('v', type: dataType)}) RETURNING v', + substitutionValues: {'v': value}); expect(result.first.first, equals(value)); - final encoder = new PostgresBinaryEncoder(dataType); + final encoder = PostgresBinaryEncoder(dataType); final encodedValue = encoder.convert(value); if (dataType == PostgreSQLDataType.serial) { @@ -415,14 +412,14 @@ Future expectInverse(dynamic value, PostgreSQLDataType dataType) async { } else if (dataType == PostgreSQLDataType.bigSerial) { dataType = PostgreSQLDataType.bigInteger; } - var code; + int code; PostgresBinaryDecoder.typeMap.forEach((key, type) { if (type == dataType) { code = key; } }); - final decoder = new PostgresBinaryDecoder(code); + final decoder = PostgresBinaryDecoder(code); final decodedValue = decoder.convert(encodedValue); expect(decodedValue, value); diff --git a/test/framer_test.dart b/test/framer_test.dart index 13cf361..25f62d7 100644 --- a/test/framer_test.dart +++ b/test/framer_test.dart @@ -1,69 +1,72 @@ +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:buffer/buffer.dart'; +import 'package:test/test.dart'; + import 'package:postgres/src/message_window.dart'; import 'package:postgres/src/server_messages.dart'; -import 'package:test/test.dart'; -import 'dart:typed_data'; -import 'dart:io'; void main() { MessageFramer framer; setUp(() { - framer = new MessageFramer(); + framer = MessageFramer(); }); tearDown(() { flush(framer); }); - test("Perfectly sized message in one buffer", () { + test('Perfectly sized message in one buffer', () { framer.addBytes(bufferWithMessages([ messageWithBytes([1, 2, 3], 1) ])); - var messages = framer.messageQueue.map((f) => f.message).toList(); + final messages = framer.messageQueue.map((f) => f.message).toList(); expect(messages, [ - new UnknownMessage() + UnknownMessage() ..code = 1 - ..bytes = new Uint8List.fromList([1, 2, 3]) + ..bytes = Uint8List.fromList([1, 2, 3]) ]); }); - test("Two perfectly sized messages in one buffer", () { + test('Two perfectly sized messages in one buffer', () { framer.addBytes(bufferWithMessages([ messageWithBytes([1, 2, 3], 1), messageWithBytes([1, 2, 3, 4], 2) ])); - var messages = framer.messageQueue.map((f) => f.message).toList(); + final messages = framer.messageQueue.map((f) => f.message).toList(); expect(messages, [ - new UnknownMessage() + UnknownMessage() ..code = 1 - ..bytes = new Uint8List.fromList([1, 2, 3]), - new UnknownMessage() + ..bytes = Uint8List.fromList([1, 2, 3]), + UnknownMessage() ..code = 2 - ..bytes = new Uint8List.fromList([1, 2, 3, 4]) + ..bytes = Uint8List.fromList([1, 2, 3, 4]) ]); }); - test("Header fragment", () { - var message = messageWithBytes([1, 2, 3], 1); - var fragments = fragmentedMessageBuffer(message, 2); + test('Header fragment', () { + final message = messageWithBytes([1, 2, 3], 1); + final fragments = fragmentedMessageBuffer(message, 2); framer.addBytes(fragments.first); expect(framer.messageQueue, isEmpty); framer.addBytes(fragments.last); - var messages = framer.messageQueue.map((f) => f.message).toList(); + final messages = framer.messageQueue.map((f) => f.message).toList(); expect(messages, [ - new UnknownMessage() + UnknownMessage() ..code = 1 - ..bytes = new Uint8List.fromList([1, 2, 3]) + ..bytes = Uint8List.fromList([1, 2, 3]) ]); }); - test("Two header fragments", () { - var message = messageWithBytes([1, 2, 3], 1); - var fragments = fragmentedMessageBuffer(message, 2); - var moreFragments = fragmentedMessageBuffer(fragments.first, 1); + test('Two header fragments', () { + final message = messageWithBytes([1, 2, 3], 1); + final fragments = fragmentedMessageBuffer(message, 2); + final moreFragments = fragmentedMessageBuffer(fragments.first, 1); framer.addBytes(moreFragments.first); expect(framer.messageQueue, isEmpty); @@ -73,18 +76,18 @@ void main() { framer.addBytes(fragments.last); - var messages = framer.messageQueue.map((f) => f.message).toList(); + final messages = framer.messageQueue.map((f) => f.message).toList(); expect(messages, [ - new UnknownMessage() + UnknownMessage() ..code = 1 - ..bytes = new Uint8List.fromList([1, 2, 3]) + ..bytes = Uint8List.fromList([1, 2, 3]) ]); }); - test("One message + header fragment", () { - var message1 = messageWithBytes([1, 2, 3], 1); - var message2 = messageWithBytes([2, 2, 3], 2); - var message2Fragments = fragmentedMessageBuffer(message2, 3); + test('One message + header fragment', () { + final message1 = messageWithBytes([1, 2, 3], 1); + final message2 = messageWithBytes([2, 2, 3], 2); + final message2Fragments = fragmentedMessageBuffer(message2, 3); framer.addBytes(bufferWithMessages([message1, message2Fragments.first])); @@ -92,21 +95,21 @@ void main() { framer.addBytes(message2Fragments.last); - var messages = framer.messageQueue.map((f) => f.message).toList(); + final messages = framer.messageQueue.map((f) => f.message).toList(); expect(messages, [ - new UnknownMessage() + UnknownMessage() ..code = 1 - ..bytes = new Uint8List.fromList([1, 2, 3]), - new UnknownMessage() + ..bytes = Uint8List.fromList([1, 2, 3]), + UnknownMessage() ..code = 2 - ..bytes = new Uint8List.fromList([2, 2, 3]), + ..bytes = Uint8List.fromList([2, 2, 3]), ]); }); - test("Message + header, missing rest of buffer", () { - var message1 = messageWithBytes([1, 2, 3], 1); - var message2 = messageWithBytes([2, 2, 3], 2); - var message2Fragments = fragmentedMessageBuffer(message2, 5); + test('Message + header, missing rest of buffer', () { + final message1 = messageWithBytes([1, 2, 3], 1); + final message2 = messageWithBytes([2, 2, 3], 2); + final message2Fragments = fragmentedMessageBuffer(message2, 5); framer.addBytes(bufferWithMessages([message1, message2Fragments.first])); @@ -114,38 +117,38 @@ void main() { framer.addBytes(message2Fragments.last); - var messages = framer.messageQueue.map((f) => f.message).toList(); + final messages = framer.messageQueue.map((f) => f.message).toList(); expect(messages, [ - new UnknownMessage() + UnknownMessage() ..code = 1 - ..bytes = new Uint8List.fromList([1, 2, 3]), - new UnknownMessage() + ..bytes = Uint8List.fromList([1, 2, 3]), + UnknownMessage() ..code = 2 - ..bytes = new Uint8List.fromList([2, 2, 3]), + ..bytes = Uint8List.fromList([2, 2, 3]), ]); }); - test("Message body spans two packets", () { - var message = messageWithBytes([1, 2, 3, 4, 5, 6, 7], 1); - var fragments = fragmentedMessageBuffer(message, 8); + test('Message body spans two packets', () { + final message = messageWithBytes([1, 2, 3, 4, 5, 6, 7], 1); + final fragments = fragmentedMessageBuffer(message, 8); framer.addBytes(fragments.first); expect(framer.messageQueue, isEmpty); framer.addBytes(fragments.last); - var messages = framer.messageQueue.map((f) => f.message).toList(); + final messages = framer.messageQueue.map((f) => f.message).toList(); expect(messages, [ - new UnknownMessage() + UnknownMessage() ..code = 1 - ..bytes = new Uint8List.fromList([1, 2, 3, 4, 5, 6, 7]) + ..bytes = Uint8List.fromList([1, 2, 3, 4, 5, 6, 7]) ]); }); test( - "Message spans two packets, started in a packet that contained another message", + 'Message spans two packets, started in a packet that contained another message', () { - var earlierMessage = messageWithBytes([1, 2], 0); - var message = messageWithBytes([1, 2, 3, 4, 5, 6, 7], 1); + final earlierMessage = messageWithBytes([1, 2], 0); + final message = messageWithBytes([1, 2, 3, 4, 5, 6, 7], 1); framer.addBytes(bufferWithMessages( [earlierMessage, fragmentedMessageBuffer(message, 8).first])); @@ -153,20 +156,20 @@ void main() { framer.addBytes(fragmentedMessageBuffer(message, 8).last); - var messages = framer.messageQueue.map((f) => f.message).toList(); + final messages = framer.messageQueue.map((f) => f.message).toList(); expect(messages, [ - new UnknownMessage() + UnknownMessage() ..code = 0 - ..bytes = new Uint8List.fromList([1, 2]), - new UnknownMessage() + ..bytes = Uint8List.fromList([1, 2]), + UnknownMessage() ..code = 1 - ..bytes = new Uint8List.fromList([1, 2, 3, 4, 5, 6, 7]) + ..bytes = Uint8List.fromList([1, 2, 3, 4, 5, 6, 7]) ]); }); - test("Message spans three packets, only part of header in the first", () { - var earlierMessage = messageWithBytes([1, 2], 0); - var message = + test('Message spans three packets, only part of header in the first', () { + final earlierMessage = messageWithBytes([1, 2], 0); + final message = messageWithBytes([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13], 1); framer.addBytes(bufferWithMessages( @@ -182,44 +185,44 @@ void main() { fragmentedMessageBuffer(fragmentedMessageBuffer(message, 3).last, 6) .last); - var messages = framer.messageQueue.map((f) => f.message).toList(); + final messages = framer.messageQueue.map((f) => f.message).toList(); expect(messages, [ - new UnknownMessage() + UnknownMessage() ..code = 0 - ..bytes = new Uint8List.fromList([1, 2]), - new UnknownMessage() + ..bytes = Uint8List.fromList([1, 2]), + UnknownMessage() ..code = 1 ..bytes = - new Uint8List.fromList([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]) + Uint8List.fromList([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]) ]); }); - test("Frame with no data", () { + test('Frame with no data', () { framer.addBytes(bufferWithMessages([messageWithBytes([], 10)])); - var messages = framer.messageQueue.map((f) => f.message).toList(); - expect(messages, [new UnknownMessage()..code = 10]); + final messages = framer.messageQueue.map((f) => f.message).toList(); + expect(messages, [UnknownMessage()..code = 10]); }); } List messageWithBytes(List bytes, int messageID) { - var buffer = new BytesBuilder(); + final buffer = BytesBuilder(); buffer.addByte(messageID); - var lengthBuffer = new ByteData(4); + final lengthBuffer = ByteData(4); lengthBuffer.setUint32(0, bytes.length + 4); buffer.add(lengthBuffer.buffer.asUint8List()); buffer.add(bytes); return buffer.toBytes(); } -List> fragmentedMessageBuffer(List message, int pivotPoint) { - var l1 = message.sublist(0, pivotPoint); - var l2 = message.sublist(pivotPoint, message.length); - return [l1, l2]; +List fragmentedMessageBuffer(List message, int pivotPoint) { + final l1 = message.sublist(0, pivotPoint); + final l2 = message.sublist(pivotPoint, message.length); + return [castBytes(l1), castBytes(l2)]; } -List bufferWithMessages(List> messages) { - return new Uint8List.fromList(messages.expand((l) => l).toList()); +Uint8List bufferWithMessages(List> messages) { + return Uint8List.fromList(messages.expand((l) => l).toList()); } flush(MessageFramer framer) { @@ -228,10 +231,10 @@ flush(MessageFramer framer) { messageWithBytes([1, 2, 3], 1) ])); - var messages = framer.messageQueue.map((f) => f.message).toList(); + final messages = framer.messageQueue.map((f) => f.message).toList(); expect(messages, [ - new UnknownMessage() + UnknownMessage() ..code = 1 - ..bytes = new Uint8List.fromList([1, 2, 3]) + ..bytes = Uint8List.fromList([1, 2, 3]) ]); } diff --git a/test/interpolation_test.dart b/test/interpolation_test.dart index 810a528..5f57756 100644 --- a/test/interpolation_test.dart +++ b/test/interpolation_test.dart @@ -5,7 +5,7 @@ import 'package:postgres/src/query.dart'; import 'package:test/test.dart'; void main() { - test("Ensure all types/format type mappings are available and accurate", () { + test('Ensure all types/format type mappings are available and accurate', () { PostgreSQLDataType.values .where((t) => t != PostgreSQLDataType.bigSerial && t != PostgreSQLDataType.serial) @@ -17,91 +17,92 @@ void main() { }); }); - test("Ensure bigserial gets translated to int8", () { + test('Ensure bigserial gets translated to int8', () { expect( PostgreSQLFormat.dataTypeStringForDataType(PostgreSQLDataType.serial), - "int4"); + 'int4'); }); - test("Ensure serial gets translated to int4", () { + test('Ensure serial gets translated to int4', () { expect( PostgreSQLFormat.dataTypeStringForDataType( PostgreSQLDataType.bigSerial), - "int8"); + 'int8'); }); - test("Simple replacement", () { - var result = PostgreSQLFormat.substitute("@id", {"id": 20}); - expect(result, equals("20")); + test('Simple replacement', () { + final result = PostgreSQLFormat.substitute('@id', {'id': 20}); + expect(result, equals('20')); }); - test("Trailing/leading space", () { - var result = PostgreSQLFormat.substitute(" @id ", {"id": 20}); - expect(result, equals(" 20 ")); + test('Trailing/leading space', () { + final result = PostgreSQLFormat.substitute(' @id ', {'id': 20}); + expect(result, equals(' 20 ')); }); - test("Two identifiers next to eachother", () { - var result = PostgreSQLFormat.substitute("@id@bob", {"id": 20, "bob": 13}); - expect(result, equals("2013")); + test('Two identifiers next to eachother', () { + final result = + PostgreSQLFormat.substitute('@id@bob', {'id': 20, 'bob': 13}); + expect(result, equals('2013')); }); - test("Identifier with underscores", () { - var result = PostgreSQLFormat.substitute("@_one_two", {"_one_two": 12}); - expect(result, equals("12")); + test('Identifier with underscores', () { + final result = PostgreSQLFormat.substitute('@_one_two', {'_one_two': 12}); + expect(result, equals('12')); }); - test("Identifier with type info", () { - var result = PostgreSQLFormat.substitute("@id:int2", {"id": 12}); - expect(result, equals("12")); + test('Identifier with type info', () { + final result = PostgreSQLFormat.substitute('@id:int2', {'id': 12}); + expect(result, equals('12')); }); - test("Identifiers next to eachother with type info", () { - var result = PostgreSQLFormat.substitute( - "@id:int2@foo:float4", {"id": 12, "foo": 2.0}); - expect(result, equals("122.0")); + test('Identifiers next to eachother with type info', () { + final result = PostgreSQLFormat.substitute( + '@id:int2@foo:float4', {'id': 12, 'foo': 2.0}); + expect(result, equals('122.0')); }); - test("Disambiguate PostgreSQL typecast", () { - var result = PostgreSQLFormat.substitute("@id::jsonb", {"id": "12"}); + test('Disambiguate PostgreSQL typecast', () { + final result = PostgreSQLFormat.substitute('@id::jsonb', {'id': '12'}); expect(result, "'12'::jsonb"); }); - test("PostgreSQL typecast appears in query", () { - var results = PostgreSQLFormat.substitute( + test('PostgreSQL typecast appears in query', () { + final results = PostgreSQLFormat.substitute( "SELECT * FROM t WHERE id=@id:int2 WHERE blob=@blob::jsonb AND blob='{\"a\":1}'::jsonb", - {"id": 2, "blob": "{\"key\":\"value\"}"}); + {'id': 2, 'blob': '{"key":"value"}'}); expect(results, "SELECT * FROM t WHERE id=2 WHERE blob='{\"key\":\"value\"}'::jsonb AND blob='{\"a\":1}'::jsonb"); }); - test("Can both provide type and typecast", () { - var results = PostgreSQLFormat.substitute( - "SELECT * FROM t WHERE id=@id:int2::int4", - {"id": 2, "blob": "{\"key\":\"value\"}"}); + test('Can both provide type and typecast', () { + final results = PostgreSQLFormat.substitute( + 'SELECT * FROM t WHERE id=@id:int2::int4', + {'id': 2, 'blob': '{"key":"value"}'}); - expect(results, "SELECT * FROM t WHERE id=2::int4"); + expect(results, 'SELECT * FROM t WHERE id=2::int4'); }); - test("UTF16 symbols with quotes", () { - var value = "'©™®'"; - var results = PostgreSQLFormat.substitute( - "INSERT INTO t (t) VALUES (@t)", {"t": value}); + test('UTF16 symbols with quotes', () { + final value = "'©™®'"; + final results = PostgreSQLFormat.substitute( + 'INSERT INTO t (t) VALUES (@t)', {'t': value}); expect(results, "INSERT INTO t (t) VALUES ('''©™®''')"); }); - test("UTF16 symbols with backslash", () { - var value = "'©\\™®'"; - var results = PostgreSQLFormat.substitute( - "INSERT INTO t (t) VALUES (@t)", {"t": value}); + test('UTF16 symbols with backslash', () { + final value = "'©\\™®'"; + final results = PostgreSQLFormat.substitute( + 'INSERT INTO t (t) VALUES (@t)', {'t': value}); expect(results, "INSERT INTO t (t) VALUES ( E'''©\\\\™®''')"); }); - test("String identifiers get escaped", () { - var result = PostgreSQLFormat.substitute( - "@id:text @foo", {"id": "1';select", "foo": "3\\4"}); + test('String identifiers get escaped', () { + final result = PostgreSQLFormat.substitute( + '@id:text @foo', {'id': "1';select", 'foo': '3\\4'}); // ' 1 ' ' ; s e l e c t ' sp sp E ' 3 \ \ 4 ' expect(utf8.encode(result), [ @@ -129,7 +130,7 @@ void main() { ]); }); - test("JSONB operator does not throw", () { + test('JSONB operator does not throw', () { final query = "SELECT id FROM table WHERE data @> '{\"key\": \"value\"}'"; final results = PostgreSQLFormat.substitute(query, {}); diff --git a/test/json_test.dart b/test/json_test.dart index 8a95e1d..cee5b27 100644 --- a/test/json_test.dart +++ b/test/json_test.dart @@ -5,145 +5,145 @@ void main() { PostgreSQLConnection connection; setUp(() async { - connection = new PostgreSQLConnection("localhost", 5432, "dart_test", - username: "dart", password: "dart"); + connection = PostgreSQLConnection('localhost', 5432, 'dart_test', + username: 'dart', password: 'dart'); await connection.open(); - await connection.execute(""" + await connection.execute(''' CREATE TEMPORARY TABLE t (j jsonb) - """); + '''); }); tearDown(() async { await connection?.close(); }); - group("Storage", () { - test("Can store JSON String", () async { + group('Storage', () { + test('Can store JSON String', () async { var result = await connection .query("INSERT INTO t (j) VALUES ('\"xyz\"'::jsonb) RETURNING j"); expect(result, [ - ["xyz"] + ['xyz'] ]); - result = await connection.query("SELECT j FROM t"); + result = await connection.query('SELECT j FROM t'); expect(result, [ - ["xyz"] + ['xyz'] ]); }); - test("Can store JSON String with driver type annotation", () async { + test('Can store JSON String with driver type annotation', () async { var result = await connection.query( - "INSERT INTO t (j) VALUES (@a:jsonb) RETURNING j", - substitutionValues: {"a": "xyz"}); + 'INSERT INTO t (j) VALUES (@a:jsonb) RETURNING j', + substitutionValues: {'a': 'xyz'}); expect(result, [ - ["xyz"] + ['xyz'] ]); - result = await connection.query("SELECT j FROM t"); + result = await connection.query('SELECT j FROM t'); expect(result, [ - ["xyz"] + ['xyz'] ]); }); - test("Can store JSON Number", () async { + test('Can store JSON Number', () async { var result = await connection .query("INSERT INTO t (j) VALUES ('4'::jsonb) RETURNING j"); expect(result, [ [4] ]); - result = await connection.query("SELECT j FROM t"); + result = await connection.query('SELECT j FROM t'); expect(result, [ [4] ]); }); - test("Can store JSON Number with driver type annotation", () async { + test('Can store JSON Number with driver type annotation', () async { var result = await connection.query( - "INSERT INTO t (j) VALUES (@a:jsonb) RETURNING j", - substitutionValues: {"a": 4}); + 'INSERT INTO t (j) VALUES (@a:jsonb) RETURNING j', + substitutionValues: {'a': 4}); expect(result, [ [4] ]); - result = await connection.query("SELECT j FROM t"); + result = await connection.query('SELECT j FROM t'); expect(result, [ [4] ]); }); - test("Can store JSON map", () async { + test('Can store JSON map', () async { var result = await connection .query("INSERT INTO t (j) VALUES ('{\"a\":4}') RETURNING j"); expect(result, [ [ - {"a": 4} + {'a': 4} ] ]); - result = await connection.query("SELECT j FROM t"); + result = await connection.query('SELECT j FROM t'); expect(result, [ [ - {"a": 4} + {'a': 4} ] ]); }); - test("Can store JSON map with driver type annotation", () async { + test('Can store JSON map with driver type annotation', () async { var result = await connection.query( - "INSERT INTO t (j) VALUES (@a:jsonb) RETURNING j", + 'INSERT INTO t (j) VALUES (@a:jsonb) RETURNING j', substitutionValues: { - "a": {"a": 4} + 'a': {'a': 4} }); expect(result, [ [ - {"a": 4} + {'a': 4} ] ]); - result = await connection.query("SELECT j FROM t"); + result = await connection.query('SELECT j FROM t'); expect(result, [ [ - {"a": 4} + {'a': 4} ] ]); }); - test("Can store JSON list", () async { + test('Can store JSON list', () async { var result = await connection .query("INSERT INTO t (j) VALUES ('[{\"a\":4}]') RETURNING j"); expect(result, [ [ [ - {"a": 4} + {'a': 4} ] ] ]); - result = await connection.query("SELECT j FROM t"); + result = await connection.query('SELECT j FROM t'); expect(result, [ [ [ - {"a": 4} + {'a': 4} ] ] ]); }); - test("Can store JSON list with driver type annotation", () async { + test('Can store JSON list with driver type annotation', () async { var result = await connection.query( - "INSERT INTO t (j) VALUES (@a:jsonb) RETURNING j", + 'INSERT INTO t (j) VALUES (@a:jsonb) RETURNING j', substitutionValues: { - "a": [ - {"a": 4} + 'a': [ + {'a': 4} ] }); expect(result, [ [ [ - {"a": 4} + {'a': 4} ] ] ]); - result = await connection.query("SELECT j FROM t"); + result = await connection.query('SELECT j FROM t'); expect(result, [ [ [ - {"a": 4} + {'a': 4} ] ] ]); diff --git a/test/map_return_test.dart b/test/map_return_test.dart index 2a1de3b..bdcb463 100644 --- a/test/map_return_test.dart +++ b/test/map_return_test.dart @@ -6,17 +6,17 @@ void main() { InterceptingConnection connection; setUp(() async { - connection = new InterceptingConnection("localhost", 5432, "dart_test", - username: "dart", password: "dart"); + connection = InterceptingConnection('localhost', 5432, 'dart_test', + username: 'dart', password: 'dart'); await connection.open(); - await connection.execute(""" + await connection.execute(''' CREATE TEMPORARY TABLE t (id int primary key, name text) - """); + '''); - await connection.execute(""" + await connection.execute(''' CREATE TEMPORARY TABLE u (id int primary key, name text, t_id int references t (id)) - """); + '''); await connection.execute("INSERT INTO t (id, name) VALUES (1, 'a')"); await connection.execute("INSERT INTO t (id, name) VALUES (2, 'b')"); @@ -33,106 +33,106 @@ void main() { await connection?.close(); }); - test("Get row map without specifying columns", () async { + test('Get row map without specifying columns', () async { final results = - await connection.mappedResultsQuery("SELECT * from t ORDER BY id ASC"); + await connection.mappedResultsQuery('SELECT * from t ORDER BY id ASC'); expect(results, [ { - "t": {"id": 1, "name": "a"} + 't': {'id': 1, 'name': 'a'} }, { - "t": {"id": 2, "name": "b"} + 't': {'id': 2, 'name': 'b'} }, { - "t": {"id": 3, "name": "c"} + 't': {'id': 3, 'name': 'c'} }, ]); }); - test("Get row map by with specified columns", () async { + test('Get row map by with specified columns', () async { final results = await connection - .mappedResultsQuery("SELECT name, id from t ORDER BY id ASC"); + .mappedResultsQuery('SELECT name, id from t ORDER BY id ASC'); expect(results, [ { - "t": {"id": 1, "name": "a"} + 't': {'id': 1, 'name': 'a'} }, { - "t": {"id": 2, "name": "b"} + 't': {'id': 2, 'name': 'b'} }, { - "t": {"id": 3, "name": "c"} + 't': {'id': 3, 'name': 'c'} }, ]); final nextResults = await connection - .mappedResultsQuery("SELECT name from t ORDER BY name DESC"); + .mappedResultsQuery('SELECT name from t ORDER BY name DESC'); expect(nextResults, [ { - "t": {"name": "c"} + 't': {'name': 'c'} }, { - "t": {"name": "b"} + 't': {'name': 'b'} }, { - "t": {"name": "a"} + 't': {'name': 'a'} }, ]); }); - test("Get row with joined row", () async { + test('Get row with joined row', () async { final results = await connection.mappedResultsQuery( - "SELECT t.name, t.id, u.id, u.name, u.t_id from t LEFT OUTER JOIN u ON t.id=u.t_id ORDER BY t.id ASC"); + 'SELECT t.name, t.id, u.id, u.name, u.t_id from t LEFT OUTER JOIN u ON t.id=u.t_id ORDER BY t.id ASC'); expect(results, [ { - "t": {"name": "a", "id": 1}, - "u": {"id": 1, "name": "ua", "t_id": 1} + 't': {'name': 'a', 'id': 1}, + 'u': {'id': 1, 'name': 'ua', 't_id': 1} }, { - "t": {"name": "a", "id": 1}, - "u": {"id": 2, "name": "ub", "t_id": 1} + 't': {'name': 'a', 'id': 1}, + 'u': {'id': 2, 'name': 'ub', 't_id': 1} }, { - "t": {"name": "b", "id": 2}, - "u": {"id": 3, "name": "uc", "t_id": 2} + 't': {'name': 'b', 'id': 2}, + 'u': {'id': 3, 'name': 'uc', 't_id': 2} }, { - "t": {"name": "c", "id": 3}, - "u": {"name": null, "id": null, "t_id": null} + 't': {'name': 'c', 'id': 3}, + 'u': {'name': null, 'id': null, 't_id': null} } ]); }); - test("Table names get cached", () async { - final regex = new RegExp( + test('Table names get cached', () async { + final regex = RegExp( "SELECT relname FROM pg_class WHERE relkind='r' AND oid IN \\(([0-9]*)\\) ORDER BY oid ASC"); final oids = []; - await connection.mappedResultsQuery("SELECT id FROM t"); + await connection.mappedResultsQuery('SELECT id FROM t'); expect(connection.queries.length, 1); var match = regex.firstMatch(connection.queries.first); oids.add(match.group(1)); connection.queries.clear(); - await connection.mappedResultsQuery("SELECT id FROM t"); + await connection.mappedResultsQuery('SELECT id FROM t'); expect(connection.queries.length, 0); await connection.mappedResultsQuery( - "SELECT t.id, u.id FROM t LEFT OUTER JOIN u ON t.id=u.t_id"); + 'SELECT t.id, u.id FROM t LEFT OUTER JOIN u ON t.id=u.t_id'); expect(connection.queries.length, 1); match = regex.firstMatch(connection.queries.first); expect(oids.contains(match.group(1)), false); oids.add(match.group(1)); connection.queries.clear(); - await connection.mappedResultsQuery("SELECT u.id FROM u"); + await connection.mappedResultsQuery('SELECT u.id FROM u'); expect(connection.queries.length, 0); }); - test("Non-table mappedResultsQuery succeeds", () async { - final result = await connection.mappedResultsQuery("SELECT 1"); + test('Non-table mappedResultsQuery succeeds', () async { + final result = await connection.mappedResultsQuery('SELECT 1'); expect(result, [ { - null: {"?column?": 1} + null: {'?column?': 1} } ]); }); @@ -140,15 +140,15 @@ void main() { class InterceptingConnection extends PostgreSQLConnection { InterceptingConnection(String host, int port, String databaseName, - {String username: null, String password: null}) + {String username, String password}) : super(host, port, databaseName, username: username, password: password); List queries = []; @override Future>> query(String fmtString, - {Map substitutionValues: null, - bool allowReuse: true, + {Map substitutionValues, + bool allowReuse = true, int timeoutInSeconds}) { queries.add(fmtString); return super.query(fmtString, diff --git a/test/notification_test.dart b/test/notification_test.dart index fbe32e7..576450f 100644 --- a/test/notification_test.dart +++ b/test/notification_test.dart @@ -4,13 +4,13 @@ import 'package:postgres/postgres.dart'; import 'package:test/test.dart'; void main() { - group("Successful notifications", () { - var connection = new PostgreSQLConnection("localhost", 5432, "dart_test", - username: "dart", password: "dart"); + group('Successful notifications', () { + var connection = PostgreSQLConnection('localhost', 5432, 'dart_test', + username: 'dart', password: 'dart'); setUp(() async { - connection = new PostgreSQLConnection("localhost", 5432, "dart_test", - username: "dart", password: "dart"); + connection = PostgreSQLConnection('localhost', 5432, 'dart_test', + username: 'dart', password: 'dart'); await connection.open(); }); @@ -18,91 +18,91 @@ void main() { await connection.close(); }); - test("Notification Response", () async { - var channel = 'virtual'; - var payload = 'This is the payload'; - var futureMsg = connection.notifications.first; - await connection.execute("LISTEN $channel;" + test('Notification Response', () async { + final channel = 'virtual'; + final payload = 'This is the payload'; + final futureMsg = connection.notifications.first; + await connection.execute('LISTEN $channel;' "NOTIFY $channel, '$payload';"); - var msg = await futureMsg.timeout(new Duration(milliseconds: 200)); + final msg = await futureMsg.timeout(Duration(milliseconds: 200)); expect(msg.channel, channel); expect(msg.payload, payload); }); - test("Notification Response empty payload", () async { - var channel = 'virtual'; - var futureMsg = connection.notifications.first; - await connection.execute("LISTEN $channel;" - "NOTIFY $channel;"); + test('Notification Response empty payload', () async { + final channel = 'virtual'; + final futureMsg = connection.notifications.first; + await connection.execute('LISTEN $channel;' + 'NOTIFY $channel;'); - var msg = await futureMsg.timeout(new Duration(milliseconds: 200)); + final msg = await futureMsg.timeout(Duration(milliseconds: 200)); expect(msg.channel, channel); expect(msg.payload, ''); }); - test("Notification UNLISTEN", () async { - var channel = 'virtual'; - var payload = 'This is the payload'; + test('Notification UNLISTEN', () async { + final channel = 'virtual'; + final payload = 'This is the payload'; var futureMsg = connection.notifications.first; - await connection.execute("LISTEN $channel;" + await connection.execute('LISTEN $channel;' "NOTIFY $channel, '$payload';"); - var msg = await futureMsg.timeout(new Duration(milliseconds: 200)); + final msg = await futureMsg.timeout(Duration(milliseconds: 200)); expect(msg.channel, channel); expect(msg.payload, payload); - await connection.execute("UNLISTEN $channel;"); + await connection.execute('UNLISTEN $channel;'); futureMsg = connection.notifications.first; try { await connection.execute("NOTIFY $channel, '$payload';"); - await futureMsg.timeout(new Duration(milliseconds: 200)); + await futureMsg.timeout(Duration(milliseconds: 200)); fail('There should be no notification'); } on TimeoutException catch (_) {} }); - test("Notification many channel", () async { - Map countResponse = new Map(); + test('Notification many channel', () async { + final countResponse = {}; int totalCountResponse = 0; - Completer finishExecute = new Completer(); + final finishExecute = Completer(); connection.notifications.listen((msg) { - int count = countResponse[msg.channel]; + final count = countResponse[msg.channel]; countResponse[msg.channel] = (count ?? 0) + 1; totalCountResponse++; if (totalCountResponse == 20) finishExecute.complete(); }); - var channel1 = 'virtual1'; - var channel2 = 'virtual2'; + final channel1 = 'virtual1'; + final channel2 = 'virtual2'; - var notifier = () async { + final notifier = () async { for (int i = 0; i < 5; i++) { - await connection.execute("NOTIFY $channel1;" - "NOTIFY $channel2;"); + await connection.execute('NOTIFY $channel1;' + 'NOTIFY $channel2;'); } }; - await connection.execute("LISTEN $channel1;"); + await connection.execute('LISTEN $channel1;'); await notifier(); - await connection.execute("LISTEN $channel2;"); + await connection.execute('LISTEN $channel2;'); await notifier(); - await connection.execute("UNLISTEN $channel1;"); + await connection.execute('UNLISTEN $channel1;'); await notifier(); - await connection.execute("UNLISTEN $channel2;"); + await connection.execute('UNLISTEN $channel2;'); await notifier(); - await finishExecute.future.timeout(new Duration(milliseconds: 200)); + await finishExecute.future.timeout(Duration(milliseconds: 200)); expect(countResponse[channel1], 10); expect(countResponse[channel2], 10); - }, timeout: new Timeout(new Duration(seconds: 5))); + }, timeout: Timeout(Duration(seconds: 5))); }); } diff --git a/test/query_reuse_test.dart b/test/query_reuse_test.dart index 77127ae..a6984eb 100644 --- a/test/query_reuse_test.dart +++ b/test/query_reuse_test.dart @@ -1,406 +1,408 @@ -import 'package:postgres/postgres.dart'; -import 'package:test/test.dart'; import 'dart:async'; import 'dart:mirrors'; +import 'package:test/test.dart'; + +import 'package:postgres/postgres.dart'; + String sid(String id, PostgreSQLDataType dt) => PostgreSQLFormat.id(id, type: dt); void main() { - group("Retaining type information", () { + group('Retaining type information', () { PostgreSQLConnection connection; setUp(() async { - connection = new PostgreSQLConnection("localhost", 5432, "dart_test", - username: "dart", password: "dart"); + connection = PostgreSQLConnection('localhost', 5432, 'dart_test', + username: 'dart', password: 'dart'); await connection.open(); await connection.execute( - "CREATE TEMPORARY TABLE t (i int, s serial, bi bigint, bs bigserial, bl boolean, si smallint, t text, f real, d double precision, dt date, ts timestamp, tsz timestamptz)"); + 'CREATE TEMPORARY TABLE t (i int, s serial, bi bigint, bs bigserial, bl boolean, si smallint, t text, f real, d double precision, dt date, ts timestamp, tsz timestamptz)'); }); tearDown(() async { await connection.close(); }); - test("Call query multiple times with all parameter types succeeds", + test('Call query multiple times with all parameter types succeeds', () async { - var insertQueryString = - "INSERT INTO t (i, bi, bl, si, t, f, d, dt, ts, tsz) VALUES " - "(${sid("i", PostgreSQLDataType.integer)}, ${sid("bi", PostgreSQLDataType.bigInteger)}," - "${sid("bl", PostgreSQLDataType.boolean)}, ${sid("si", PostgreSQLDataType.smallInteger)}," - "${sid("t", PostgreSQLDataType.text)}, ${sid("f", PostgreSQLDataType.real)}," - "${sid("d", PostgreSQLDataType.double)}, ${sid("dt", PostgreSQLDataType.date)}," - "${sid("ts", PostgreSQLDataType.timestampWithoutTimezone)}, ${sid("tsz", PostgreSQLDataType.timestampWithTimezone)}" - ") returning i, s, bi, bs, bl, si, t, f, d, dt, ts, tsz"; + final insertQueryString = + 'INSERT INTO t (i, bi, bl, si, t, f, d, dt, ts, tsz) VALUES ' + '(${sid('i', PostgreSQLDataType.integer)}, ${sid('bi', PostgreSQLDataType.bigInteger)},' + '${sid('bl', PostgreSQLDataType.boolean)}, ${sid('si', PostgreSQLDataType.smallInteger)},' + '${sid('t', PostgreSQLDataType.text)}, ${sid('f', PostgreSQLDataType.real)},' + '${sid('d', PostgreSQLDataType.double)}, ${sid('dt', PostgreSQLDataType.date)},' + '${sid('ts', PostgreSQLDataType.timestampWithoutTimezone)}, ${sid('tsz', PostgreSQLDataType.timestampWithTimezone)}' + ') returning i, s, bi, bs, bl, si, t, f, d, dt, ts, tsz'; var results = await connection.query(insertQueryString, substitutionValues: { - "i": 1, - "bi": 2, - "bl": true, - "si": 3, - "t": "foobar", - "f": 5.0, - "d": 6.0, - "dt": new DateTime.utc(2000), - "ts": new DateTime.utc(2000, 2), - "tsz": new DateTime.utc(2000, 3) + 'i': 1, + 'bi': 2, + 'bl': true, + 'si': 3, + 't': 'foobar', + 'f': 5.0, + 'd': 6.0, + 'dt': DateTime.utc(2000), + 'ts': DateTime.utc(2000, 2), + 'tsz': DateTime.utc(2000, 3) }); expect(hasCachedQueryNamed(connection, insertQueryString), true); - var expectedRow1 = [ + final expectedRow1 = [ 1, 1, 2, 1, true, 3, - "foobar", + 'foobar', 5.0, 6.0, - new DateTime.utc(2000), - new DateTime.utc(2000, 2), - new DateTime.utc(2000, 3) + DateTime.utc(2000), + DateTime.utc(2000, 2), + DateTime.utc(2000, 3) ]; expect(results, [expectedRow1]); results = await connection.query(insertQueryString, substitutionValues: { - "i": 2, - "bi": 3, - "bl": false, - "si": 4, - "t": "barfoo", - "f": 6.0, - "d": 7.0, - "dt": new DateTime.utc(2001), - "ts": new DateTime.utc(2001, 2), - "tsz": new DateTime.utc(2001, 3) + 'i': 2, + 'bi': 3, + 'bl': false, + 'si': 4, + 't': 'barfoo', + 'f': 6.0, + 'd': 7.0, + 'dt': DateTime.utc(2001), + 'ts': DateTime.utc(2001, 2), + 'tsz': DateTime.utc(2001, 3) }); expect(hasCachedQueryNamed(connection, insertQueryString), true); - var expectedRow2 = [ + final expectedRow2 = [ 2, 2, 3, 2, false, 4, - "barfoo", + 'barfoo', 6.0, 7.0, - new DateTime.utc(2001), - new DateTime.utc(2001, 2), - new DateTime.utc(2001, 3) + DateTime.utc(2001), + DateTime.utc(2001, 2), + DateTime.utc(2001, 3) ]; expect(results, [expectedRow2]); results = await connection - .query("select i, s, bi, bs, bl, si, t, f, d, dt, ts, tsz from t"); + .query('select i, s, bi, bs, bl, si, t, f, d, dt, ts, tsz from t'); expect(results, [expectedRow1, expectedRow2]); expect( hasCachedQueryNamed(connection, - "select i, s, bi, bs, bl, si, t, f, d, dt, ts, tsz from t"), + 'select i, s, bi, bs, bl, si, t, f, d, dt, ts, tsz from t'), true); results = await connection.query( - "select i, s, bi, bs, bl, si, t, f, d, dt, ts, tsz from t where i < @i", - substitutionValues: {"i": 0}); + 'select i, s, bi, bs, bl, si, t, f, d, dt, ts, tsz from t where i < @i', + substitutionValues: {'i': 0}); expect(results, []); results = await connection.query( - "select i, s, bi, bs, bl, si, t, f, d, dt, ts, tsz from t where i < @i", - substitutionValues: {"i": 2}); + 'select i, s, bi, bs, bl, si, t, f, d, dt, ts, tsz from t where i < @i', + substitutionValues: {'i': 2}); expect(results, [expectedRow1]); results = await connection.query( - "select i, s, bi, bs, bl, si, t, f, d, dt, ts, tsz from t where i < @i", - substitutionValues: {"i": 5}); + 'select i, s, bi, bs, bl, si, t, f, d, dt, ts, tsz from t where i < @i', + substitutionValues: {'i': 5}); expect(results, [expectedRow1, expectedRow2]); expect(hasCachedQueryNamed(connection, insertQueryString), true); }); - test("Call query multiple times without type data succeeds ", () async { - var insertQueryString = - "INSERT INTO t (i, bi, bl, si, t, f, d, dt, ts, tsz) VALUES " - "(@i, @bi, @bl, @si, @t, @f, @d, @dt, @ts, @tsz) " - "returning i, s, bi, bs, bl, si, t, f, d, dt, ts, tsz"; + test('Call query multiple times without type data succeeds ', () async { + final insertQueryString = + 'INSERT INTO t (i, bi, bl, si, t, f, d, dt, ts, tsz) VALUES ' + '(@i, @bi, @bl, @si, @t, @f, @d, @dt, @ts, @tsz) ' + 'returning i, s, bi, bs, bl, si, t, f, d, dt, ts, tsz'; var results = await connection.query(insertQueryString, substitutionValues: { - "i": 1, - "bi": 2, - "bl": true, - "si": 3, - "t": "foobar", - "f": 5.0, - "d": 6.0, - "dt": new DateTime.utc(2000), - "ts": new DateTime.utc(2000, 2), - "tsz": new DateTime.utc(2000, 3) + 'i': 1, + 'bi': 2, + 'bl': true, + 'si': 3, + 't': 'foobar', + 'f': 5.0, + 'd': 6.0, + 'dt': DateTime.utc(2000), + 'ts': DateTime.utc(2000, 2), + 'tsz': DateTime.utc(2000, 3) }); - var expectedRow1 = [ + final expectedRow1 = [ 1, 1, 2, 1, true, 3, - "foobar", + 'foobar', 5.0, 6.0, - new DateTime.utc(2000), - new DateTime.utc(2000, 2), - new DateTime.utc(2000, 3) + DateTime.utc(2000), + DateTime.utc(2000, 2), + DateTime.utc(2000, 3) ]; expect(results, [expectedRow1]); results = await connection.query(insertQueryString, substitutionValues: { - "i": 2, - "bi": 3, - "bl": false, - "si": 4, - "t": "barfoo", - "f": 6.0, - "d": 7.0, - "dt": new DateTime.utc(2001), - "ts": new DateTime.utc(2001, 2), - "tsz": new DateTime.utc(2001, 3) + 'i': 2, + 'bi': 3, + 'bl': false, + 'si': 4, + 't': 'barfoo', + 'f': 6.0, + 'd': 7.0, + 'dt': DateTime.utc(2001), + 'ts': DateTime.utc(2001, 2), + 'tsz': DateTime.utc(2001, 3) }); - var expectedRow2 = [ + final expectedRow2 = [ 2, 2, 3, 2, false, 4, - "barfoo", + 'barfoo', 6.0, 7.0, - new DateTime.utc(2001), - new DateTime.utc(2001, 2), - new DateTime.utc(2001, 3) + DateTime.utc(2001), + DateTime.utc(2001, 2), + DateTime.utc(2001, 3) ]; expect(results, [expectedRow2]); }); - test("Call query multiple times with partial parameter type info succeeds", + test('Call query multiple times with partial parameter type info succeeds', () async { - var insertQueryString = - "INSERT INTO t (i, bi, bl, si, t, f, d, dt, ts, tsz) VALUES " - "(${sid("i", PostgreSQLDataType.integer)}, @bi," - "${sid("bl", PostgreSQLDataType.boolean)}, @si," - "${sid("t", PostgreSQLDataType.text)}, @f," - "${sid("d", PostgreSQLDataType.double)}, @dt," - "${sid("ts", PostgreSQLDataType.timestampWithoutTimezone)}, @tsz" - ") returning i, s, bi, bs, bl, si, t, f, d, dt, ts, tsz"; + final insertQueryString = + 'INSERT INTO t (i, bi, bl, si, t, f, d, dt, ts, tsz) VALUES ' + '(${sid('i', PostgreSQLDataType.integer)}, @bi,' + '${sid('bl', PostgreSQLDataType.boolean)}, @si,' + '${sid('t', PostgreSQLDataType.text)}, @f,' + '${sid('d', PostgreSQLDataType.double)}, @dt,' + '${sid('ts', PostgreSQLDataType.timestampWithoutTimezone)}, @tsz' + ') returning i, s, bi, bs, bl, si, t, f, d, dt, ts, tsz'; var results = await connection.query(insertQueryString, substitutionValues: { - "i": 1, - "bi": 2, - "bl": true, - "si": 3, - "t": "foobar", - "f": 5.0, - "d": 6.0, - "dt": new DateTime.utc(2000), - "ts": new DateTime.utc(2000, 2), - "tsz": new DateTime.utc(2000, 3) + 'i': 1, + 'bi': 2, + 'bl': true, + 'si': 3, + 't': 'foobar', + 'f': 5.0, + 'd': 6.0, + 'dt': DateTime.utc(2000), + 'ts': DateTime.utc(2000, 2), + 'tsz': DateTime.utc(2000, 3) }); - var expectedRow1 = [ + final expectedRow1 = [ 1, 1, 2, 1, true, 3, - "foobar", + 'foobar', 5.0, 6.0, - new DateTime.utc(2000), - new DateTime.utc(2000, 2), - new DateTime.utc(2000, 3) + DateTime.utc(2000), + DateTime.utc(2000, 2), + DateTime.utc(2000, 3) ]; expect(results, [expectedRow1]); results = await connection.query(insertQueryString, substitutionValues: { - "i": 2, - "bi": 3, - "bl": false, - "si": 4, - "t": "barfoo", - "f": 6.0, - "d": 7.0, - "dt": new DateTime.utc(2001), - "ts": new DateTime.utc(2001, 2), - "tsz": new DateTime.utc(2001, 3) + 'i': 2, + 'bi': 3, + 'bl': false, + 'si': 4, + 't': 'barfoo', + 'f': 6.0, + 'd': 7.0, + 'dt': DateTime.utc(2001), + 'ts': DateTime.utc(2001, 2), + 'tsz': DateTime.utc(2001, 3) }); - var expectedRow2 = [ + final expectedRow2 = [ 2, 2, 3, 2, false, 4, - "barfoo", + 'barfoo', 6.0, 7.0, - new DateTime.utc(2001), - new DateTime.utc(2001, 2), - new DateTime.utc(2001, 3) + DateTime.utc(2001), + DateTime.utc(2001, 2), + DateTime.utc(2001, 3) ]; expect(results, [expectedRow2]); results = await connection - .query("select i, s, bi, bs, bl, si, t, f, d, dt, ts, tsz from t"); + .query('select i, s, bi, bs, bl, si, t, f, d, dt, ts, tsz from t'); expect(results, [expectedRow1, expectedRow2]); results = await connection.query( - "select i, s, bi, bs, bl, si, t, f, d, dt, ts, tsz from t where i < @i", - substitutionValues: {"i": 0}); + 'select i, s, bi, bs, bl, si, t, f, d, dt, ts, tsz from t where i < @i', + substitutionValues: {'i': 0}); expect(results, []); results = await connection.query( - "select i, s, bi, bs, bl, si, t, f, d, dt, ts, tsz from t where i < @i", - substitutionValues: {"i": 2}); + 'select i, s, bi, bs, bl, si, t, f, d, dt, ts, tsz from t where i < @i', + substitutionValues: {'i': 2}); expect(results, [expectedRow1]); results = await connection.query( - "select i, s, bi, bs, bl, si, t, f, d, dt, ts, tsz from t where i < @i", - substitutionValues: {"i": 5}); + 'select i, s, bi, bs, bl, si, t, f, d, dt, ts, tsz from t where i < @i', + substitutionValues: {'i': 5}); expect(results, [expectedRow1, expectedRow2]); }); }); - group("Mixing prepared statements", () { + group('Mixing prepared statements', () { PostgreSQLConnection connection; setUp(() async { - connection = new PostgreSQLConnection("localhost", 5432, "dart_test", - username: "dart", password: "dart"); + connection = PostgreSQLConnection('localhost', 5432, 'dart_test', + username: 'dart', password: 'dart'); await connection.open(); await connection.execute( - "CREATE TEMPORARY TABLE t (i1 int not null, i2 int not null)"); - await connection.execute("INSERT INTO t (i1, i2) VALUES (0, 1)"); - await connection.execute("INSERT INTO t (i1, i2) VALUES (1, 2)"); - await connection.execute("INSERT INTO t (i1, i2) VALUES (2, 3)"); - await connection.execute("INSERT INTO t (i1, i2) VALUES (3, 4)"); + 'CREATE TEMPORARY TABLE t (i1 int not null, i2 int not null)'); + await connection.execute('INSERT INTO t (i1, i2) VALUES (0, 1)'); + await connection.execute('INSERT INTO t (i1, i2) VALUES (1, 2)'); + await connection.execute('INSERT INTO t (i1, i2) VALUES (2, 3)'); + await connection.execute('INSERT INTO t (i1, i2) VALUES (3, 4)'); }); tearDown(() async { await connection.close(); }); - test("Call query multiple times, mixing in unnammed queries, succeeds", + test('Call query multiple times, mixing in unnammed queries, succeeds', () async { var results = await connection.query( - "select i1, i2 from t where i1 > @i1", - substitutionValues: {"i1": 1}); + 'select i1, i2 from t where i1 > @i1', + substitutionValues: {'i1': 1}); expect(results, [ [2, 3], [3, 4] ]); - results = await connection.query("select i1,i2 from t where i1 > @i1", - substitutionValues: {"i1": 1}, allowReuse: false); + results = await connection.query('select i1,i2 from t where i1 > @i1', + substitutionValues: {'i1': 1}, allowReuse: false); expect(results, [ [2, 3], [3, 4] ]); - results = await connection.query("select i1, i2 from t where i1 > @i1", - substitutionValues: {"i1": 2}); + results = await connection.query('select i1, i2 from t where i1 > @i1', + substitutionValues: {'i1': 2}); expect(results, [ [3, 4] ]); - results = await connection.query("select i1,i2 from t where i1 > @i1", - substitutionValues: {"i1": 0}, allowReuse: false); + results = await connection.query('select i1,i2 from t where i1 > @i1', + substitutionValues: {'i1': 0}, allowReuse: false); expect(results, [ [1, 2], [2, 3], [3, 4] ]); - results = await connection.query("select i1, i2 from t where i1 > @i1", - substitutionValues: {"i1": 2}); + results = await connection.query('select i1, i2 from t where i1 > @i1', + substitutionValues: {'i1': 2}); expect(results, [ [3, 4] ]); expect( hasCachedQueryNamed( - connection, "select i1, i2 from t where i1 > @i1"), + connection, 'select i1, i2 from t where i1 > @i1'), true); expect(cachedQueryMap(connection).length, 1); }); - test("Call query multiple times, mixing in other named queries, succeeds", + test('Call query multiple times, mixing in other named queries, succeeds', () async { var results = await connection.query( - "select i1, i2 from t where i1 > @i1", - substitutionValues: {"i1": 1}); + 'select i1, i2 from t where i1 > @i1', + substitutionValues: {'i1': 1}); expect(results, [ [2, 3], [3, 4] ]); - results = await connection.query("select i1,i2 from t where i2 < @i2", - substitutionValues: {"i2": 1}); + results = await connection.query('select i1,i2 from t where i2 < @i2', + substitutionValues: {'i2': 1}); expect(results, []); - results = await connection.query("select i1, i2 from t where i1 > @i1", - substitutionValues: {"i1": 2}); + results = await connection.query('select i1, i2 from t where i1 > @i1', + substitutionValues: {'i1': 2}); expect(results, [ [3, 4] ]); - results = await connection.query("select i1,i2 from t where i2 < @i2", - substitutionValues: {"i2": 2}); + results = await connection.query('select i1,i2 from t where i2 < @i2', + substitutionValues: {'i2': 2}); expect(results, [ [0, 1] ]); - results = await connection.query("select i1, i2 from t where i1 > @i1", - substitutionValues: {"i1": 2}); + results = await connection.query('select i1, i2 from t where i1 > @i1', + substitutionValues: {'i1': 2}); expect(results, [ [3, 4] ]); expect( hasCachedQueryNamed( - connection, "select i1, i2 from t where i1 > @i1"), + connection, 'select i1, i2 from t where i1 > @i1'), true); expect( - hasCachedQueryNamed(connection, "select i1,i2 from t where i2 < @i2"), + hasCachedQueryNamed(connection, 'select i1,i2 from t where i2 < @i2'), true); expect(cachedQueryMap(connection).length, 2); }); test( - "Call a bunch of named and unnamed queries without awaiting, still process correctly", + 'Call a bunch of named and unnamed queries without awaiting, still process correctly', () async { - var futures = [ - connection.query("select i1, i2 from t where i1 > @i1", - substitutionValues: {"i1": 1}), - connection.execute("select 1"), - connection.query("select i1,i2 from t where i2 < @i2", - substitutionValues: {"i2": 1}), - connection.query("select i1, i2 from t where i1 > @i1", - substitutionValues: {"i1": 2}), - connection.query("select 1", allowReuse: false), - connection.query("select i1,i2 from t where i2 < @i2", - substitutionValues: {"i2": 2}), - connection.query("select i1, i2 from t where i1 > @i1", - substitutionValues: {"i1": 2}) + final futures = [ + connection.query('select i1, i2 from t where i1 > @i1', + substitutionValues: {'i1': 1}), + connection.execute('select 1'), + connection.query('select i1,i2 from t where i2 < @i2', + substitutionValues: {'i2': 1}), + connection.query('select i1, i2 from t where i1 > @i1', + substitutionValues: {'i1': 2}), + connection.query('select 1', allowReuse: false), + connection.query('select i1,i2 from t where i2 < @i2', + substitutionValues: {'i2': 2}), + connection.query('select i1, i2 from t where i1 > @i1', + substitutionValues: {'i1': 2}) ]; - var results = await Future.wait(futures); + final results = await Future.wait(futures); expect(results, [ [ [2, 3], @@ -423,42 +425,42 @@ void main() { ]); }); - test("Make a prepared query that has no parameters", () async { - var results = await connection.query("select 1"); + test('Make a prepared query that has no parameters', () async { + var results = await connection.query('select 1'); expect(results, [ [1] ]); - results = await connection.query("select 1"); + results = await connection.query('select 1'); expect(results, [ [1] ]); }); }); - group("Failure cases", () { - var connection = new PostgreSQLConnection("localhost", 5432, "dart_test", - username: "dart", password: "dart"); + group('Failure cases', () { + var connection = PostgreSQLConnection('localhost', 5432, 'dart_test', + username: 'dart', password: 'dart'); setUp(() async { - connection = new PostgreSQLConnection("localhost", 5432, "dart_test", - username: "dart", password: "dart"); + connection = PostgreSQLConnection('localhost', 5432, 'dart_test', + username: 'dart', password: 'dart'); await connection.open(); await connection.execute( - "CREATE TEMPORARY TABLE t (i int, s serial, bi bigint, bs bigserial, bl boolean, si smallint, t text, f real, d double precision, dt date, ts timestamp, tsz timestamptz)"); + 'CREATE TEMPORARY TABLE t (i int, s serial, bi bigint, bs bigserial, bl boolean, si smallint, t text, f real, d double precision, dt date, ts timestamp, tsz timestamptz)'); await connection.execute( - "CREATE TEMPORARY TABLE u (i1 int not null, i2 int not null);"); + 'CREATE TEMPORARY TABLE u (i1 int not null, i2 int not null);'); await connection - .execute("CREATE TEMPORARY TABLE n (i1 int, i2 int not null);"); + .execute('CREATE TEMPORARY TABLE n (i1 int, i2 int not null);'); }); tearDown(() async { await connection.close(); }); - test("A failed parse does not generate cached query", () async { + test('A failed parse does not generate cached query', () async { try { - await connection.query("ljkasd"); + await connection.query('ljkasd'); expect(true, false); } on PostgreSQLException {} @@ -466,13 +468,13 @@ void main() { }); test( - "Trying to parse/describe a query with inaccurate types fails and does not cache query", + 'Trying to parse/describe a query with inaccurate types fails and does not cache query', () async { - var string = - "insert into u (i1, i2) values (@i1:text, @i2:text) returning i1, i2"; + final string = + 'insert into u (i1, i2) values (@i1:text, @i2:text) returning i1, i2'; try { await connection - .query(string, substitutionValues: {"i1": "foo", "i2": "bar"}); + .query(string, substitutionValues: {'i1': 'foo', 'i2': 'bar'}); expect(true, false); } on PostgreSQLException {} @@ -481,23 +483,24 @@ void main() { }); test( - "A failed bind on initial query fails query, but can still make query later", + 'A failed bind on initial query fails query, but can still make query later', () async { - var string = "insert into u (i1, i2) values (@i1, @i2) returning i1, i2"; + final string = + 'insert into u (i1, i2) values (@i1, @i2) returning i1, i2'; try { await connection - .query(string, substitutionValues: {"i1": "foo", "i2": "bar"}); + .query(string, substitutionValues: {'i1': 'foo', 'i2': 'bar'}); expect(true, false); } on PostgreSQLException {} expect(hasCachedQueryNamed(connection, string), false); - var results = await connection.query("select i1, i2 from u"); + var results = await connection.query('select i1, i2 from u'); expect(results, []); - await connection.query(string, substitutionValues: {"i1": 1, "i2": 2}); - results = await connection.query("select i1, i2 from u"); + await connection.query(string, substitutionValues: {'i1': 1, 'i2': 2}); + results = await connection.query('select i1, i2 from u'); expect(results, [ [1, 2] ]); @@ -505,28 +508,28 @@ void main() { }); test( - "Cached query that works the first time, wrong type for params the next time throws early error but can still be used", + 'Cached query that works the first time, wrong type for params the next time throws early error but can still be used', () async { await connection.query( - "insert into u (i1, i2) values (@i1:int4, @i2:int4) returning i1, i2", - substitutionValues: {"i1": 1, "i2": 2}); + 'insert into u (i1, i2) values (@i1:int4, @i2:int4) returning i1, i2', + substitutionValues: {'i1': 1, 'i2': 2}); await connection.query( - "insert into u (i1, i2) values (@i1:int4, @i2:int4) returning i1, i2", - substitutionValues: {"i1": 2, "i2": 3}); + 'insert into u (i1, i2) values (@i1:int4, @i2:int4) returning i1, i2', + substitutionValues: {'i1': 2, 'i2': 3}); - var string = "select i1, i2 from u where i1 = @i:int4"; + final string = 'select i1, i2 from u where i1 = @i:int4'; var results = - await connection.query(string, substitutionValues: {"i": 1}); + await connection.query(string, substitutionValues: {'i': 1}); expect(results, [ [1, 2] ]); expect(hasCachedQueryNamed(connection, string), true); try { - await connection.query(string, substitutionValues: {"i": "foo"}); + await connection.query(string, substitutionValues: {'i': 'foo'}); } on FormatException {} - results = await connection.query(string, substitutionValues: {"i": 2}); + results = await connection.query(string, substitutionValues: {'i': 2}); expect(results, [ [2, 3] ]); @@ -534,20 +537,20 @@ void main() { }); test( - "Send two queries that will be the same prepared statement async, first one fails on bind", + 'Send two queries that will be the same prepared statement async, first one fails on bind', () async { await connection.query( - "insert into u (i1, i2) values (@i1:int4, @i2:int4) returning i1, i2", - substitutionValues: {"i1": 1, "i2": 2}, + 'insert into u (i1, i2) values (@i1:int4, @i2:int4) returning i1, i2', + substitutionValues: {'i1': 1, 'i2': 2}, allowReuse: false); - var string = "select i1, i2 from u where i1 = @i:int4"; + final string = 'select i1, i2 from u where i1 = @i:int4'; // ignore: unawaited_futures connection - .query(string, substitutionValues: {"i": "foo"}).catchError((e) {}); + .query(string, substitutionValues: {'i': 'foo'}).catchError((e) {}); - var results = - await connection.query(string, substitutionValues: {"i": 1}); + final results = + await connection.query(string, substitutionValues: {'i': 1}); expect(results, [ [1, 2] @@ -559,8 +562,8 @@ void main() { } Map cachedQueryMap(PostgreSQLConnection connection) { - var cacheMirror = reflect(connection).type.declarations.values.firstWhere( - (DeclarationMirror dm) => dm.simpleName.toString().contains("_cache")); + final cacheMirror = reflect(connection).type.declarations.values.firstWhere( + (DeclarationMirror dm) => dm.simpleName.toString().contains('_cache')); return reflect(connection) .getField(cacheMirror.simpleName) .getField(#queries) diff --git a/test/query_test.dart b/test/query_test.dart index cdc87f0..dcc5164 100644 --- a/test/query_test.dart +++ b/test/query_test.dart @@ -3,267 +3,267 @@ import 'package:test/test.dart'; import 'package:postgres/src/types.dart'; void main() { - group("Successful queries", () { - var connection = new PostgreSQLConnection("localhost", 5432, "dart_test", - username: "dart", password: "dart"); + group('Successful queries', () { + var connection = PostgreSQLConnection('localhost', 5432, 'dart_test', + username: 'dart', password: 'dart'); setUp(() async { - connection = new PostgreSQLConnection("localhost", 5432, "dart_test", - username: "dart", password: "dart"); + connection = PostgreSQLConnection('localhost', 5432, 'dart_test', + username: 'dart', password: 'dart'); await connection.open(); - await connection.execute("CREATE TEMPORARY TABLE t " - "(i int, s serial, bi bigint, " - "bs bigserial, bl boolean, si smallint, " - "t text, f real, d double precision, " - "dt date, ts timestamp, tsz timestamptz, j jsonb, u uuid)"); + await connection.execute('CREATE TEMPORARY TABLE t ' + '(i int, s serial, bi bigint, ' + 'bs bigserial, bl boolean, si smallint, ' + 't text, f real, d double precision, ' + 'dt date, ts timestamp, tsz timestamptz, j jsonb, u uuid)'); await connection.execute( - "CREATE TEMPORARY TABLE u (i1 int not null, i2 int not null);"); + 'CREATE TEMPORARY TABLE u (i1 int not null, i2 int not null);'); await connection - .execute("CREATE TEMPORARY TABLE n (i1 int, i2 int not null);"); + .execute('CREATE TEMPORARY TABLE n (i1 int, i2 int not null);'); }); tearDown(() async { await connection.close(); }); - test("UTF16 strings in value", () async { + test('UTF16 strings in value', () async { var result = await connection.query( - "INSERT INTO t (t) values " + 'INSERT INTO t (t) values ' "(${PostgreSQLFormat.id("t", type: PostgreSQLDataType.text)})" - "returning t", + 'returning t', substitutionValues: { - "t": "°∆", + 't': '°∆', }); - var expectedRow = ["°∆"]; + final expectedRow = ['°∆']; expect(result, [expectedRow]); - result = await connection.query("select t from t"); + result = await connection.query('select t from t'); expect(result, [expectedRow]); }); - test("UTF16 strings in query", () async { + test('UTF16 strings in query', () async { var result = await connection.query("INSERT INTO t (t) values ('°∆') RETURNING t"); - var expectedRow = ["°∆"]; + final expectedRow = ['°∆']; expect(result, [expectedRow]); - result = await connection.query("select t from t"); + result = await connection.query('select t from t'); expect(result, [expectedRow]); }); - test("UTF16 strings in value with escape characters", () async { + test('UTF16 strings in value with escape characters', () async { await connection.execute( - "INSERT INTO t (t) values " - "(${PostgreSQLFormat.id("t", type: PostgreSQLDataType.text)})", + 'INSERT INTO t (t) values ' + '(${PostgreSQLFormat.id('t', type: PostgreSQLDataType.text)})', substitutionValues: { - "t": "'©™®'", + 't': "'©™®'", }); - var expectedRow = ["'©™®'"]; + final expectedRow = ["'©™®'"]; - var result = await connection.query("select t from t"); + final result = await connection.query('select t from t'); expect(result, [expectedRow]); }); - test("UTF16 strings in value with backslash", () async { + test('UTF16 strings in value with backslash', () async { await connection.execute( - "INSERT INTO t (t) values " - "(${PostgreSQLFormat.id("t", type: PostgreSQLDataType.text)})", + 'INSERT INTO t (t) values ' + '(${PostgreSQLFormat.id('t', type: PostgreSQLDataType.text)})', substitutionValues: { - "t": "°\\'©™®'", + 't': "°\\'©™®'", }); - var expectedRow = ["°\\'©™®'"]; + final expectedRow = ["°\\'©™®'"]; - var result = await connection.query("select t from t"); + final result = await connection.query('select t from t'); expect(result, [expectedRow]); }); - test("UTF16 strings in query with escape characters", () async { + test('UTF16 strings in query with escape characters', () async { await connection.execute("INSERT INTO t (t) values ('°''©™®''')"); - var expectedRow = ["°'©™®'"]; + final expectedRow = ["°'©™®'"]; - var result = await connection.query("select t from t"); + final result = await connection.query('select t from t'); expect(result, [expectedRow]); }); - test("Really long raw substitution value", () async { - var result = await connection.query( + test('Really long raw substitution value', () async { + final result = await connection.query( "INSERT INTO t (t) VALUES (${PostgreSQLFormat.id("t", type: PostgreSQLDataType.text)}) returning t;", - substitutionValues: {"t": lorumIpsum}); + substitutionValues: {'t': lorumIpsum}); expect(result, [ [lorumIpsum] ]); }); - test("Really long SQL string in execute", () async { - var result = await connection + test('Really long SQL string in execute', () async { + final result = await connection .execute("INSERT INTO t (t) VALUES ('$lorumIpsum') returning t;"); expect(result, 1); }); - test("Query without specifying types", () async { + test('Query without specifying types', () async { var result = await connection.query( - "INSERT INTO t (i, bi, bl, si, t, f, d, dt, ts, tsz, j, u) values " - "(${PostgreSQLFormat.id("i")}," - "${PostgreSQLFormat.id("bi")}," - "${PostgreSQLFormat.id("bl")}," - "${PostgreSQLFormat.id("si")}," - "${PostgreSQLFormat.id("t")}," - "${PostgreSQLFormat.id("f")}," - "${PostgreSQLFormat.id("d")}," - "${PostgreSQLFormat.id("dt")}," - "${PostgreSQLFormat.id("ts")}," - "${PostgreSQLFormat.id("tsz")}," - "${PostgreSQLFormat.id("j")}," - "${PostgreSQLFormat.id("u")}" - ") returning i,s, bi, bs, bl, si, t, f, d, dt, ts, tsz, j, u", + 'INSERT INTO t (i, bi, bl, si, t, f, d, dt, ts, tsz, j, u) values ' + '(${PostgreSQLFormat.id('i')},' + '${PostgreSQLFormat.id('bi')},' + '${PostgreSQLFormat.id('bl')},' + '${PostgreSQLFormat.id('si')},' + '${PostgreSQLFormat.id('t')},' + '${PostgreSQLFormat.id('f')},' + '${PostgreSQLFormat.id('d')},' + '${PostgreSQLFormat.id('dt')},' + '${PostgreSQLFormat.id('ts')},' + '${PostgreSQLFormat.id('tsz')},' + '${PostgreSQLFormat.id('j')},' + '${PostgreSQLFormat.id('u')}' + ') returning i,s, bi, bs, bl, si, t, f, d, dt, ts, tsz, j, u', substitutionValues: { - "i": 1, - "bi": 2, - "bl": true, - "si": 3, - "t": "foobar", - "f": 5.0, - "d": 6.0, - "dt": new DateTime.utc(2000), - "ts": new DateTime.utc(2000, 2), - "tsz": new DateTime.utc(2000, 3), - "j": {"a": "b"}, - "u": "01234567-89ab-cdef-0123-0123456789ab" + 'i': 1, + 'bi': 2, + 'bl': true, + 'si': 3, + 't': 'foobar', + 'f': 5.0, + 'd': 6.0, + 'dt': DateTime.utc(2000), + 'ts': DateTime.utc(2000, 2), + 'tsz': DateTime.utc(2000, 3), + 'j': {'a': 'b'}, + 'u': '01234567-89ab-cdef-0123-0123456789ab' }); - var expectedRow = [ + final expectedRow = [ 1, 1, 2, 1, true, 3, - "foobar", + 'foobar', 5.0, 6.0, - new DateTime.utc(2000), - new DateTime.utc(2000, 2), - new DateTime.utc(2000, 3), - {"a": "b"}, - "01234567-89ab-cdef-0123-0123456789ab" + DateTime.utc(2000), + DateTime.utc(2000, 2), + DateTime.utc(2000, 3), + {'a': 'b'}, + '01234567-89ab-cdef-0123-0123456789ab' ]; expect(result, [expectedRow]); result = await connection.query( - "select i,s, bi, bs, bl, si, t, f, d, dt, ts, tsz, j, u from t"); + 'select i,s, bi, bs, bl, si, t, f, d, dt, ts, tsz, j, u from t'); expect(result, [expectedRow]); }); - test("Query by specifying all types", () async { + test('Query by specifying all types', () async { var result = await connection.query( - "INSERT INTO t (i, bi, bl, si, t, f, d, dt, ts, tsz, j, u) values " - "(${PostgreSQLFormat.id("i", type: PostgreSQLDataType.integer)}," - "${PostgreSQLFormat.id("bi", type: PostgreSQLDataType.bigInteger)}," - "${PostgreSQLFormat.id("bl", type: PostgreSQLDataType.boolean)}," - "${PostgreSQLFormat.id("si", type: PostgreSQLDataType.smallInteger)}," - "${PostgreSQLFormat.id("t", type: PostgreSQLDataType.text)}," - "${PostgreSQLFormat.id("f", type: PostgreSQLDataType.real)}," - "${PostgreSQLFormat.id("d", type: PostgreSQLDataType.double)}," - "${PostgreSQLFormat.id("dt", type: PostgreSQLDataType.date)}," - "${PostgreSQLFormat.id("ts", type: PostgreSQLDataType.timestampWithoutTimezone)}," - "${PostgreSQLFormat.id("tsz", type: PostgreSQLDataType.timestampWithTimezone)}," - "${PostgreSQLFormat.id("j", type: PostgreSQLDataType.json)}," - "${PostgreSQLFormat.id("u", type: PostgreSQLDataType.uuid)})" - " returning i,s, bi, bs, bl, si, t, f, d, dt, ts, tsz, j, u", + 'INSERT INTO t (i, bi, bl, si, t, f, d, dt, ts, tsz, j, u) values ' + '(${PostgreSQLFormat.id('i', type: PostgreSQLDataType.integer)},' + '${PostgreSQLFormat.id('bi', type: PostgreSQLDataType.bigInteger)},' + '${PostgreSQLFormat.id('bl', type: PostgreSQLDataType.boolean)},' + '${PostgreSQLFormat.id('si', type: PostgreSQLDataType.smallInteger)},' + '${PostgreSQLFormat.id('t', type: PostgreSQLDataType.text)},' + '${PostgreSQLFormat.id('f', type: PostgreSQLDataType.real)},' + '${PostgreSQLFormat.id('d', type: PostgreSQLDataType.double)},' + '${PostgreSQLFormat.id('dt', type: PostgreSQLDataType.date)},' + '${PostgreSQLFormat.id('ts', type: PostgreSQLDataType.timestampWithoutTimezone)},' + '${PostgreSQLFormat.id('tsz', type: PostgreSQLDataType.timestampWithTimezone)},' + '${PostgreSQLFormat.id('j', type: PostgreSQLDataType.json)},' + '${PostgreSQLFormat.id('u', type: PostgreSQLDataType.uuid)})' + ' returning i,s, bi, bs, bl, si, t, f, d, dt, ts, tsz, j, u', substitutionValues: { - "i": 1, - "bi": 2, - "bl": true, - "si": 3, - "t": "foobar", - "f": 5.0, - "d": 6.0, - "dt": new DateTime.utc(2000), - "ts": new DateTime.utc(2000, 2), - "tsz": new DateTime.utc(2000, 3), - "j": {"key": "value"}, - "u": "01234567-89ab-cdef-0123-0123456789ab" + 'i': 1, + 'bi': 2, + 'bl': true, + 'si': 3, + 't': 'foobar', + 'f': 5.0, + 'd': 6.0, + 'dt': DateTime.utc(2000), + 'ts': DateTime.utc(2000, 2), + 'tsz': DateTime.utc(2000, 3), + 'j': {'key': 'value'}, + 'u': '01234567-89ab-cdef-0123-0123456789ab' }); - var expectedRow = [ + final expectedRow = [ 1, 1, 2, 1, true, 3, - "foobar", + 'foobar', 5.0, 6.0, - new DateTime.utc(2000), - new DateTime.utc(2000, 2), - new DateTime.utc(2000, 3), - {"key": "value"}, - "01234567-89ab-cdef-0123-0123456789ab" + DateTime.utc(2000), + DateTime.utc(2000, 2), + DateTime.utc(2000, 3), + {'key': 'value'}, + '01234567-89ab-cdef-0123-0123456789ab' ]; expect(result, [expectedRow]); result = await connection.query( - "select i,s, bi, bs, bl, si, t, f, d, dt, ts, tsz, j, u from t"); + 'select i,s, bi, bs, bl, si, t, f, d, dt, ts, tsz, j, u from t'); expect(result, [expectedRow]); }); - test("Query by specifying some types", () async { + test('Query by specifying some types', () async { var result = await connection.query( - "INSERT INTO t (i, bi, bl, si, t, f, d, dt, ts, tsz) values " - "(${PostgreSQLFormat.id("i")}," - "${PostgreSQLFormat.id("bi", type: PostgreSQLDataType.bigInteger)}," - "${PostgreSQLFormat.id("bl")}," - "${PostgreSQLFormat.id("si", type: PostgreSQLDataType.smallInteger)}," - "${PostgreSQLFormat.id("t")}," - "${PostgreSQLFormat.id("f", type: PostgreSQLDataType.real)}," - "${PostgreSQLFormat.id("d")}," - "${PostgreSQLFormat.id("dt", type: PostgreSQLDataType.date)}," - "${PostgreSQLFormat.id("ts")}," - "${PostgreSQLFormat.id("tsz", type: PostgreSQLDataType.timestampWithTimezone)}) returning i,s, bi, bs, bl, si, t, f, d, dt, ts, tsz", + 'INSERT INTO t (i, bi, bl, si, t, f, d, dt, ts, tsz) values ' + '(${PostgreSQLFormat.id('i')},' + '${PostgreSQLFormat.id('bi', type: PostgreSQLDataType.bigInteger)},' + '${PostgreSQLFormat.id('bl')},' + '${PostgreSQLFormat.id('si', type: PostgreSQLDataType.smallInteger)},' + '${PostgreSQLFormat.id('t')},' + '${PostgreSQLFormat.id('f', type: PostgreSQLDataType.real)},' + '${PostgreSQLFormat.id('d')},' + '${PostgreSQLFormat.id('dt', type: PostgreSQLDataType.date)},' + '${PostgreSQLFormat.id('ts')},' + '${PostgreSQLFormat.id('tsz', type: PostgreSQLDataType.timestampWithTimezone)}) returning i,s, bi, bs, bl, si, t, f, d, dt, ts, tsz', substitutionValues: { - "i": 1, - "bi": 2, - "bl": true, - "si": 3, - "t": "foobar", - "f": 5.0, - "d": 6.0, - "dt": new DateTime.utc(2000), - "ts": new DateTime.utc(2000, 2), - "tsz": new DateTime.utc(2000, 3), + 'i': 1, + 'bi': 2, + 'bl': true, + 'si': 3, + 't': 'foobar', + 'f': 5.0, + 'd': 6.0, + 'dt': DateTime.utc(2000), + 'ts': DateTime.utc(2000, 2), + 'tsz': DateTime.utc(2000, 3), }); - var expectedRow = [ + final expectedRow = [ 1, 1, 2, 1, true, 3, - "foobar", + 'foobar', 5.0, 6.0, - new DateTime.utc(2000), - new DateTime.utc(2000, 2), - new DateTime.utc(2000, 3) + DateTime.utc(2000), + DateTime.utc(2000, 2), + DateTime.utc(2000, 3) ]; expect(result, [expectedRow]); result = await connection - .query("select i,s, bi, bs, bl, si, t, f, d, dt, ts, tsz from t"); + .query('select i,s, bi, bs, bl, si, t, f, d, dt, ts, tsz from t'); expect(result, [expectedRow]); }); - test("Can supply null for values (binary)", () async { - var results = await connection.query( - "INSERT INTO n (i1, i2) values (@i1:int4, @i2:int4) returning i1, i2", + test('Can supply null for values (binary)', () async { + final results = await connection.query( + 'INSERT INTO n (i1, i2) values (@i1:int4, @i2:int4) returning i1, i2', substitutionValues: { - "i1": null, - "i2": 1, + 'i1': null, + 'i2': 1, }); expect(results, [ @@ -271,12 +271,12 @@ void main() { ]); }); - test("Can supply null for values (text)", () async { - var results = await connection.query( - "INSERT INTO n (i1, i2) values (@i1, @i2:int4) returning i1, i2", + test('Can supply null for values (text)', () async { + final results = await connection.query( + 'INSERT INTO n (i1, i2) values (@i1, @i2:int4) returning i1, i2', substitutionValues: { - "i1": null, - "i2": 1, + 'i1': null, + 'i2': 1, }); expect(results, [ @@ -284,13 +284,13 @@ void main() { ]); }); - test("Overspecifying parameters does not impact query (text)", () async { - var results = await connection.query( - "INSERT INTO u (i1, i2) values (@i1, @i2) returning i1, i2", + test('Overspecifying parameters does not impact query (text)', () async { + final results = await connection.query( + 'INSERT INTO u (i1, i2) values (@i1, @i2) returning i1, i2', substitutionValues: { - "i1": 0, - "i2": 1, - "i3": 0, + 'i1': 0, + 'i2': 1, + 'i3': 0, }); expect(results, [ @@ -298,13 +298,13 @@ void main() { ]); }); - test("Overspecifying parameters does not impact query (binary)", () async { - var results = await connection.query( - "INSERT INTO u (i1, i2) values (@i1:int4, @i2:int4) returning i1, i2", + test('Overspecifying parameters does not impact query (binary)', () async { + final results = await connection.query( + 'INSERT INTO u (i1, i2) values (@i1:int4, @i2:int4) returning i1, i2', substitutionValues: { - "i1": 0, - "i2": 1, - "i3": 0, + 'i1': 0, + 'i2': 1, + 'i3': 0, }); expect(results, [ @@ -312,10 +312,10 @@ void main() { ]); }); - test("Can cast text to int on db server", () async { - var results = await connection.query( - "INSERT INTO u (i1, i2) VALUES (@i1::int4, @i2::int4) RETURNING i1, i2", - substitutionValues: {"i1": "0", "i2": "1"}); + test('Can cast text to int on db server', () async { + final results = await connection.query( + 'INSERT INTO u (i1, i2) VALUES (@i1::int4, @i2::int4) RETURNING i1, i2', + substitutionValues: {'i1': '0', 'i2': '1'}); expect(results, [ [0, 1] @@ -323,16 +323,16 @@ void main() { }); }); - group("Unsuccesful queries", () { - var connection = new PostgreSQLConnection("localhost", 5432, "dart_test", - username: "dart", password: "dart"); + group('Unsuccesful queries', () { + var connection = PostgreSQLConnection('localhost', 5432, 'dart_test', + username: 'dart', password: 'dart'); setUp(() async { - connection = new PostgreSQLConnection("localhost", 5432, "dart_test", - username: "dart", password: "dart"); + connection = PostgreSQLConnection('localhost', 5432, 'dart_test', + username: 'dart', password: 'dart'); await connection.open(); await connection.execute( - "CREATE TEMPORARY TABLE t (i1 int not null, i2 int not null)"); + 'CREATE TEMPORARY TABLE t (i1 int not null, i2 int not null)'); }); tearDown(() async { @@ -340,65 +340,65 @@ void main() { }); test( - "A query that fails on the server will report back an exception through the query method", + 'A query that fails on the server will report back an exception through the query method', () async { try { - await connection.query("INSERT INTO t (i1) values (@i1)", - substitutionValues: {"i1": 0}); + await connection.query('INSERT INTO t (i1) values (@i1)', + substitutionValues: {'i1': 0}); expect(true, false); } on PostgreSQLException catch (e) { expect(e.severity, PostgreSQLSeverity.error); - expect(e.message, contains("null value in column \"i2\"")); + expect(e.message, contains('null value in column "i2"')); } }); test( - "Not enough parameters to support format string throws error prior to sending to server", + 'Not enough parameters to support format string throws error prior to sending to server', () async { try { await connection - .query("INSERT INTO t (i1) values (@i1)", substitutionValues: {}); + .query('INSERT INTO t (i1) values (@i1)', substitutionValues: {}); expect(true, false); } on FormatException catch (e) { expect(e.message, - contains("Format string specified identifier with name i1")); + contains('Format string specified identifier with name i1')); } try { - await connection.query("INSERT INTO t (i1) values (@i1)"); + await connection.query('INSERT INTO t (i1) values (@i1)'); expect(true, false); } on FormatException catch (e) { expect(e.message, - contains("Format string specified identifier with name i1")); + contains('Format string specified identifier with name i1')); } }); - test("Wrong type for parameter in substitution values fails", () async { + test('Wrong type for parameter in substitution values fails', () async { try { await connection.query( - "INSERT INTO t (i1, i2) values (@i1:int4, @i2:int4)", - substitutionValues: {"i1": "1", "i2": 1}); + 'INSERT INTO t (i1, i2) values (@i1:int4, @i2:int4)', + substitutionValues: {'i1': '1', 'i2': 1}); expect(true, false); } on FormatException catch (e) { - expect(e.toString(), contains("Invalid type for parameter value")); + expect(e.toString(), contains('Invalid type for parameter value')); } }); - test("Invalid type code", () async { + test('Invalid type code', () async { try { await connection.query( - "INSERT INTO t (i1, i2) values (@i1:qwerty, @i2:int4)", - substitutionValues: {"i1": "1", "i2": 1}); + 'INSERT INTO t (i1, i2) values (@i1:qwerty, @i2:int4)', + substitutionValues: {'i1': '1', 'i2': 1}); expect(true, false); } on FormatException catch (e) { - expect(e.toString(), contains("Invalid type code")); + expect(e.toString(), contains('Invalid type code')); expect(e.toString(), contains("'@i1:qwerty")); } }); }); } -const String lorumIpsum = """Lorem +const String lorumIpsum = '''Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque in accumsan felis. Nunc semper velit purus, a pellentesque mauris aliquam ut. Sed laoreet iaculis nunc sit amet dignissim. Aenean venenatis sollicitudin @@ -511,4 +511,4 @@ const String lorumIpsum = """Lorem Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Proin malesuada orci sit amet neque dapibus bibendum. In lobortis imperdiet condimentum. Nullam est nisi, efficitur ac consectetur - eu, efficitur a libero. In nullam."""; + eu, efficitur a libero. In nullam.'''; diff --git a/test/timeout_test.dart b/test/timeout_test.dart index 1d94f9e..21a5f7f 100644 --- a/test/timeout_test.dart +++ b/test/timeout_test.dart @@ -1,15 +1,17 @@ -import 'package:postgres/postgres.dart'; -import 'package:test/test.dart'; import 'dart:async'; +import 'package:test/test.dart'; + +import 'package:postgres/postgres.dart'; + void main() { PostgreSQLConnection conn; setUp(() async { - conn = new PostgreSQLConnection("localhost", 5432, "dart_test", - username: "dart", password: "dart"); + conn = PostgreSQLConnection('localhost', 5432, 'dart_test', + username: 'dart', password: 'dart'); await conn.open(); - await conn.execute("CREATE TEMPORARY TABLE t (id INT UNIQUE)"); + await conn.execute('CREATE TEMPORARY TABLE t (id INT UNIQUE)'); }); tearDown(() async { @@ -17,73 +19,73 @@ void main() { }); test( - "Timeout fires on query while in queue does not execute query, query throws exception", + 'Timeout fires on query while in queue does not execute query, query throws exception', () async { //ignore: unawaited_futures - final f = conn.query("SELECT pg_sleep(2)"); + final f = conn.query('SELECT pg_sleep(2)'); try { - await conn.query("SELECT 1", timeoutInSeconds: 1); + await conn.query('SELECT 1', timeoutInSeconds: 1); fail('unreachable'); } on TimeoutException {} expect(f, completes); }); - test("Timeout fires during transaction rolls ack transaction", () async { + test('Timeout fires during transaction rolls ack transaction', () async { try { await conn.transaction((ctx) async { - await ctx.query("INSERT INTO t (id) VALUES (1)"); - await ctx.query("SELECT pg_sleep(2)", timeoutInSeconds: 1); + await ctx.query('INSERT INTO t (id) VALUES (1)'); + await ctx.query('SELECT pg_sleep(2)', timeoutInSeconds: 1); }); fail('unreachable'); } on TimeoutException {} - expect(await conn.query("SELECT * from t"), hasLength(0)); + expect(await conn.query('SELECT * from t'), hasLength(0)); }); test( - "Query on parent context for transaction completes (with error) after timeout", + 'Query on parent context for transaction completes (with error) after timeout', () async { try { await conn.transaction((ctx) async { - await conn.query("SELECT 1", timeoutInSeconds: 1); - await ctx.query("INSERT INTO t (id) VALUES (1)"); + await conn.query('SELECT 1', timeoutInSeconds: 1); + await ctx.query('INSERT INTO t (id) VALUES (1)'); }); fail('unreachable'); } on TimeoutException {} - expect(await conn.query("SELECT * from t"), hasLength(0)); + expect(await conn.query('SELECT * from t'), hasLength(0)); }); test( - "If query is already on the wire and times out, safely throws timeoutexception and nothing else", + 'If query is already on the wire and times out, safely throws timeoutexception and nothing else', () async { try { - await conn.query("SELECT pg_sleep(2)", timeoutInSeconds: 1); + await conn.query('SELECT pg_sleep(2)', timeoutInSeconds: 1); fail('unreachable'); } on TimeoutException {} }); - test("Query times out, next query in the queue runs", () async { + test('Query times out, next query in the queue runs', () async { //ignore: unawaited_futures conn - .query("SELECT pg_sleep(2)", timeoutInSeconds: 1) + .query('SELECT pg_sleep(2)', timeoutInSeconds: 1) .catchError((_) => null); - expect(await conn.query("SELECT 1"), [ + expect(await conn.query('SELECT 1'), [ [1] ]); }); - test("Query that succeeds does not timeout", () async { - await conn.query("SELECT 1", timeoutInSeconds: 1); - expect(new Future.delayed(new Duration(seconds: 2)), completes); + test('Query that succeeds does not timeout', () async { + await conn.query('SELECT 1', timeoutInSeconds: 1); + expect(Future.delayed(Duration(seconds: 2)), completes); }); - test("Query that fails does not timeout", () async { + test('Query that fails does not timeout', () async { await conn .query("INSERT INTO t (id) VALUES ('foo')", timeoutInSeconds: 1) .catchError((_) => null); - expect(new Future.delayed(new Duration(seconds: 2)), completes); + expect(Future.delayed(Duration(seconds: 2)), completes); }); } diff --git a/test/transaction_test.dart b/test/transaction_test.dart index 8cc8785..a409756 100644 --- a/test/transaction_test.dart +++ b/test/transaction_test.dart @@ -1,65 +1,67 @@ // ignore_for_file: unawaited_futures +import 'dart:async'; -import 'package:postgres/postgres.dart'; import 'package:test/test.dart'; -import 'dart:async'; + +import 'package:postgres/postgres.dart'; void main() { - group("Transaction behavior", () { - PostgreSQLConnection conn = null; + group('Transaction behavior', () { + PostgreSQLConnection conn; setUp(() async { - conn = new PostgreSQLConnection("localhost", 5432, "dart_test", - username: "dart", password: "dart"); + conn = PostgreSQLConnection('localhost', 5432, 'dart_test', + username: 'dart', password: 'dart'); await conn.open(); - await conn.execute("CREATE TEMPORARY TABLE t (id INT UNIQUE)"); + await conn.execute('CREATE TEMPORARY TABLE t (id INT UNIQUE)'); }); tearDown(() async { await conn?.close(); }); - test("Rows are Lists of column values", () async { - await conn.execute("INSERT INTO t (id) VALUES (1)"); + test('Rows are Lists of column values', () async { + await conn.execute('INSERT INTO t (id) VALUES (1)'); - final List> outValue = await conn.transaction((ctx) async { + final outValue = await conn.transaction((ctx) async { return await ctx.query('SELECT * FROM t WHERE id = @id LIMIT 1', substitutionValues: {'id': 1}); - }); + }) as List; expect(outValue.length, 1); expect(outValue.first is List, true); - expect(outValue.first.length, 1); - expect(outValue.first.first, 1); + final firstItem = outValue.first as List; + expect(firstItem.length, 1); + expect(firstItem.first, 1); }); - test("Send successful transaction succeeds, returns returned value", + test('Send successful transaction succeeds, returns returned value', () async { - var outResult = await conn.transaction((c) async { - await c.query("INSERT INTO t (id) VALUES (1)"); + final outResult = await conn.transaction((c) async { + await c.query('INSERT INTO t (id) VALUES (1)'); - return await c.query("SELECT id FROM t"); + return await c.query('SELECT id FROM t'); }); expect(outResult, [ [1] ]); - var result = await conn.query("SELECT id FROM t"); + final result = await conn.query('SELECT id FROM t'); expect(result, [ [1] ]); }); - test("Query during transaction must wait until transaction is finished", + test('Query during transaction must wait until transaction is finished', () async { - var orderEnsurer = []; - var nextCompleter = new Completer.sync(); - var outResult = conn.transaction((c) async { + final orderEnsurer = []; + final nextCompleter = Completer.sync(); + final outResult = conn.transaction((c) async { orderEnsurer.add(1); - await c.query("INSERT INTO t (id) VALUES (1)"); + await c.query('INSERT INTO t (id) VALUES (1)'); orderEnsurer.add(2); nextCompleter.complete(); - var result = await c.query("SELECT id FROM t"); + final result = await c.query('SELECT id FROM t'); orderEnsurer.add(3); return result; @@ -67,12 +69,12 @@ void main() { await nextCompleter.future; orderEnsurer.add(11); - await conn.query("INSERT INTO t (id) VALUES (2)"); + await conn.query('INSERT INTO t (id) VALUES (2)'); orderEnsurer.add(12); - var laterResults = await conn.query("SELECT id FROM t"); + final laterResults = await conn.query('SELECT id FROM t'); orderEnsurer.add(13); - var firstResult = await outResult; + final firstResult = await outResult; expect(orderEnsurer, [1, 2, 11, 3, 12, 13]); expect(firstResult, [ @@ -84,32 +86,32 @@ void main() { ]); }); - test("Make sure two simultaneous transactions cannot be interwoven", + test('Make sure two simultaneous transactions cannot be interwoven', () async { - var orderEnsurer = []; + final orderEnsurer = []; - var firstTransactionFuture = conn.transaction((c) async { + final firstTransactionFuture = conn.transaction((c) async { orderEnsurer.add(11); - await c.query("INSERT INTO t (id) VALUES (1)"); + await c.query('INSERT INTO t (id) VALUES (1)'); orderEnsurer.add(12); - var result = await c.query("SELECT id FROM t"); + final result = await c.query('SELECT id FROM t'); orderEnsurer.add(13); return result; }); - var secondTransactionFuture = conn.transaction((c) async { + final secondTransactionFuture = conn.transaction((c) async { orderEnsurer.add(21); - await c.query("INSERT INTO t (id) VALUES (2)"); + await c.query('INSERT INTO t (id) VALUES (2)'); orderEnsurer.add(22); - var result = await c.query("SELECT id FROM t"); + final result = await c.query('SELECT id FROM t'); orderEnsurer.add(23); return result; }); - var firstResults = await firstTransactionFuture; - var secondResults = await secondTransactionFuture; + final firstResults = await firstTransactionFuture; + final secondResults = await secondTransactionFuture; expect(orderEnsurer, [11, 12, 13, 21, 22, 23]); @@ -122,37 +124,37 @@ void main() { ]); }); - test("May intentionally rollback transaction", () async { - var reached = false; + test('May intentionally rollback transaction', () async { + bool reached = false; await conn.transaction((c) async { - await c.query("INSERT INTO t (id) VALUES (1)"); + await c.query('INSERT INTO t (id) VALUES (1)'); c.cancelTransaction(); reached = true; - await c.query("INSERT INTO t (id) VALUES (2)"); + await c.query('INSERT INTO t (id) VALUES (2)'); }); expect(reached, false); - var result = await conn.query("SELECT id FROM t"); + final result = await conn.query('SELECT id FROM t'); expect(result, []); }); - test("Intentional rollback on non-transaction has no impact", () async { + test('Intentional rollback on non-transaction has no impact', () async { conn.cancelTransaction(); - var result = await conn.query("SELECT id FROM t"); + final result = await conn.query('SELECT id FROM t'); expect(result, []); }); - test("Intentional rollback from outside of a transaction has no impact", + test('Intentional rollback from outside of a transaction has no impact', () async { - var orderEnsurer = []; - var nextCompleter = new Completer.sync(); - var outResult = conn.transaction((c) async { + final orderEnsurer = []; + final nextCompleter = Completer.sync(); + final outResult = conn.transaction((c) async { orderEnsurer.add(1); - await c.query("INSERT INTO t (id) VALUES (1)"); + await c.query('INSERT INTO t (id) VALUES (1)'); orderEnsurer.add(2); nextCompleter.complete(); - var result = await c.query("SELECT id FROM t"); + final result = await c.query('SELECT id FROM t'); orderEnsurer.add(3); return result; @@ -162,7 +164,7 @@ void main() { conn.cancelTransaction(); orderEnsurer.add(11); - var results = await outResult; + final results = await outResult; expect(orderEnsurer, [1, 2, 11, 3]); expect(results, [ @@ -170,15 +172,15 @@ void main() { ]); }); - test("A transaction does not preempt pending queries", () async { + test('A transaction does not preempt pending queries', () async { // Add a few insert queries but don't await, then do a transaction that does a fetch, // make sure that transaction contains all of the elements. - conn.execute("INSERT INTO t (id) VALUES (1)"); - conn.execute("INSERT INTO t (id) VALUES (2)"); - conn.execute("INSERT INTO t (id) VALUES (3)"); + conn.execute('INSERT INTO t (id) VALUES (1)'); + conn.execute('INSERT INTO t (id) VALUES (2)'); + conn.execute('INSERT INTO t (id) VALUES (3)'); - var results = await conn.transaction((ctx) async { - return await ctx.query("SELECT id FROM t"); + final results = await conn.transaction((ctx) async { + return await ctx.query('SELECT id FROM t'); }); expect(results, [ [1], @@ -189,12 +191,12 @@ void main() { test("A transaction doesn't have to await on queries", () async { conn.transaction((ctx) async { - ctx.query("INSERT INTO t (id) VALUES (1)"); - ctx.query("INSERT INTO t (id) VALUES (2)"); - ctx.query("INSERT INTO t (id) VALUES (3)"); + ctx.query('INSERT INTO t (id) VALUES (1)'); + ctx.query('INSERT INTO t (id) VALUES (2)'); + ctx.query('INSERT INTO t (id) VALUES (3)'); }); - var total = await conn.query("SELECT id FROM t"); + final total = await conn.query('SELECT id FROM t'); expect(total, [ [1], [2], @@ -205,90 +207,86 @@ void main() { test( "A transaction doesn't have to await on queries, when the last query fails, it still emits an error from the transaction", () async { - var transactionError; + dynamic transactionError; await conn.transaction((ctx) async { - ctx.query("INSERT INTO t (id) VALUES (1)"); - ctx.query("INSERT INTO t (id) VALUES (2)"); + ctx.query('INSERT INTO t (id) VALUES (1)'); + ctx.query('INSERT INTO t (id) VALUES (2)'); ctx.query("INSERT INTO t (id) VALUES ('foo')").catchError((e) {}); }).catchError((e) => transactionError = e); expect(transactionError, isNotNull); - var total = await conn.query("SELECT id FROM t"); + final total = await conn.query('SELECT id FROM t'); expect(total, []); }); test( "A transaction doesn't have to await on queries, when the non-last query fails, it still emits an error from the transaction", () async { - var failingQueryError; - var pendingQueryError; - var transactionError; + dynamic failingQueryError; + dynamic pendingQueryError; + dynamic transactionError; await conn.transaction((ctx) async { - ctx.query("INSERT INTO t (id) VALUES (1)"); + ctx.query('INSERT INTO t (id) VALUES (1)'); ctx.query("INSERT INTO t (id) VALUES ('foo')").catchError((e) { failingQueryError = e; }); - ctx.query("INSERT INTO t (id) VALUES (2)").catchError((e) { + ctx.query('INSERT INTO t (id) VALUES (2)').catchError((e) { pendingQueryError = e; }); }).catchError((e) => transactionError = e); expect(transactionError, isNotNull); - expect(failingQueryError.toString(), contains("invalid input")); + expect(failingQueryError.toString(), contains('invalid input')); expect( - pendingQueryError.toString(), contains("failed prior to execution")); - var total = await conn.query("SELECT id FROM t"); + pendingQueryError.toString(), contains('failed prior to execution')); + final total = await conn.query('SELECT id FROM t'); expect(total, []); }); test( - "A transaction with a rollback and non-await queries rolls back transaction", + 'A transaction with a rollback and non-await queries rolls back transaction', () async { - var errs = []; + final errs = []; await conn.transaction((ctx) async { - ctx.query("INSERT INTO t (id) VALUES (1)").catchError((e) { - errs.add(e); - }); - ctx.query("INSERT INTO t (id) VALUES (2)").catchError((e) { - errs.add(e); - }); + ctx.query('INSERT INTO t (id) VALUES (1)').catchError(errs.add); + ctx.query('INSERT INTO t (id) VALUES (2)').catchError(errs.add); ctx.cancelTransaction(); - ctx.query("INSERT INTO t (id) VALUES (3)").catchError((e) {}); + ctx.query('INSERT INTO t (id) VALUES (3)').catchError((e) {}); }); - var total = await conn.query("SELECT id FROM t"); + final total = await conn.query('SELECT id FROM t'); expect(total, []); expect(errs.length, 2); }); test( - "A transaction that mixes awaiting and non-awaiting queries fails gracefully when an awaited query fails", + 'A transaction that mixes awaiting and non-awaiting queries fails gracefully when an awaited query fails', () async { - var transactionError; + dynamic transactionError; await conn.transaction((ctx) async { - ctx.query("INSERT INTO t (id) VALUES (1)"); + ctx.query('INSERT INTO t (id) VALUES (1)'); await ctx.query("INSERT INTO t (id) VALUES ('foo')").catchError((_) {}); - ctx.query("INSERT INTO t (id) VALUES (2)").catchError((_) {}); + ctx.query('INSERT INTO t (id) VALUES (2)').catchError((_) {}); }).catchError((e) => transactionError = e); expect(transactionError, isNotNull); - var total = await conn.query("SELECT id FROM t"); + final total = await conn.query('SELECT id FROM t'); expect(total, []); }); test( - "A transaction that mixes awaiting and non-awaiting queries fails gracefully when an unawaited query fails", + 'A transaction that mixes awaiting and non-awaiting queries fails gracefully when an unawaited query fails', () async { - var transactionError; + dynamic transactionError; await conn.transaction((ctx) async { - await ctx.query("INSERT INTO t (id) VALUES (1)"); + await ctx.query('INSERT INTO t (id) VALUES (1)'); ctx.query("INSERT INTO t (id) VALUES ('foo')").catchError((_) {}); - await ctx.query("INSERT INTO t (id) VALUES (2)").catchError((_) {}); + await ctx.query('INSERT INTO t (id) VALUES (2)').catchError((_) {}); }).catchError((e) => transactionError = e); expect(transactionError, isNotNull); - var total = await conn.query("SELECT id FROM t"); + final total = await conn.query('SELECT id FROM t'); expect(total, []); }); }); @@ -296,76 +294,76 @@ void main() { // A transaction can fail for three reasons: query error, exception in code, or a rollback. // After a transaction fails, the changes must be rolled back, it should continue with pending queries, pending transactions, later queries, later transactions - group("Transaction:Query recovery", () { - PostgreSQLConnection conn = null; + group('Transaction:Query recovery', () { + PostgreSQLConnection conn; setUp(() async { - conn = new PostgreSQLConnection("localhost", 5432, "dart_test", - username: "dart", password: "dart"); + conn = PostgreSQLConnection('localhost', 5432, 'dart_test', + username: 'dart', password: 'dart'); await conn.open(); - await conn.execute("CREATE TEMPORARY TABLE t (id INT UNIQUE)"); + await conn.execute('CREATE TEMPORARY TABLE t (id INT UNIQUE)'); }); tearDown(() async { await conn?.close(); }); - test("Is rolled back/executes later query", () async { + test('Is rolled back/executes later query', () async { try { await conn.transaction((c) async { - await c.query("INSERT INTO t (id) VALUES (1)"); - var oneRow = await c.query("SELECT id FROM t"); + await c.query('INSERT INTO t (id) VALUES (1)'); + final oneRow = await c.query('SELECT id FROM t'); expect(oneRow, [ [1] ]); // This will error - await c.query("INSERT INTO t (id) VALUES (1)"); + await c.query('INSERT INTO t (id) VALUES (1)'); }); expect(true, false); } on PostgreSQLException catch (e) { - expect(e.message, contains("unique constraint")); + expect(e.message, contains('unique constraint')); } - var noRows = await conn.query("SELECT id FROM t"); + final noRows = await conn.query('SELECT id FROM t'); expect(noRows, []); }); - test("Executes pending query", () async { - var orderEnsurer = []; + test('Executes pending query', () async { + final orderEnsurer = []; conn.transaction((c) async { orderEnsurer.add(1); - await c.query("INSERT INTO t (id) VALUES (1)"); + await c.query('INSERT INTO t (id) VALUES (1)'); orderEnsurer.add(2); // This will error - await c.query("INSERT INTO t (id) VALUES (1)"); + await c.query('INSERT INTO t (id) VALUES (1)'); }).catchError((e) => null); orderEnsurer.add(11); - var result = await conn.query("SELECT id FROM t"); + final result = await conn.query('SELECT id FROM t'); orderEnsurer.add(12); expect(orderEnsurer, [11, 1, 2, 12]); expect(result, []); }); - test("Executes pending transaction", () async { - var orderEnsurer = []; + test('Executes pending transaction', () async { + final orderEnsurer = []; conn.transaction((c) async { orderEnsurer.add(1); - await c.query("INSERT INTO t (id) VALUES (1)"); + await c.query('INSERT INTO t (id) VALUES (1)'); orderEnsurer.add(2); // This will error - await c.query("INSERT INTO t (id) VALUES (1)"); + await c.query('INSERT INTO t (id) VALUES (1)'); }).catchError((e) => null); - var result = await conn.transaction((ctx) async { + final result = await conn.transaction((ctx) async { orderEnsurer.add(11); - return await ctx.query("SELECT id FROM t"); + return await ctx.query('SELECT id FROM t'); }); orderEnsurer.add(12); @@ -373,86 +371,86 @@ void main() { expect(result, []); }); - test("Executes later transaction", () async { + test('Executes later transaction', () async { try { await conn.transaction((c) async { - await c.query("INSERT INTO t (id) VALUES (1)"); - var oneRow = await c.query("SELECT id FROM t"); + await c.query('INSERT INTO t (id) VALUES (1)'); + final oneRow = await c.query('SELECT id FROM t'); expect(oneRow, [ [1] ]); // This will error - await c.query("INSERT INTO t (id) VALUES (1)"); + await c.query('INSERT INTO t (id) VALUES (1)'); }); expect(true, false); } on PostgreSQLException {} - var result = await conn.transaction((ctx) async { - return await ctx.query("SELECT id FROM t"); + final result = await conn.transaction((ctx) async { + return await ctx.query('SELECT id FROM t'); }); expect(result, []); }); }); - group("Transaction:Exception recovery", () { - PostgreSQLConnection conn = null; + group('Transaction:Exception recovery', () { + PostgreSQLConnection conn; setUp(() async { - conn = new PostgreSQLConnection("localhost", 5432, "dart_test", - username: "dart", password: "dart"); + conn = PostgreSQLConnection('localhost', 5432, 'dart_test', + username: 'dart', password: 'dart'); await conn.open(); - await conn.execute("CREATE TEMPORARY TABLE t (id INT UNIQUE)"); + await conn.execute('CREATE TEMPORARY TABLE t (id INT UNIQUE)'); }); tearDown(() async { await conn?.close(); }); - test("Is rolled back/executes later query", () async { + test('Is rolled back/executes later query', () async { try { await conn.transaction((c) async { - await c.query("INSERT INTO t (id) VALUES (1)"); - throw 'foo'; + await c.query('INSERT INTO t (id) VALUES (1)'); + throw Exception('foo'); }); expect(true, false); - } on String {} + } on Exception {} - var noRows = await conn.query("SELECT id FROM t"); + final noRows = await conn.query('SELECT id FROM t'); expect(noRows, []); }); - test("Executes pending query", () async { - var orderEnsurer = []; + test('Executes pending query', () async { + final orderEnsurer = []; conn.transaction((c) async { orderEnsurer.add(1); - await c.query("INSERT INTO t (id) VALUES (1)"); + await c.query('INSERT INTO t (id) VALUES (1)'); orderEnsurer.add(2); - throw 'foo'; + throw Exception('foo'); }).catchError((e) => null); orderEnsurer.add(11); - var result = await conn.query("SELECT id FROM t"); + final result = await conn.query('SELECT id FROM t'); orderEnsurer.add(12); expect(orderEnsurer, [11, 1, 2, 12]); expect(result, []); }); - test("Executes pending transaction", () async { - var orderEnsurer = []; + test('Executes pending transaction', () async { + final orderEnsurer = []; conn.transaction((c) async { orderEnsurer.add(1); - await c.query("INSERT INTO t (id) VALUES (1)"); + await c.query('INSERT INTO t (id) VALUES (1)'); orderEnsurer.add(2); - throw 'foo'; + throw Exception('foo'); }).catchError((e) => null); - var result = await conn.transaction((ctx) async { + final result = await conn.transaction((ctx) async { orderEnsurer.add(11); - return await ctx.query("SELECT id FROM t"); + return await ctx.query('SELECT id FROM t'); }); orderEnsurer.add(12); @@ -460,140 +458,140 @@ void main() { expect(result, []); }); - test("Executes later transaction", () async { + test('Executes later transaction', () async { try { await conn.transaction((c) async { - await c.query("INSERT INTO t (id) VALUES (1)"); - throw 'foo'; + await c.query('INSERT INTO t (id) VALUES (1)'); + throw Exception('foo'); }); expect(true, false); - } on String {} + } on Exception {} - var result = await conn.transaction((ctx) async { - return await ctx.query("SELECT id FROM t"); + final result = await conn.transaction((ctx) async { + return await ctx.query('SELECT id FROM t'); }); expect(result, []); }); test( - "If exception thrown while preparing query, transaction gets rolled back", + 'If exception thrown while preparing query, transaction gets rolled back', () async { try { await conn.transaction((c) async { - await c.query("INSERT INTO t (id) VALUES (1)"); + await c.query('INSERT INTO t (id) VALUES (1)'); - c.query("INSERT INTO t (id) VALUES (@id:int4)", - substitutionValues: {"id": "foobar"}).catchError((_) => null); - await c.query("INSERT INTO t (id) VALUES (2)"); + c.query('INSERT INTO t (id) VALUES (@id:int4)', + substitutionValues: {'id': 'foobar'}).catchError((_) => null); + await c.query('INSERT INTO t (id) VALUES (2)'); }); expect(true, false); } catch (e) { expect(e is FormatException, true); } - var noRows = await conn.query("SELECT id FROM t"); + final noRows = await conn.query('SELECT id FROM t'); expect(noRows, []); }); - test("Async query failure prevents closure from continuning", () async { - var reached = false; + test('Async query failure prevents closure from continuning', () async { + bool reached = false; try { await conn.transaction((c) async { - await c.query("INSERT INTO t (id) VALUES (1)"); + await c.query('INSERT INTO t (id) VALUES (1)'); await c.query("INSERT INTO t (id) VALUE ('foo') RETURNING id"); reached = true; - await c.query("INSERT INTO t (id) VALUES (2)"); + await c.query('INSERT INTO t (id) VALUES (2)'); }); fail('unreachable'); } on PostgreSQLException {} expect(reached, false); - final res = await conn.query("SELECT * FROM t"); + final res = await conn.query('SELECT * FROM t'); expect(res, []); }); test( - "When exception thrown in unawaited on future, transaction is rolled back", + 'When exception thrown in unawaited on future, transaction is rolled back', () async { try { await conn.transaction((c) async { - await c.query("INSERT INTO t (id) VALUES (1)"); + await c.query('INSERT INTO t (id) VALUES (1)'); c .query("INSERT INTO t (id) VALUE ('foo') RETURNING id") .catchError((_) => null); - await c.query("INSERT INTO t (id) VALUES (2)"); + await c.query('INSERT INTO t (id) VALUES (2)'); }); fail('unreachable'); } on PostgreSQLException {} - final res = await conn.query("SELECT * FROM t"); + final res = await conn.query('SELECT * FROM t'); expect(res, []); }); }); - group("Transaction:Rollback recovery", () { - PostgreSQLConnection conn = null; + group('Transaction:Rollback recovery', () { + PostgreSQLConnection conn; setUp(() async { - conn = new PostgreSQLConnection("localhost", 5432, "dart_test", - username: "dart", password: "dart"); + conn = PostgreSQLConnection('localhost', 5432, 'dart_test', + username: 'dart', password: 'dart'); await conn.open(); - await conn.execute("CREATE TEMPORARY TABLE t (id INT UNIQUE)"); + await conn.execute('CREATE TEMPORARY TABLE t (id INT UNIQUE)'); }); tearDown(() async { await conn?.close(); }); - test("Is rolled back/executes later query", () async { - var result = await conn.transaction((c) async { - await c.query("INSERT INTO t (id) VALUES (1)"); + test('Is rolled back/executes later query', () async { + final result = await conn.transaction((c) async { + await c.query('INSERT INTO t (id) VALUES (1)'); c.cancelTransaction(); - await c.query("INSERT INTO t (id) VALUES (2)"); + await c.query('INSERT INTO t (id) VALUES (2)'); }); expect(result is PostgreSQLRollback, true); - var noRows = await conn.query("SELECT id FROM t"); + final noRows = await conn.query('SELECT id FROM t'); expect(noRows, []); }); - test("Executes pending query", () async { - var orderEnsurer = []; + test('Executes pending query', () async { + final orderEnsurer = []; conn.transaction((c) async { orderEnsurer.add(1); - await c.query("INSERT INTO t (id) VALUES (1)"); + await c.query('INSERT INTO t (id) VALUES (1)'); orderEnsurer.add(2); - await c.cancelTransaction(); - await c.query("INSERT INTO t (id) VALUES (2)"); + c.cancelTransaction(); + await c.query('INSERT INTO t (id) VALUES (2)'); }); orderEnsurer.add(11); - var result = await conn.query("SELECT id FROM t"); + final result = await conn.query('SELECT id FROM t'); orderEnsurer.add(12); expect(orderEnsurer, [11, 1, 2, 12]); expect(result, []); }); - test("Executes pending transaction", () async { - var orderEnsurer = []; + test('Executes pending transaction', () async { + final orderEnsurer = []; conn.transaction((c) async { orderEnsurer.add(1); - await c.query("INSERT INTO t (id) VALUES (1)"); + await c.query('INSERT INTO t (id) VALUES (1)'); orderEnsurer.add(2); - await c.cancelTransaction(); - await c.query("INSERT INTO t (id) VALUES (2)"); + c.cancelTransaction(); + await c.query('INSERT INTO t (id) VALUES (2)'); orderEnsurer.add(3); }); - var result = await conn.transaction((ctx) async { + final result = await conn.transaction((ctx) async { orderEnsurer.add(11); - return await ctx.query("SELECT id FROM t"); + return await ctx.query('SELECT id FROM t'); }); orderEnsurer.add(12); @@ -601,16 +599,16 @@ void main() { expect(result, []); }); - test("Executes later transaction", () async { - var result = await conn.transaction((c) async { - await c.query("INSERT INTO t (id) VALUES (1)"); + test('Executes later transaction', () async { + dynamic result = await conn.transaction((c) async { + await c.query('INSERT INTO t (id) VALUES (1)'); c.cancelTransaction(); - await c.query("INSERT INTO t (id) VALUES (2)"); + await c.query('INSERT INTO t (id) VALUES (2)'); }); expect(result is PostgreSQLRollback, true); result = await conn.transaction((ctx) async { - return await ctx.query("SELECT id FROM t"); + return await ctx.query('SELECT id FROM t'); }); expect(result, []); }); From 365ffb62dc4f6b80449ae4b25899ad7c93373335 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Istv=C3=A1n=20So=C3=B3s?= Date: Sat, 23 Mar 2019 11:08:38 +0100 Subject: [PATCH 05/20] Misc code update (#82) Misc code hardening. --- lib/src/binary_codec.dart | 6 +- lib/src/client_messages.dart | 124 +++++++++++++++------------------ lib/src/connection.dart | 4 +- lib/src/constants.dart | 10 +-- lib/src/query.dart | 9 +-- lib/src/query_cache.dart | 17 +++-- lib/src/query_queue.dart | 4 +- lib/src/substituter.dart | 2 +- lib/src/text_codec.dart | 6 +- lib/src/transaction_proxy.dart | 23 +++--- test/query_reuse_test.dart | 21 +++--- 11 files changed, 109 insertions(+), 117 deletions(-) diff --git a/lib/src/binary_codec.dart b/lib/src/binary_codec.dart index 2108663..541854c 100644 --- a/lib/src/binary_codec.dart +++ b/lib/src/binary_codec.dart @@ -7,9 +7,9 @@ import '../postgres.dart'; import 'types.dart'; class PostgresBinaryEncoder extends Converter { - const PostgresBinaryEncoder(this.dataType); + final PostgreSQLDataType _dataType; - final PostgreSQLDataType dataType; + const PostgresBinaryEncoder(this._dataType); @override Uint8List convert(dynamic value) { @@ -17,7 +17,7 @@ class PostgresBinaryEncoder extends Converter { return null; } - switch (dataType) { + switch (_dataType) { case PostgreSQLDataType.boolean: { if (value is bool) { diff --git a/lib/src/client_messages.dart b/lib/src/client_messages.dart index f46a49f..f737953 100644 --- a/lib/src/client_messages.dart +++ b/lib/src/client_messages.dart @@ -48,26 +48,21 @@ abstract class ClientMessage { } class StartupMessage extends ClientMessage { - StartupMessage(String databaseName, String timeZone, {String username}) { - this.databaseName = UTF8BackedString(databaseName); - this.timeZone = UTF8BackedString(timeZone); - if (username != null) { - this.username = UTF8BackedString(username); - } - } + final UTF8BackedString _username; + final UTF8BackedString _databaseName; + final UTF8BackedString _timeZone; - UTF8BackedString username; - UTF8BackedString databaseName; - UTF8BackedString timeZone; - - ByteData buffer; + StartupMessage(String databaseName, String timeZone, {String username}) + : _databaseName = UTF8BackedString(databaseName), + _timeZone = UTF8BackedString(timeZone), + _username = username == null ? null : UTF8BackedString(username); @override int get length { final fixedLength = 53; - final variableLength = (username?.utf8Length ?? 0) + - databaseName.utf8Length + - timeZone.utf8Length + + final variableLength = (_username?.utf8Length ?? 0) + + _databaseName.utf8Length + + _timeZone.utf8Length + 3; return fixedLength + variableLength; @@ -78,80 +73,78 @@ class StartupMessage extends ClientMessage { buffer.writeInt32(length); buffer.writeInt32(ClientMessage.ProtocolVersion); - if (username != null) { + if (_username != null) { applyBytesToBuffer((UTF8ByteConstants.user), buffer); - applyStringToBuffer(username, buffer); + applyStringToBuffer(_username, buffer); } applyBytesToBuffer(UTF8ByteConstants.database, buffer); - applyStringToBuffer(databaseName, buffer); + applyStringToBuffer(_databaseName, buffer); applyBytesToBuffer(UTF8ByteConstants.clientEncoding, buffer); applyBytesToBuffer(UTF8ByteConstants.utf8, buffer); applyBytesToBuffer(UTF8ByteConstants.timeZone, buffer); - applyStringToBuffer(timeZone, buffer); + applyStringToBuffer(_timeZone, buffer); buffer.writeInt8(0); } } class AuthMD5Message extends ClientMessage { + UTF8BackedString _hashedAuthString; + AuthMD5Message(String username, String password, List saltBytes) { final passwordHash = md5.convert('$password$username'.codeUnits).toString(); final saltString = String.fromCharCodes(saltBytes); final md5Hash = md5.convert('$passwordHash$saltString'.codeUnits).toString(); - hashedAuthString = UTF8BackedString('md5$md5Hash'); + _hashedAuthString = UTF8BackedString('md5$md5Hash'); } - UTF8BackedString hashedAuthString; - @override int get length { - return 6 + hashedAuthString.utf8Length; + return 6 + _hashedAuthString.utf8Length; } @override void applyToBuffer(ByteDataWriter buffer) { buffer.writeUint8(ClientMessage.PasswordIdentifier); buffer.writeUint32(length - 1); - applyStringToBuffer(hashedAuthString, buffer); + applyStringToBuffer(_hashedAuthString, buffer); } } class QueryMessage extends ClientMessage { - QueryMessage(String queryString) { - this.queryString = UTF8BackedString(queryString); - } + final UTF8BackedString _queryString; - UTF8BackedString queryString; + QueryMessage(String queryString) + : _queryString = UTF8BackedString(queryString); @override int get length { - return 6 + queryString.utf8Length; + return 6 + _queryString.utf8Length; } @override void applyToBuffer(ByteDataWriter buffer) { buffer.writeUint8(ClientMessage.QueryIdentifier); buffer.writeUint32(length - 1); - applyStringToBuffer(queryString, buffer); + applyStringToBuffer(_queryString, buffer); } } class ParseMessage extends ClientMessage { - ParseMessage(String statement, {String statementName = ''}) { - this.statement = UTF8BackedString(statement); - this.statementName = UTF8BackedString(statementName); - } + final UTF8BackedString _statementName; + final UTF8BackedString _statement; - UTF8BackedString statementName; - UTF8BackedString statement; + ParseMessage(String statement, {String statementName = ''}) + : _statement = UTF8BackedString(statement), + _statementName = UTF8BackedString(statementName); @override int get length { - return 9 + statement.utf8Length + statementName.utf8Length; + return 9 + _statement.utf8Length + _statementName.utf8Length; } @override @@ -159,22 +152,21 @@ class ParseMessage extends ClientMessage { buffer.writeUint8(ClientMessage.ParseIdentifier); buffer.writeUint32(length - 1); // Name of prepared statement - applyStringToBuffer(statementName, buffer); - applyStringToBuffer(statement, buffer); // Query string + applyStringToBuffer(_statementName, buffer); + applyStringToBuffer(_statement, buffer); // Query string buffer.writeUint16(0); } } class DescribeMessage extends ClientMessage { - DescribeMessage({String statementName = ''}) { - this.statementName = UTF8BackedString(statementName); - } + final UTF8BackedString _statementName; - UTF8BackedString statementName; + DescribeMessage({String statementName = ''}) + : _statementName = UTF8BackedString(statementName); @override int get length { - return 7 + statementName.utf8Length; + return 7 + _statementName.utf8Length; } @override @@ -182,35 +174,33 @@ class DescribeMessage extends ClientMessage { buffer.writeUint8(ClientMessage.DescribeIdentifier); buffer.writeUint32(length - 1); buffer.writeUint8(83); - applyStringToBuffer(statementName, buffer); // Name of prepared statement + applyStringToBuffer(_statementName, buffer); // Name of prepared statement } } class BindMessage extends ClientMessage { - BindMessage(this.parameters, {String statementName = ''}) { - typeSpecCount = parameters.where((p) => p.isBinary).length; - this.statementName = UTF8BackedString(statementName); - } - - List parameters; - UTF8BackedString statementName; - - int typeSpecCount; + final List _parameters; + final UTF8BackedString _statementName; + final int _typeSpecCount; int _cachedLength; + BindMessage(this._parameters, {String statementName = ''}) + : _typeSpecCount = _parameters.where((p) => p.isBinary).length, + _statementName = UTF8BackedString(statementName); + @override int get length { if (_cachedLength == null) { - var inputParameterElementCount = parameters.length; - if (typeSpecCount == parameters.length || typeSpecCount == 0) { + var inputParameterElementCount = _parameters.length; + if (_typeSpecCount == _parameters.length || _typeSpecCount == 0) { inputParameterElementCount = 1; } _cachedLength = 15; - _cachedLength += statementName.utf8Length; + _cachedLength += _statementName.utf8Length; _cachedLength += inputParameterElementCount * 2; _cachedLength += - parameters.fold(0, (len, ParameterValue paramValue) { + _parameters.fold(0, (len, ParameterValue paramValue) { if (paramValue.bytes == null) { return len + 4; } else { @@ -229,30 +219,30 @@ class BindMessage extends ClientMessage { // Name of portal - currently unnamed portal. applyBytesToBuffer([0], buffer); // Name of prepared statement. - applyStringToBuffer(statementName, buffer); + applyStringToBuffer(_statementName, buffer); // OK, if we have no specified types at all, we can use 0. If we have all specified types, we can use 1. If we have a mix, we have to individually // call out each type. - if (typeSpecCount == parameters.length) { + if (_typeSpecCount == _parameters.length) { buffer.writeUint16(1); // Apply following format code for all parameters by indicating 1 buffer.writeUint16(ClientMessage.FormatBinary); - } else if (typeSpecCount == 0) { + } else if (_typeSpecCount == 0) { buffer.writeUint16(1); // Apply following format code for all parameters by indicating 1 buffer.writeUint16(ClientMessage.FormatText); } else { // Well, we have some text and some binary, so we have to be explicit about each one - buffer.writeUint16(parameters.length); - parameters.forEach((p) { + buffer.writeUint16(_parameters.length); + _parameters.forEach((p) { buffer.writeUint16( p.isBinary ? ClientMessage.FormatBinary : ClientMessage.FormatText); }); } // This must be the number of $n's in the query. - buffer.writeUint16(parameters.length); - parameters.forEach((p) { + buffer.writeUint16(_parameters.length); + _parameters.forEach((p) { if (p.bytes == null) { buffer.writeInt32(-1); } else { @@ -268,8 +258,6 @@ class BindMessage extends ClientMessage { } class ExecuteMessage extends ClientMessage { - ExecuteMessage(); - @override int get length { return 10; @@ -285,8 +273,6 @@ class ExecuteMessage extends ClientMessage { } class SyncMessage extends ClientMessage { - SyncMessage(); - @override int get length { return 5; diff --git a/lib/src/connection.dart b/lib/src/connection.dart index 3b1eefd..c67a379 100644 --- a/lib/src/connection.dart +++ b/lib/src/connection.dart @@ -205,9 +205,9 @@ class PostgreSQLConnection extends Object final proxy = _TransactionProxy(this, queryBlock, commitTimeoutInSeconds); - await _enqueue(proxy.beginQuery); + await _enqueue(proxy._beginQuery); - return await proxy.completer.future; + return await proxy.future; } @override diff --git a/lib/src/constants.dart b/lib/src/constants.dart index 1364c63..4d7d872 100644 --- a/lib/src/constants.dart +++ b/lib/src/constants.dart @@ -1,7 +1,7 @@ class UTF8ByteConstants { - static const user = const [117, 115, 101, 114, 0]; - static const database = const [100, 97, 116, 97, 98, 97, 115, 101, 0]; - static const clientEncoding = const [ + static const user = const [117, 115, 101, 114, 0]; + static const database = const [100, 97, 116, 97, 98, 97, 115, 101, 0]; + static const clientEncoding = const [ 99, 108, 105, @@ -19,6 +19,6 @@ class UTF8ByteConstants { 103, 0 ]; - static const utf8 = const [85, 84, 70, 56, 0]; - static const timeZone = const [84, 105, 109, 101, 90, 111, 110, 101, 0]; + static const utf8 = const [85, 84, 70, 56, 0]; + static const timeZone = const [84, 105, 109, 101, 90, 111, 110, 101, 0]; } diff --git a/lib/src/query.dart b/lib/src/query.dart index b76247b..21d49f1 100644 --- a/lib/src/query.dart +++ b/lib/src/query.dart @@ -28,8 +28,8 @@ class Query { final PostgreSQLExecutionContext transaction; final PostgreSQLConnection connection; - List specifiedParameterTypeCodes; - List> rows = []; + List _specifiedParameterTypeCodes; + final rows = >[]; CachedQuery cache; @@ -68,7 +68,8 @@ class Query { return '\$$index'; }); - specifiedParameterTypeCodes = formatIdentifiers.map((i) => i.type).toList(); + _specifiedParameterTypeCodes = + formatIdentifiers.map((i) => i.type).toList(); final parameterList = formatIdentifiers .map((id) => ParameterValue(id, substitutionValues)) @@ -108,7 +109,7 @@ class Query { PostgreSQLException validateParameters(List parameterTypeIDs) { final actualParameterTypeCodeIterator = parameterTypeIDs.iterator; final parametersAreMismatched = - specifiedParameterTypeCodes.map((specifiedType) { + _specifiedParameterTypeCodes.map((specifiedType) { actualParameterTypeCodeIterator.moveNext(); if (specifiedType == null) { diff --git a/lib/src/query_cache.dart b/lib/src/query_cache.dart index 221c8f9..2acf5d9 100644 --- a/lib/src/query_cache.dart +++ b/lib/src/query_cache.dart @@ -1,8 +1,11 @@ import 'query.dart'; class QueryCache { - final Map queries = {}; - int idCounter = 0; + final Map _queries = {}; + int _idCounter = 0; + + int get length => _queries.length; + bool get isEmpty => _queries.isEmpty; void add(Query query) { if (query.cache == null) { @@ -10,7 +13,7 @@ class QueryCache { } if (query.cache.isValid) { - queries[query.statement] = query.cache; + _queries[query.statement] = query.cache; } } @@ -19,18 +22,18 @@ class QueryCache { return null; } - return queries[statementId]; + return _queries[statementId]; } String identifierForQuery(Query query) { - final existing = queries[query.statement]; + final existing = _queries[query.statement]; if (existing != null) { return existing.preparedStatementName; } - final string = '$idCounter'.padLeft(12, '0'); + final string = '$_idCounter'.padLeft(12, '0'); - idCounter++; + _idCounter++; return string; } diff --git a/lib/src/query_queue.dart b/lib/src/query_queue.dart index 1007d84..29514cb 100644 --- a/lib/src/query_queue.dart +++ b/lib/src/query_queue.dart @@ -7,7 +7,7 @@ import 'query.dart'; class QueryQueue extends ListBase> implements List> { - List> _inner = []; + List> _inner = >[]; bool _isCancelled = false; PostgreSQLException get _cancellationException => PostgreSQLException( @@ -24,7 +24,7 @@ class QueryQueue extends ListBase> _isCancelled = true; error ??= _cancellationException; final existing = _inner; - _inner = []; + _inner = >[]; // We need to jump this to the next event so that the queries // get the error and not the close message, since completeError is diff --git a/lib/src/substituter.dart b/lib/src/substituter.dart index 41505f7..35ce77b 100644 --- a/lib/src/substituter.dart +++ b/lib/src/substituter.dart @@ -55,7 +55,7 @@ class PostgreSQLFormat { static String substitute(String fmtString, Map values, {SQLReplaceIdentifierFunction replace}) { final converter = PostgresTextEncoder(true); - values ??= {}; + values ??= {}; replace ??= (spec, index) => converter.convert(values[spec.name]); final items = []; diff --git a/lib/src/text_codec.dart b/lib/src/text_codec.dart index f85516e..215f6a4 100644 --- a/lib/src/text_codec.dart +++ b/lib/src/text_codec.dart @@ -3,9 +3,9 @@ import 'dart:convert'; import 'package:postgres/postgres.dart'; class PostgresTextEncoder extends Converter { - const PostgresTextEncoder(this.escapeStrings); + const PostgresTextEncoder(this._escapeStrings); - final bool escapeStrings; + final bool _escapeStrings; @override String convert(dynamic value) { @@ -22,7 +22,7 @@ class PostgresTextEncoder extends Converter { } if (value is String) { - return encodeString(value, escapeStrings); + return encodeString(value, _escapeStrings); } if (value is DateTime) { diff --git a/lib/src/transaction_proxy.dart b/lib/src/transaction_proxy.dart index df530c3..eff2e8d 100644 --- a/lib/src/transaction_proxy.dart +++ b/lib/src/transaction_proxy.dart @@ -8,20 +8,20 @@ class _TransactionProxy extends Object implements PostgreSQLExecutionContext { _TransactionProxy( this._connection, this.executionBlock, this.commitTimeoutInSeconds) { - beginQuery = Query('BEGIN', {}, _connection, this) + _beginQuery = Query('BEGIN', {}, _connection, this) ..onlyReturnAffectedRowCount = true; - beginQuery.future.then(startTransaction).catchError((err, StackTrace st) { + _beginQuery.future.then(startTransaction).catchError((err, StackTrace st) { Future(() { - completer.completeError(err, st); + _completer.completeError(err, st); }); }); } - Query beginQuery; - Completer completer = Completer(); + Query _beginQuery; + final _completer = Completer(); - Future get future => completer.future; + Future get future => _completer.future; @override final PostgreSQLConnection _connection; @@ -66,7 +66,7 @@ class _TransactionProxy extends Object if (!_hasRolledBack && !_hasFailed) { await execute('COMMIT', timeoutInSeconds: commitTimeoutInSeconds); - completer.complete(result); + _completer.complete(result); } } @@ -100,9 +100,9 @@ class _TransactionProxy extends Object } if (object is _TransactionRollbackException) { - completer.complete(PostgreSQLRollback._(object.reason)); + _completer.complete(PostgreSQLRollback._(object.reason)); } else { - completer.completeError(object, trace); + _completer.completeError(object, trace); } } @@ -131,5 +131,8 @@ class PostgreSQLRollback { PostgreSQLRollback._(this.reason); /// The reason the transaction was cancelled. - String reason; + final String reason; + + @override + String toString() => 'PostgreSQLRollback: $reason'; } diff --git a/test/query_reuse_test.dart b/test/query_reuse_test.dart index a6984eb..306a069 100644 --- a/test/query_reuse_test.dart +++ b/test/query_reuse_test.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:mirrors'; +import 'package:postgres/src/query_cache.dart'; import 'package:test/test.dart'; import 'package:postgres/postgres.dart'; @@ -339,7 +340,7 @@ void main() { hasCachedQueryNamed( connection, 'select i1, i2 from t where i1 > @i1'), true); - expect(cachedQueryMap(connection).length, 1); + expect(getQueryCache(connection).length, 1); }); test('Call query multiple times, mixing in other named queries, succeeds', @@ -381,7 +382,7 @@ void main() { expect( hasCachedQueryNamed(connection, 'select i1,i2 from t where i2 < @i2'), true); - expect(cachedQueryMap(connection).length, 2); + expect(getQueryCache(connection).length, 2); }); test( @@ -464,7 +465,7 @@ void main() { expect(true, false); } on PostgreSQLException {} - expect(cachedQueryMap(connection).isEmpty, true); + expect(getQueryCache(connection).isEmpty, true); }); test( @@ -479,7 +480,7 @@ void main() { expect(true, false); } on PostgreSQLException {} - expect(cachedQueryMap(connection).length, 0); + expect(getQueryCache(connection).length, 0); }); test( @@ -555,21 +556,19 @@ void main() { expect(results, [ [1, 2] ]); - expect(cachedQueryMap(connection).length, 1); + expect(getQueryCache(connection).length, 1); expect(hasCachedQueryNamed(connection, string), true); }); }); } -Map cachedQueryMap(PostgreSQLConnection connection) { +QueryCache getQueryCache(PostgreSQLConnection connection) { final cacheMirror = reflect(connection).type.declarations.values.firstWhere( (DeclarationMirror dm) => dm.simpleName.toString().contains('_cache')); - return reflect(connection) - .getField(cacheMirror.simpleName) - .getField(#queries) - .reflectee as Map; + return reflect(connection).getField(cacheMirror.simpleName).reflectee + as QueryCache; } bool hasCachedQueryNamed(PostgreSQLConnection connection, String name) { - return cachedQueryMap(connection)[name] != null; + return getQueryCache(connection)[name] != null; } From 8181a89e748fb7064851c10cc5fe0ca8ef0bc278 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Istv=C3=A1n=20So=C3=B3s?= Date: Sun, 24 Mar 2019 23:41:49 +0100 Subject: [PATCH 06/20] Hardened and more efficient ServerMessages. (#83) * Simpler MessageFramer * Refactor ServerMessage creation. * Hardened and more efficient ServerMessages. * Update ErrorResponseMessage parsing --- lib/src/connection.dart | 2 +- lib/src/message_window.dart | 88 +++++++-------- lib/src/query.dart | 6 +- lib/src/server_messages.dart | 208 ++++++++++++++++------------------- test/framer_test.dart | 86 +++++---------- 5 files changed, 167 insertions(+), 223 deletions(-) diff --git a/lib/src/connection.dart b/lib/src/connection.dart index c67a379..a69d689 100644 --- a/lib/src/connection.dart +++ b/lib/src/connection.dart @@ -248,7 +248,7 @@ class PostgreSQLConnection extends Object // anything with that data. _framer.addBytes(castBytes(bytes)); while (_framer.hasMessage) { - final msg = _framer.popMessage().message; + final msg = _framer.popMessage(); try { if (msg is ErrorResponseMessage) { _transitionToState(_connectionState.onErrorResponse(msg)); diff --git a/lib/src/message_window.dart b/lib/src/message_window.dart index 3c0d193..899f5f5 100644 --- a/lib/src/message_window.dart +++ b/lib/src/message_window.dart @@ -5,45 +5,39 @@ import 'package:buffer/buffer.dart'; import 'server_messages.dart'; -class MessageFrame { - static const int HeaderByteSize = 5; - static Map messageTypeMap = { - 49: () => ParseCompleteMessage(), - 50: () => BindCompleteMessage(), - 65: () => NotificationResponseMessage(), - 67: () => CommandCompleteMessage(), - 68: () => DataRowMessage(), - 69: () => ErrorResponseMessage(), - 75: () => BackendKeyMessage(), - 82: () => AuthenticationMessage(), - 83: () => ParameterStatusMessage(), - 84: () => RowDescriptionMessage(), - 90: () => ReadyForQueryMessage(), - 110: () => NoDataMessage(), - 116: () => ParameterDescriptionMessage() - }; +const int _headerByteSize = 5; +final _emptyData = Uint8List(0); - bool get hasReadHeader => type != null; - int type; - int expectedLength; +typedef ServerMessage _ServerMessageFn(Uint8List data); - bool get isComplete => data != null || expectedLength == 0; - Uint8List data; - - ServerMessage get message { - final msgMaker = - messageTypeMap[type] ?? () => UnknownMessage()..code = type; - - final msg = msgMaker() as ServerMessage; - msg.readBytes(data); - return msg; - } -} +Map _messageTypeMap = { + 49: (d) => ParseCompleteMessage(), + 50: (d) => BindCompleteMessage(), + 65: (d) => NotificationResponseMessage(d), + 67: (d) => CommandCompleteMessage(d), + 68: (d) => DataRowMessage(d), + 69: (d) => ErrorResponseMessage(d), + 75: (d) => BackendKeyMessage(d), + 82: (d) => AuthenticationMessage(d), + 83: (d) => ParameterStatusMessage(d), + 84: (d) => RowDescriptionMessage(d), + 90: (d) => ReadyForQueryMessage(d), + 110: (d) => NoDataMessage(), + 116: (d) => ParameterDescriptionMessage(d), +}; class MessageFramer { final _reader = ByteDataReader(); - MessageFrame messageInProgress = MessageFrame(); - final messageQueue = Queue(); + final messageQueue = Queue(); + + int _type; + int _expectedLength; + + bool get _hasReadHeader => _type != null; + bool get _canReadHeader => _reader.remainingLength >= _headerByteSize; + + bool get _isComplete => + _expectedLength == 0 || _expectedLength <= _reader.remainingLength; void addBytes(Uint8List bytes) { _reader.add(bytes); @@ -51,21 +45,21 @@ class MessageFramer { bool evaluateNextMessage = true; while (evaluateNextMessage) { evaluateNextMessage = false; - if (!messageInProgress.hasReadHeader && - _reader.remainingLength >= MessageFrame.HeaderByteSize) { - messageInProgress.type = _reader.readUint8(); - messageInProgress.expectedLength = _reader.readUint32() - 4; - } - if (messageInProgress.hasReadHeader && - messageInProgress.expectedLength > 0 && - _reader.remainingLength >= messageInProgress.expectedLength) { - messageInProgress.data = _reader.read(messageInProgress.expectedLength); + if (!_hasReadHeader && _canReadHeader) { + _type = _reader.readUint8(); + _expectedLength = _reader.readUint32() - 4; } - if (messageInProgress.isComplete) { - messageQueue.add(messageInProgress); - messageInProgress = MessageFrame(); + if (_hasReadHeader && _isComplete) { + final data = + _expectedLength == 0 ? _emptyData : _reader.read(_expectedLength); + final msgMaker = _messageTypeMap[_type]; + final msg = + msgMaker == null ? UnknownMessage(_type, data) : msgMaker(data); + messageQueue.add(msg); + _type = null; + _expectedLength = null; evaluateNextMessage = true; } } @@ -73,7 +67,7 @@ class MessageFramer { bool get hasMessage => messageQueue.isNotEmpty; - MessageFrame popMessage() { + ServerMessage popMessage() { return messageQueue.removeFirst(); } } diff --git a/lib/src/query.dart b/lib/src/query.dart index 21d49f1..50d42ef 100644 --- a/lib/src/query.dart +++ b/lib/src/query.dart @@ -129,7 +129,7 @@ class Query { return null; } - void addRow(List rawRowData) { + void addRow(List rawRowData) { if (onlyReturnAffectedRowCount) { return; } @@ -137,9 +137,7 @@ class Query { final iterator = fieldDescriptions.iterator; final lazyDecodedData = rawRowData.map((bd) { iterator.moveNext(); - - return iterator.current.converter - .convert(bd?.buffer?.asUint8List(bd.offsetInBytes, bd.lengthInBytes)); + return iterator.current.converter.convert(bd); }); rows.add(lazyDecodedData.toList()); diff --git a/lib/src/server_messages.dart b/lib/src/server_messages.dart index 2cfbc47..e0d2cbc 100644 --- a/lib/src/server_messages.dart +++ b/lib/src/server_messages.dart @@ -1,29 +1,38 @@ import 'dart:convert'; import 'dart:typed_data'; +import 'package:buffer/buffer.dart'; + import 'connection.dart'; import 'query.dart'; -abstract class ServerMessage { - void readBytes(Uint8List bytes); -} +abstract class ServerMessage {} class ErrorResponseMessage implements ServerMessage { - List fields = [ErrorField()]; - - @override - void readBytes(Uint8List bytes) { - final lastByteRemovedList = - Uint8List.view(bytes.buffer, bytes.offsetInBytes, bytes.length - 1); - - lastByteRemovedList.forEach((byte) { - if (byte != 0) { - fields.last.add(byte); - return; + final fields = []; + + ErrorResponseMessage(Uint8List bytes) { + final reader = ByteDataReader()..add(bytes); + + int identificationToken; + StringBuffer sb; + + while (reader.remainingLength > 0) { + final byte = reader.readUint8(); + if (identificationToken == null) { + identificationToken = byte; + sb = StringBuffer(); + } else if (byte == 0) { + fields.add(ErrorField(identificationToken, sb.toString())); + identificationToken = null; + sb = null; + } else { + sb.writeCharCode(byte); } - - fields.add(ErrorField()); - }); + } + if (identificationToken != null && sb != null) { + fields.add(ErrorField(identificationToken, sb.toString())); + } } } @@ -37,33 +46,33 @@ class AuthenticationMessage implements ServerMessage { static const int KindGSSContinue = 8; static const int KindSSPI = 9; - int type; - - List salt; + final int type; + final List salt; - @override - void readBytes(Uint8List bytes) { - final view = ByteData.view(bytes.buffer, bytes.offsetInBytes); - type = view.getUint32(0); + AuthenticationMessage._(this.type, this.salt); + factory AuthenticationMessage(Uint8List bytes) { + final reader = ByteDataReader()..add(bytes); + final type = reader.readUint32(); + List salt; if (type == KindMD5Password) { - salt = List(4); - for (var i = 0; i < 4; i++) { - salt[i] = view.getUint8(4 + i); - } + salt = reader.read(4, copy: true); } + return AuthenticationMessage._(type, salt); } } class ParameterStatusMessage extends ServerMessage { - String name; - String value; + final String name; + final String value; - @override - void readBytes(Uint8List bytes) { - name = utf8.decode(bytes.sublist(0, bytes.indexOf(0))); - value = - utf8.decode(bytes.sublist(bytes.indexOf(0) + 1, bytes.lastIndexOf(0))); + ParameterStatusMessage._(this.name, this.value); + + factory ParameterStatusMessage(Uint8List bytes) { + final first0 = bytes.indexOf(0); + final name = utf8.decode(bytes.sublist(0, first0)); + final value = utf8.decode(bytes.sublist(first0 + 1, bytes.lastIndexOf(0))); + return ParameterStatusMessage._(name, value); } } @@ -72,37 +81,34 @@ class ReadyForQueryMessage extends ServerMessage { static const String StateTransaction = 'T'; static const String StateTransactionError = 'E'; - String state; + final String state; - @override - void readBytes(Uint8List bytes) { - state = utf8.decode(bytes); - } + ReadyForQueryMessage(Uint8List bytes) : state = utf8.decode(bytes); } class BackendKeyMessage extends ServerMessage { - int processID; - int secretKey; + final int processID; + final int secretKey; - @override - void readBytes(Uint8List bytes) { + BackendKeyMessage._(this.processID, this.secretKey); + + factory BackendKeyMessage(Uint8List bytes) { final view = ByteData.view(bytes.buffer, bytes.offsetInBytes); - processID = view.getUint32(0); - secretKey = view.getUint32(4); + final processID = view.getUint32(0); + final secretKey = view.getUint32(4); + return BackendKeyMessage._(processID, secretKey); } } class RowDescriptionMessage extends ServerMessage { - List fieldDescriptions; + final fieldDescriptions = []; - @override - void readBytes(Uint8List bytes) { + RowDescriptionMessage(Uint8List bytes) { final view = ByteData.view(bytes.buffer, bytes.offsetInBytes); int offset = 0; final fieldCount = view.getInt16(offset); offset += 2; - fieldDescriptions = []; for (var i = 0; i < fieldCount; i++) { final rowDesc = FieldDescription(); offset = rowDesc.parse(view, offset); @@ -112,28 +118,22 @@ class RowDescriptionMessage extends ServerMessage { } class DataRowMessage extends ServerMessage { - List values = []; + final values = []; - @override - void readBytes(Uint8List bytes) { - final view = ByteData.view(bytes.buffer, bytes.offsetInBytes); - int offset = 0; - final fieldCount = view.getInt16(offset); - offset += 2; + DataRowMessage(Uint8List bytes) { + final reader = ByteDataReader()..add(bytes); + final fieldCount = reader.readInt16(); for (var i = 0; i < fieldCount; i++) { - final dataSize = view.getInt32(offset); - offset += 4; + final dataSize = reader.readInt32(); if (dataSize == 0) { - values.add(ByteData(0)); + values.add(Uint8List(0)); } else if (dataSize == -1) { values.add(null); } else { - final rawBytes = - ByteData.view(bytes.buffer, bytes.offsetInBytes + offset, dataSize); + final rawBytes = reader.read(dataSize); values.add(rawBytes); - offset += dataSize; } } } @@ -143,90 +143,80 @@ class DataRowMessage extends ServerMessage { } class NotificationResponseMessage extends ServerMessage { - int processID; - String channel; - String payload; + final int processID; + final String channel; + final String payload; - @override - void readBytes(Uint8List bytes) { + NotificationResponseMessage._(this.processID, this.channel, this.payload); + + factory NotificationResponseMessage(Uint8List bytes) { final view = ByteData.view(bytes.buffer, bytes.offsetInBytes); - processID = view.getUint32(0); - channel = utf8.decode(bytes.sublist(4, bytes.indexOf(0, 4))); - payload = utf8 - .decode(bytes.sublist(bytes.indexOf(0, 4) + 1, bytes.lastIndexOf(0))); + final processID = view.getUint32(0); + final first0 = bytes.indexOf(0, 4); + final channel = utf8.decode(bytes.sublist(4, first0)); + final payload = + utf8.decode(bytes.sublist(first0 + 1, bytes.lastIndexOf(0))); + return NotificationResponseMessage._(processID, channel, payload); } } class CommandCompleteMessage extends ServerMessage { - int rowsAffected; + final int rowsAffected; static RegExp identifierExpression = RegExp(r'[A-Z ]*'); - @override - void readBytes(Uint8List bytes) { - final str = utf8.decode(bytes.sublist(0, bytes.length - 1)); + CommandCompleteMessage._(this.rowsAffected); + factory CommandCompleteMessage(Uint8List bytes) { + final str = utf8.decode(bytes.sublist(0, bytes.length - 1)); final match = identifierExpression.firstMatch(str); + int rowsAffected = 0; if (match.end < str.length) { rowsAffected = int.parse(str.split(' ').last); - } else { - rowsAffected = 0; } + return CommandCompleteMessage._(rowsAffected); } } class ParseCompleteMessage extends ServerMessage { - @override - void readBytes(Uint8List bytes) {} + ParseCompleteMessage(); @override String toString() => 'Parse Complete Message'; } class BindCompleteMessage extends ServerMessage { - @override - void readBytes(Uint8List bytes) {} + BindCompleteMessage(); @override String toString() => 'Bind Complete Message'; } class ParameterDescriptionMessage extends ServerMessage { - List parameterTypeIDs; - - @override - void readBytes(Uint8List bytes) { - final view = ByteData.view(bytes.buffer, bytes.offsetInBytes); + final parameterTypeIDs = []; - int offset = 0; - final count = view.getUint16(0); - offset += 2; + ParameterDescriptionMessage(Uint8List bytes) { + final reader = ByteDataReader()..add(bytes); + final count = reader.readUint16(); - parameterTypeIDs = []; for (var i = 0; i < count; i++) { - final v = view.getUint32(offset); - offset += 4; - parameterTypeIDs.add(v); + parameterTypeIDs.add(reader.readUint32()); } } } class NoDataMessage extends ServerMessage { - @override - void readBytes(Uint8List bytes) {} + NoDataMessage(); @override String toString() => 'No Data Message'; } class UnknownMessage extends ServerMessage { - Uint8List bytes; - int code; + final int code; + final Uint8List bytes; - @override - void readBytes(Uint8List bytes) { - this.bytes = bytes; - } + UnknownMessage(this.code, this.bytes); @override int get hashCode { @@ -295,16 +285,8 @@ class ErrorField { return PostgreSQLSeverity.unknown; } - int identificationToken; - - String get text => _buffer.toString(); - final _buffer = StringBuffer(); + final int identificationToken; + final String text; - void add(int byte) { - if (identificationToken == null) { - identificationToken = byte; - } else { - _buffer.writeCharCode(byte); - } - } + ErrorField(this.identificationToken, this.text); } diff --git a/test/framer_test.dart b/test/framer_test.dart index 25f62d7..79139e3 100644 --- a/test/framer_test.dart +++ b/test/framer_test.dart @@ -22,11 +22,9 @@ void main() { messageWithBytes([1, 2, 3], 1) ])); - final messages = framer.messageQueue.map((f) => f.message).toList(); + final messages = framer.messageQueue.toList(); expect(messages, [ - UnknownMessage() - ..code = 1 - ..bytes = Uint8List.fromList([1, 2, 3]) + UnknownMessage(1, Uint8List.fromList([1, 2, 3])), ]); }); @@ -36,14 +34,10 @@ void main() { messageWithBytes([1, 2, 3, 4], 2) ])); - final messages = framer.messageQueue.map((f) => f.message).toList(); + final messages = framer.messageQueue.toList(); expect(messages, [ - UnknownMessage() - ..code = 1 - ..bytes = Uint8List.fromList([1, 2, 3]), - UnknownMessage() - ..code = 2 - ..bytes = Uint8List.fromList([1, 2, 3, 4]) + UnknownMessage(1, Uint8List.fromList([1, 2, 3])), + UnknownMessage(2, Uint8List.fromList([1, 2, 3, 4])), ]); }); @@ -55,11 +49,9 @@ void main() { framer.addBytes(fragments.last); - final messages = framer.messageQueue.map((f) => f.message).toList(); + final messages = framer.messageQueue.toList(); expect(messages, [ - UnknownMessage() - ..code = 1 - ..bytes = Uint8List.fromList([1, 2, 3]) + UnknownMessage(1, Uint8List.fromList([1, 2, 3])) ]); }); @@ -76,11 +68,9 @@ void main() { framer.addBytes(fragments.last); - final messages = framer.messageQueue.map((f) => f.message).toList(); + final messages = framer.messageQueue.toList(); expect(messages, [ - UnknownMessage() - ..code = 1 - ..bytes = Uint8List.fromList([1, 2, 3]) + UnknownMessage(1, Uint8List.fromList([1, 2, 3])), ]); }); @@ -95,14 +85,10 @@ void main() { framer.addBytes(message2Fragments.last); - final messages = framer.messageQueue.map((f) => f.message).toList(); + final messages = framer.messageQueue.toList(); expect(messages, [ - UnknownMessage() - ..code = 1 - ..bytes = Uint8List.fromList([1, 2, 3]), - UnknownMessage() - ..code = 2 - ..bytes = Uint8List.fromList([2, 2, 3]), + UnknownMessage(1, Uint8List.fromList([1, 2, 3])), + UnknownMessage(2, Uint8List.fromList([2, 2, 3])), ]); }); @@ -117,14 +103,10 @@ void main() { framer.addBytes(message2Fragments.last); - final messages = framer.messageQueue.map((f) => f.message).toList(); + final messages = framer.messageQueue.toList(); expect(messages, [ - UnknownMessage() - ..code = 1 - ..bytes = Uint8List.fromList([1, 2, 3]), - UnknownMessage() - ..code = 2 - ..bytes = Uint8List.fromList([2, 2, 3]), + UnknownMessage(1, Uint8List.fromList([1, 2, 3])), + UnknownMessage(2, Uint8List.fromList([2, 2, 3])), ]); }); @@ -136,11 +118,9 @@ void main() { framer.addBytes(fragments.last); - final messages = framer.messageQueue.map((f) => f.message).toList(); + final messages = framer.messageQueue.toList(); expect(messages, [ - UnknownMessage() - ..code = 1 - ..bytes = Uint8List.fromList([1, 2, 3, 4, 5, 6, 7]) + UnknownMessage(1, Uint8List.fromList([1, 2, 3, 4, 5, 6, 7])), ]); }); @@ -156,14 +136,10 @@ void main() { framer.addBytes(fragmentedMessageBuffer(message, 8).last); - final messages = framer.messageQueue.map((f) => f.message).toList(); + final messages = framer.messageQueue.toList(); expect(messages, [ - UnknownMessage() - ..code = 0 - ..bytes = Uint8List.fromList([1, 2]), - UnknownMessage() - ..code = 1 - ..bytes = Uint8List.fromList([1, 2, 3, 4, 5, 6, 7]) + UnknownMessage(0, Uint8List.fromList([1, 2])), + UnknownMessage(1, Uint8List.fromList([1, 2, 3, 4, 5, 6, 7])) ]); }); @@ -185,23 +161,19 @@ void main() { fragmentedMessageBuffer(fragmentedMessageBuffer(message, 3).last, 6) .last); - final messages = framer.messageQueue.map((f) => f.message).toList(); + final messages = framer.messageQueue.toList(); expect(messages, [ - UnknownMessage() - ..code = 0 - ..bytes = Uint8List.fromList([1, 2]), - UnknownMessage() - ..code = 1 - ..bytes = - Uint8List.fromList([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]) + UnknownMessage(0, Uint8List.fromList([1, 2])), + UnknownMessage( + 1, Uint8List.fromList([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13])), ]); }); test('Frame with no data', () { framer.addBytes(bufferWithMessages([messageWithBytes([], 10)])); - final messages = framer.messageQueue.map((f) => f.message).toList(); - expect(messages, [UnknownMessage()..code = 10]); + final messages = framer.messageQueue.toList(); + expect(messages, [UnknownMessage(10, Uint8List(0))]); }); } @@ -231,10 +203,8 @@ flush(MessageFramer framer) { messageWithBytes([1, 2, 3], 1) ])); - final messages = framer.messageQueue.map((f) => f.message).toList(); + final messages = framer.messageQueue.toList(); expect(messages, [ - UnknownMessage() - ..code = 1 - ..bytes = Uint8List.fromList([1, 2, 3]) + UnknownMessage(1, Uint8List.fromList([1, 2, 3])), ]); } From d4d7f2071364380b9f2de4429fdff9f3fea08e4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Istv=C3=A1n=20So=C3=B3s?= Date: Mon, 25 Mar 2019 00:07:40 +0100 Subject: [PATCH 07/20] More efficient binary codec. (#84) --- lib/src/binary_codec.dart | 83 +++++++++++++++++---------------------- 1 file changed, 37 insertions(+), 46 deletions(-) diff --git a/lib/src/binary_codec.dart b/lib/src/binary_codec.dart index 541854c..89c75ce 100644 --- a/lib/src/binary_codec.dart +++ b/lib/src/binary_codec.dart @@ -6,6 +6,28 @@ import 'package:buffer/buffer.dart'; import '../postgres.dart'; import 'types.dart'; +final _bool0 = Uint8List(1)..[0] = 0; +final _bool1 = Uint8List(1)..[0] = 1; +final _dashUnit = '-'.codeUnits.first; +final _hex = [ + '0', + '1', + '2', + '3', + '4', + '5', + '6', + '7', + '8', + '9', + 'a', + 'b', + 'c', + 'd', + 'e', + 'f', +]; + class PostgresBinaryEncoder extends Converter { final PostgreSQLDataType _dataType; @@ -21,9 +43,7 @@ class PostgresBinaryEncoder extends Converter { case PostgreSQLDataType.boolean: { if (value is bool) { - final bd = ByteData(1); - bd.setUint8(0, value ? 1 : 0); - return bd.buffer.asUint8List(); + return value ? _bool1 : _bool0; } throw FormatException( 'Invalid type for parameter value. Expected: bool Got: ${value.runtimeType}'); @@ -127,13 +147,10 @@ class PostgresBinaryEncoder extends Converter { case PostgreSQLDataType.json: { final jsonBytes = utf8.encode(json.encode(value)); - final outBuffer = Uint8List(jsonBytes.length + 1); - outBuffer[0] = 1; - for (var i = 0; i < jsonBytes.length; i++) { - outBuffer[i + 1] = jsonBytes[i]; - } - - return outBuffer; + final writer = ByteDataWriter(bufferLength: jsonBytes.length + 1); + writer.writeUint8(1); + writer.write(jsonBytes); + return writer.toBytes(); } case PostgreSQLDataType.byteArray: @@ -152,11 +169,10 @@ class PostgresBinaryEncoder extends Converter { 'Invalid type for parameter value. Expected: String Got: ${value.runtimeType}'); } - final dashUnit = '-'.codeUnits.first; final hexBytes = (value as String) .toLowerCase() .codeUnits - .where((c) => c != dashUnit) + .where((c) => c != _dashUnit) .toList(); if (hexBytes.length != 32) { throw FormatException( @@ -175,11 +191,11 @@ class PostgresBinaryEncoder extends Converter { }; final outBuffer = Uint8List(16); - for (var i = 0; i < hexBytes.length; i += 2) { + for (var i = 0, j = 0; i < hexBytes.length; i += 2, j++) { final upperByte = byteConvert(hexBytes[i]); final lowerByte = byteConvert(hexBytes[i + 1]); - outBuffer[i ~/ 2] = upperByte * 16 + lowerByte; + outBuffer[j] = (upperByte << 4) + lowerByte; } return outBuffer; } @@ -208,8 +224,7 @@ class PostgresBinaryDecoder extends Converter { switch (dataType) { case PostgreSQLDataType.name: case PostgreSQLDataType.text: - return utf8.decode( - value.buffer.asUint8List(value.offsetInBytes, value.lengthInBytes)); + return utf8.decode(value); case PostgreSQLDataType.boolean: return buffer.getInt8(0) != 0; case PostgreSQLDataType.smallInteger: @@ -241,46 +256,22 @@ class PostgresBinaryDecoder extends Converter { } case PostgreSQLDataType.byteArray: - return value.buffer - .asUint8List(value.offsetInBytes, value.lengthInBytes); + return value; case PostgreSQLDataType.uuid: { - final codeDash = '-'.codeUnitAt(0); - - final cipher = [ - '0', - '1', - '2', - '3', - '4', - '5', - '6', - '7', - '8', - '9', - 'a', - 'b', - 'c', - 'd', - 'e', - 'f' - ]; - final byteConvert = (int value) { - return cipher[value]; - }; - final buf = StringBuffer(); for (var i = 0; i < buffer.lengthInBytes; i++) { final byteValue = buffer.getUint8(i); - final upperByteValue = byteValue ~/ 16; + final upperByteValue = byteValue >> 4; + final lowerByteValue = byteValue & 0x0f; - final upperByteHex = byteConvert(upperByteValue); - final lowerByteHex = byteConvert(byteValue - (upperByteValue * 16)); + final upperByteHex = _hex[upperByteValue]; + final lowerByteHex = _hex[lowerByteValue]; buf.write(upperByteHex); buf.write(lowerByteHex); if (i == 3 || i == 5 || i == 7 || i == 9) { - buf.writeCharCode(codeDash); + buf.writeCharCode(_dashUnit); } } From 7b8f52c8a6d71eb090de6a6777798b681b5cf6aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Istv=C3=A1n=20So=C3=B3s?= Date: Tue, 26 Mar 2019 21:08:49 +0100 Subject: [PATCH 08/20] Simplified ClientMessages. (#85) --- lib/src/client_messages.dart | 102 +++++++++++------------------------ 1 file changed, 32 insertions(+), 70 deletions(-) diff --git a/lib/src/client_messages.dart b/lib/src/client_messages.dart index f737953..03910c5 100644 --- a/lib/src/client_messages.dart +++ b/lib/src/client_messages.dart @@ -21,17 +21,6 @@ abstract class ClientMessage { static const int SyncIdentifier = 83; static const int PasswordIdentifier = 112; - int get length; - - void applyStringToBuffer(UTF8BackedString string, ByteDataWriter buffer) { - buffer.write(string.utf8Bytes); - buffer.writeInt8(0); - } - - void applyBytesToBuffer(List bytes, ByteDataWriter buffer) { - buffer.write(bytes); - } - void applyToBuffer(ByteDataWriter buffer); Uint8List asBytes() { @@ -47,6 +36,11 @@ abstract class ClientMessage { } } +void _applyStringToBuffer(UTF8BackedString string, ByteDataWriter buffer) { + buffer.write(string.utf8Bytes); + buffer.writeInt8(0); +} + class StartupMessage extends ClientMessage { final UTF8BackedString _username; final UTF8BackedString _databaseName; @@ -58,34 +52,29 @@ class StartupMessage extends ClientMessage { _username = username == null ? null : UTF8BackedString(username); @override - int get length { + void applyToBuffer(ByteDataWriter buffer) { final fixedLength = 53; final variableLength = (_username?.utf8Length ?? 0) + _databaseName.utf8Length + _timeZone.utf8Length + 3; - return fixedLength + variableLength; - } - - @override - void applyToBuffer(ByteDataWriter buffer) { - buffer.writeInt32(length); + buffer.writeInt32(fixedLength + variableLength); buffer.writeInt32(ClientMessage.ProtocolVersion); if (_username != null) { - applyBytesToBuffer((UTF8ByteConstants.user), buffer); - applyStringToBuffer(_username, buffer); + buffer.write(UTF8ByteConstants.user); + _applyStringToBuffer(_username, buffer); } - applyBytesToBuffer(UTF8ByteConstants.database, buffer); - applyStringToBuffer(_databaseName, buffer); + buffer.write(UTF8ByteConstants.database); + _applyStringToBuffer(_databaseName, buffer); - applyBytesToBuffer(UTF8ByteConstants.clientEncoding, buffer); - applyBytesToBuffer(UTF8ByteConstants.utf8, buffer); + buffer.write(UTF8ByteConstants.clientEncoding); + buffer.write(UTF8ByteConstants.utf8); - applyBytesToBuffer(UTF8ByteConstants.timeZone, buffer); - applyStringToBuffer(_timeZone, buffer); + buffer.write(UTF8ByteConstants.timeZone); + _applyStringToBuffer(_timeZone, buffer); buffer.writeInt8(0); } @@ -102,16 +91,12 @@ class AuthMD5Message extends ClientMessage { _hashedAuthString = UTF8BackedString('md5$md5Hash'); } - @override - int get length { - return 6 + _hashedAuthString.utf8Length; - } - @override void applyToBuffer(ByteDataWriter buffer) { buffer.writeUint8(ClientMessage.PasswordIdentifier); - buffer.writeUint32(length - 1); - applyStringToBuffer(_hashedAuthString, buffer); + final length = 5 + _hashedAuthString.utf8Length; + buffer.writeUint32(length); + _applyStringToBuffer(_hashedAuthString, buffer); } } @@ -121,16 +106,12 @@ class QueryMessage extends ClientMessage { QueryMessage(String queryString) : _queryString = UTF8BackedString(queryString); - @override - int get length { - return 6 + _queryString.utf8Length; - } - @override void applyToBuffer(ByteDataWriter buffer) { buffer.writeUint8(ClientMessage.QueryIdentifier); - buffer.writeUint32(length - 1); - applyStringToBuffer(_queryString, buffer); + final length = 5 + _queryString.utf8Length; + buffer.writeUint32(length); + _applyStringToBuffer(_queryString, buffer); } } @@ -142,18 +123,14 @@ class ParseMessage extends ClientMessage { : _statement = UTF8BackedString(statement), _statementName = UTF8BackedString(statementName); - @override - int get length { - return 9 + _statement.utf8Length + _statementName.utf8Length; - } - @override void applyToBuffer(ByteDataWriter buffer) { buffer.writeUint8(ClientMessage.ParseIdentifier); - buffer.writeUint32(length - 1); + final length = 8 + _statement.utf8Length + _statementName.utf8Length; + buffer.writeUint32(length); // Name of prepared statement - applyStringToBuffer(_statementName, buffer); - applyStringToBuffer(_statement, buffer); // Query string + _applyStringToBuffer(_statementName, buffer); + _applyStringToBuffer(_statement, buffer); // Query string buffer.writeUint16(0); } } @@ -164,17 +141,13 @@ class DescribeMessage extends ClientMessage { DescribeMessage({String statementName = ''}) : _statementName = UTF8BackedString(statementName); - @override - int get length { - return 7 + _statementName.utf8Length; - } - @override void applyToBuffer(ByteDataWriter buffer) { buffer.writeUint8(ClientMessage.DescribeIdentifier); - buffer.writeUint32(length - 1); + final length = 6 + _statementName.utf8Length; + buffer.writeUint32(length); buffer.writeUint8(83); - applyStringToBuffer(_statementName, buffer); // Name of prepared statement + _applyStringToBuffer(_statementName, buffer); // Name of prepared statement } } @@ -188,7 +161,6 @@ class BindMessage extends ClientMessage { : _typeSpecCount = _parameters.where((p) => p.isBinary).length, _statementName = UTF8BackedString(statementName); - @override int get length { if (_cachedLength == null) { var inputParameterElementCount = _parameters.length; @@ -217,9 +189,9 @@ class BindMessage extends ClientMessage { buffer.writeUint32(length - 1); // Name of portal - currently unnamed portal. - applyBytesToBuffer([0], buffer); + buffer.writeUint8(0); // Name of prepared statement. - applyStringToBuffer(_statementName, buffer); + _applyStringToBuffer(_statementName, buffer); // OK, if we have no specified types at all, we can use 0. If we have all specified types, we can use 1. If we have a mix, we have to individually // call out each type. @@ -258,26 +230,16 @@ class BindMessage extends ClientMessage { } class ExecuteMessage extends ClientMessage { - @override - int get length { - return 10; - } - @override void applyToBuffer(ByteDataWriter buffer) { buffer.writeUint8(ClientMessage.ExecuteIdentifier); - buffer.writeUint32(length - 1); - applyBytesToBuffer([0], buffer); // Portal name + buffer.writeUint32(9); + buffer.writeUint8(0); // Portal name buffer.writeUint32(0); } } class SyncMessage extends ClientMessage { - @override - int get length { - return 5; - } - @override void applyToBuffer(ByteDataWriter buffer) { buffer.writeUint8(ClientMessage.SyncIdentifier); From 4bab020455bfa8b8684ec66314214137e4b33ce9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Istv=C3=A1n=20So=C3=B3s?= Date: Wed, 27 Mar 2019 22:21:59 +0100 Subject: [PATCH 09/20] Harden a few things in query.dart (#86) --- lib/src/connection.dart | 6 +- lib/src/query.dart | 109 +++++++++++++++++++-------------- lib/src/server_messages.dart | 9 +-- lib/src/transaction_proxy.dart | 8 +-- 4 files changed, 74 insertions(+), 58 deletions(-) diff --git a/lib/src/connection.dart b/lib/src/connection.dart index a69d689..ee05d5b 100644 --- a/lib/src/connection.dart +++ b/lib/src/connection.dart @@ -388,9 +388,9 @@ abstract class _PostgreSQLExecutionContextMixin 'Attempting to execute query, but connection is not open.'); } - final query = - Query(fmtString, substitutionValues, _connection, _transaction) - ..onlyReturnAffectedRowCount = true; + final query = Query( + fmtString, substitutionValues, _connection, _transaction, + onlyReturnAffectedRowCount: true); return _enqueue(query, timeoutInSeconds: timeoutInSeconds); } diff --git a/lib/src/query.dart b/lib/src/query.dart index 50d42ef..2b9c812 100644 --- a/lib/src/query.dart +++ b/lib/src/query.dart @@ -14,10 +14,15 @@ import 'text_codec.dart'; import 'types.dart'; class Query { - Query(this.statement, this.substitutionValues, this.connection, - this.transaction); + Query( + this.statement, + this.substitutionValues, + this.connection, + this.transaction, { + this.onlyReturnAffectedRowCount = false, + }); - bool onlyReturnAffectedRowCount = false; + final bool onlyReturnAffectedRowCount; String statementIdentifier; @@ -171,8 +176,8 @@ class Query { class CachedQuery { CachedQuery(this.preparedStatementName, this.orderedParameters); - String preparedStatementName; - List orderedParameters; + final String preparedStatementName; + final List orderedParameters; List fieldDescriptions; bool get isValid { @@ -193,69 +198,76 @@ class ParameterValue { substitutionValues[identifier.name], identifier.type); } - ParameterValue.binary(dynamic value, PostgreSQLDataType postgresType) - : isBinary = true { + factory ParameterValue.binary( + dynamic value, PostgreSQLDataType postgresType) { final converter = PostgresBinaryEncoder(postgresType); - bytes = converter.convert(value); - length = bytes?.length ?? 0; + final bytes = converter.convert(value); + final length = bytes?.length ?? 0; + return ParameterValue._(true, bytes, length); } - ParameterValue.text(dynamic value) : isBinary = false { + factory ParameterValue.text(dynamic value) { + Uint8List bytes; if (value != null) { final converter = PostgresTextEncoder(false); bytes = castBytes(utf8.encode(converter.convert(value))); } - length = bytes?.length; + final length = bytes?.length ?? 0; + return ParameterValue._(false, bytes, length); } + ParameterValue._(this.isBinary, this.bytes, this.length); + final bool isBinary; - Uint8List bytes; - int length; + final Uint8List bytes; + final int length; } class FieldDescription { - Converter converter; + final Converter converter; - String fieldName; - int tableID; - int columnID; - int typeID; - int dataTypeSize; - int typeModifier; - int formatCode; + final String fieldName; + final int tableID; + final int columnID; + final int typeID; + final int dataTypeSize; + final int typeModifier; + final int formatCode; String resolvedTableName; - int parse(ByteData byteData, int initialOffset) { - int offset = initialOffset; + FieldDescription._( + this.converter, + this.fieldName, + this.tableID, + this.columnID, + this.typeID, + this.dataTypeSize, + this.typeModifier, + this.formatCode); + + factory FieldDescription.read(ByteDataReader reader) { final buf = StringBuffer(); int byte = 0; do { - byte = byteData.getUint8(offset); - offset += 1; + byte = reader.readUint8(); if (byte != 0) { buf.writeCharCode(byte); } } while (byte != 0); - fieldName = buf.toString(); - - tableID = byteData.getUint32(offset); - offset += 4; - columnID = byteData.getUint16(offset); - offset += 2; - typeID = byteData.getUint32(offset); - offset += 4; - dataTypeSize = byteData.getUint16(offset); - offset += 2; - typeModifier = byteData.getInt32(offset); - offset += 4; - formatCode = byteData.getUint16(offset); - offset += 2; + final fieldName = buf.toString(); - converter = PostgresBinaryDecoder(typeID); + final tableID = reader.readUint32(); + final columnID = reader.readUint16(); + final typeID = reader.readUint32(); + final dataTypeSize = reader.readUint16(); + final typeModifier = reader.readInt32(); + final formatCode = reader.readUint16(); - return offset; + final converter = PostgresBinaryDecoder(typeID); + return FieldDescription._(converter, fieldName, tableID, columnID, typeID, + dataTypeSize, typeModifier, formatCode); } @override @@ -294,7 +306,11 @@ class PostgreSQLFormatIdentifier { 'uuid': PostgreSQLDataType.uuid }; - PostgreSQLFormatIdentifier(String t) { + factory PostgreSQLFormatIdentifier(String t) { + String name; + PostgreSQLDataType type; + String typeCast; + final components = t.split('::'); if (components.length > 1) { typeCast = components.sublist(1).join(''); @@ -321,9 +337,12 @@ class PostgreSQLFormatIdentifier { // Strip @ name = name.substring(1, name.length); + return PostgreSQLFormatIdentifier._(name, type, typeCast); } - String name; - PostgreSQLDataType type; - String typeCast; + PostgreSQLFormatIdentifier._(this.name, this.type, this.typeCast); + + final String name; + final PostgreSQLDataType type; + final String typeCast; } diff --git a/lib/src/server_messages.dart b/lib/src/server_messages.dart index e0d2cbc..9d48d49 100644 --- a/lib/src/server_messages.dart +++ b/lib/src/server_messages.dart @@ -104,14 +104,11 @@ class RowDescriptionMessage extends ServerMessage { final fieldDescriptions = []; RowDescriptionMessage(Uint8List bytes) { - final view = ByteData.view(bytes.buffer, bytes.offsetInBytes); - int offset = 0; - final fieldCount = view.getInt16(offset); - offset += 2; + final reader = ByteDataReader()..add(bytes); + final fieldCount = reader.readInt16(); for (var i = 0; i < fieldCount; i++) { - final rowDesc = FieldDescription(); - offset = rowDesc.parse(view, offset); + final rowDesc = FieldDescription.read(reader); fieldDescriptions.add(rowDesc); } } diff --git a/lib/src/transaction_proxy.dart b/lib/src/transaction_proxy.dart index eff2e8d..294266d 100644 --- a/lib/src/transaction_proxy.dart +++ b/lib/src/transaction_proxy.dart @@ -8,8 +8,8 @@ class _TransactionProxy extends Object implements PostgreSQLExecutionContext { _TransactionProxy( this._connection, this.executionBlock, this.commitTimeoutInSeconds) { - _beginQuery = Query('BEGIN', {}, _connection, this) - ..onlyReturnAffectedRowCount = true; + _beginQuery = Query('BEGIN', {}, _connection, this, + onlyReturnAffectedRowCount: true); _beginQuery.future.then(startTransaction).catchError((err, StackTrace st) { Future(() { @@ -87,8 +87,8 @@ class _TransactionProxy extends Object 'that prevented this query from executing.'); _queue.cancel(err); - final rollback = Query('ROLLBACK', {}, _connection, _transaction) - ..onlyReturnAffectedRowCount = true; + final rollback = Query('ROLLBACK', {}, _connection, _transaction, + onlyReturnAffectedRowCount: true); _queue.addEvenIfCancelled(rollback); _connection._transitionToState(_connection._connectionState.awake()); From a7f2b17e23770a007075fedc024777ae90238cae Mon Sep 17 00:00:00 2001 From: Istvan Soos Date: Tue, 2 Apr 2019 19:48:51 +0200 Subject: [PATCH 10/20] Bump version --- CHANGELOG.md | 5 ++++- pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5f968d..0347961 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,13 @@ # Changelog -## 1.0.3 +## 2.0.0-dev1 - Restricted field access on [PostgreSQLConnection]. - Connection-level default query timeout. - Option to specify timeout for the transaction's `"COMMIT"` query. +- Optimized byte buffer parsing and construction with `package:buffer`. +- Hardened codebase with `package:pedantic` and additional lints. +- Updated codebase to Dart 2.2. ## 1.0.2 - Add connection queue size diff --git a/pubspec.yaml b/pubspec.yaml index 3d853df..63ddf08 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: postgres description: PostgreSQL database driver. Supports statement reuse and binary protocol. -version: 1.0.2 +version: 2.0.0-dev1.0 author: stable|kernel homepage: https://github.com/stablekernel/postgresql-dart documentation: From 0ee0dcddd0acadb9c02fc5b4b631313ea22768e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Istv=C3=A1n=20So=C3=B3s?= Date: Thu, 25 Apr 2019 20:37:29 +0200 Subject: [PATCH 11/20] PostgreSQLResult (#90) * Bump version * PostgreSQLResult --- CHANGELOG.md | 2 ++ lib/src/connection.dart | 17 +++++++++++++++-- lib/src/execution_context.dart | 12 +++++++++++- test/map_return_test.dart | 2 +- 4 files changed, 29 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0347961..90d4e20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ - Optimized byte buffer parsing and construction with `package:buffer`. - Hardened codebase with `package:pedantic` and additional lints. - Updated codebase to Dart 2.2. +- `PostgreSQLResult` and `PostgreSQLResultRow` as the return value of a query. + - Returned lists are protected with `UnmodifiableListView`. ## 1.0.2 - Add connection queue size diff --git a/lib/src/connection.dart b/lib/src/connection.dart index ee05d5b..3653898 100644 --- a/lib/src/connection.dart +++ b/lib/src/connection.dart @@ -1,6 +1,7 @@ library postgres.connection; import 'dart:async'; +import 'dart:collection'; import 'dart:io'; import 'dart:typed_data'; @@ -337,7 +338,7 @@ abstract class _PostgreSQLExecutionContextMixin int get queueSize => _queue.length; @override - Future>> query(String fmtString, + Future query(String fmtString, {Map substitutionValues, bool allowReuse = true, int timeoutInSeconds}) async { @@ -353,7 +354,9 @@ abstract class _PostgreSQLExecutionContextMixin query.statementIdentifier = _connection._cache.identifierForQuery(query); } - return _enqueue(query, timeoutInSeconds: timeoutInSeconds); + final rows = await _enqueue(query, timeoutInSeconds: timeoutInSeconds); + return _PostgreSQLResult( + rows.map((columns) => _PostgreSQLResultRow(columns)).toList()); } @override @@ -474,3 +477,13 @@ abstract class _PostgreSQLExecutionContextMixin Future _onQueryError(Query query, dynamic error, [StackTrace trace]) async {} } + +class _PostgreSQLResult extends UnmodifiableListView + implements PostgreSQLResult { + _PostgreSQLResult(List rows) : super(rows); +} + +class _PostgreSQLResultRow extends UnmodifiableListView + implements PostgreSQLResultRow { + _PostgreSQLResultRow(List columns) : super(columns); +} diff --git a/lib/src/execution_context.dart b/lib/src/execution_context.dart index e526625..18cdbd8 100644 --- a/lib/src/execution_context.dart +++ b/lib/src/execution_context.dart @@ -29,7 +29,7 @@ abstract class PostgreSQLExecutionContext { /// By default, instances of this class will reuse queries. This allows significantly more efficient transport to and from the database. You do not have to do /// anything to opt in to this behavior, this connection will track the necessary information required to reuse queries without intervention. (The [fmtString] is /// the unique identifier to look up reuse information.) You can disable reuse by passing false for [allowReuse]. - Future>> query(String fmtString, + Future query(String fmtString, {Map substitutionValues, bool allowReuse = true, int timeoutInSeconds}); @@ -86,3 +86,13 @@ abstract class PostgreSQLExecutionContext { bool allowReuse = true, int timeoutInSeconds}); } + +/// A single row of a query result. +/// +/// Column values can be accessed through the [] [List] accessor. +abstract class PostgreSQLResultRow implements List {} + +/// The query result. +/// +/// Rows can be accessed through the [] [List] accessor. +abstract class PostgreSQLResult implements List {} diff --git a/test/map_return_test.dart b/test/map_return_test.dart index bdcb463..f38d89d 100644 --- a/test/map_return_test.dart +++ b/test/map_return_test.dart @@ -146,7 +146,7 @@ class InterceptingConnection extends PostgreSQLConnection { List queries = []; @override - Future>> query(String fmtString, + Future query(String fmtString, {Map substitutionValues, bool allowReuse = true, int timeoutInSeconds}) { From 795ac044e3e13a200b7b07abd8be869ac8377b15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Istv=C3=A1n=20So=C3=B3s?= Date: Thu, 25 Apr 2019 22:27:45 +0200 Subject: [PATCH 12/20] PostgreSQLConnection and _TransactionProxy share the OID cache. (#91) --- CHANGELOG.md | 1 + lib/src/connection.dart | 80 +++++++++++++++++++++++------------------ 2 files changed, 46 insertions(+), 35 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 90d4e20..25598e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - Updated codebase to Dart 2.2. - `PostgreSQLResult` and `PostgreSQLResultRow` as the return value of a query. - Returned lists are protected with `UnmodifiableListView`. +- `PostgreSQLConnection` and `_TransactionProxy` share the OID cache. ## 1.0.2 - Add connection queue size diff --git a/lib/src/connection.dart b/lib/src/connection.dart index 3653898..f344782 100644 --- a/lib/src/connection.dart +++ b/lib/src/connection.dart @@ -104,6 +104,7 @@ class PostgreSQLConnection extends Object final Map settings = {}; final _cache = QueryCache(); + final _oidCache = _OidCache(); Socket _socket; MessageFramer _framer = MessageFramer(); int _processID; @@ -325,9 +326,44 @@ class Notification { final String payload; } +class _OidCache { + final _tableOIDNameMap = {}; + + Future _resolveOids( + PostgreSQLExecutionContext c, List columns) async { + //todo (joeconwaystk): If this was a cached query, resolving is table oids is unnecessary. + // It's not a significant impact here, but an area for optimization. This includes + // assigning resolvedTableName + final unresolvedTableOIDs = columns + .map((f) => f.tableID) + .toSet() + .where((oid) => + oid != null && oid > 0 && !_tableOIDNameMap.containsKey(oid)) + .toList() + ..sort(); + + if (unresolvedTableOIDs.isNotEmpty) { + await _resolveTableOIDs(c, unresolvedTableOIDs); + } + } + + Future _resolveTableOIDs(PostgreSQLExecutionContext c, List oids) async { + final unresolvedIDString = oids.join(','); + final orderedTableNames = await c.query( + "SELECT relname FROM pg_class WHERE relkind='r' AND oid IN ($unresolvedIDString) ORDER BY oid ASC"); + + final iterator = oids.iterator; + orderedTableNames.forEach((tableName) { + iterator.moveNext(); + if (tableName.first != null) { + _tableOIDNameMap[iterator.current] = tableName.first as String; + } + }); + } +} + abstract class _PostgreSQLExecutionContextMixin implements PostgreSQLExecutionContext { - final _tableOIDNameMap = {}; final _queue = QueryQueue(); PostgreSQLConnection get _connection; @@ -378,8 +414,9 @@ abstract class _PostgreSQLExecutionContextMixin } final rows = await _enqueue(query, timeoutInSeconds: timeoutInSeconds); - - return _mapifyRows(rows, query.fieldDescriptions); + final columns = query.fieldDescriptions; + await _connection._oidCache._resolveOids(this, columns); + return _mapifyRows(rows, columns); } @override @@ -401,27 +438,14 @@ abstract class _PostgreSQLExecutionContextMixin @override void cancelTransaction({String reason}); - Future>>> _mapifyRows( - List> rows, List columns) async { - //todo (joeconwaystk): If this was a cached query, resolving is table oids is unnecessary. - // It's not a significant impact here, but an area for optimization. This includes - // assigning resolvedTableName - final tableOIDs = Set.from(columns.map((f) => f.tableID)); - final List unresolvedTableOIDs = tableOIDs - .where((oid) => - oid != null && oid > 0 && !_tableOIDNameMap.containsKey(oid)) - .toList(); - unresolvedTableOIDs.sort((int lhs, int rhs) => lhs.compareTo(rhs)); - - if (unresolvedTableOIDs.isNotEmpty) { - await _resolveTableOIDs(unresolvedTableOIDs); - } - + List>> _mapifyRows( + List> rows, List columns) { columns.forEach((desc) { - desc.resolvedTableName = _tableOIDNameMap[desc.tableID]; + desc.resolvedTableName = + _connection._oidCache._tableOIDNameMap[desc.tableID]; }); - final tableNames = tableOIDs.map((oid) => _tableOIDNameMap[oid]).toList(); + final tableNames = columns.map((c) => c.resolvedTableName).toSet().toList(); return rows.map((row) { final rowMap = Map>.fromIterable(tableNames, key: (name) => name as String, value: (_) => {}); @@ -437,20 +461,6 @@ abstract class _PostgreSQLExecutionContextMixin }).toList(); } - Future _resolveTableOIDs(List oids) async { - final unresolvedIDString = oids.join(','); - final orderedTableNames = await query( - "SELECT relname FROM pg_class WHERE relkind='r' AND oid IN ($unresolvedIDString) ORDER BY oid ASC"); - - final iterator = oids.iterator; - orderedTableNames.forEach((tableName) { - iterator.moveNext(); - if (tableName.first != null) { - _tableOIDNameMap[iterator.current] = tableName.first as String; - } - }); - } - Future _enqueue(Query query, {int timeoutInSeconds = 30}) async { if (_queue.add(query)) { _connection._transitionToState(_connection._connectionState.awake()); From 723a73853a209564848db791401b0bb20b1cbd74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Istv=C3=A1n=20So=C3=B3s?= Date: Sun, 26 May 2019 01:28:51 +0200 Subject: [PATCH 13/20] Fix pedantic lints. (#95) --- test/connection_test.dart | 20 +++++++++++++++----- test/query_reuse_test.dart | 16 ++++++++++++---- test/timeout_test.dart | 16 ++++++++++++---- test/transaction_test.dart | 20 +++++++++++++++----- 4 files changed, 54 insertions(+), 18 deletions(-) diff --git a/test/connection_test.dart b/test/connection_test.dart index b1c2b52..d95b7d0 100644 --- a/test/connection_test.dart +++ b/test/connection_test.dart @@ -465,7 +465,9 @@ void main() { try { await conn.open(); expect(true, false); - } on SocketException {} + } on SocketException { + // ignore + } await expectConnectionIsInvalid(conn); }); @@ -479,7 +481,9 @@ void main() { try { await conn.open(); expect(true, false); - } on SocketException {} + } on SocketException { + // ignore + } await expectConnectionIsInvalid(conn); }); @@ -501,7 +505,9 @@ void main() { try { await conn.open(); fail('unreachable'); - } on TimeoutException {} + } on TimeoutException { + // ignore + } await expectConnectionIsInvalid(conn); }); @@ -523,7 +529,9 @@ void main() { try { await conn.open(); fail('unreachable'); - } on TimeoutException {} + } on TimeoutException { + // ignore + } await expectConnectionIsInvalid(conn); }); @@ -584,7 +592,9 @@ void main() { try { await conn.open(); expect(true, false); - } on PostgreSQLException {} + } on PostgreSQLException { + // ignore + } }); }); diff --git a/test/query_reuse_test.dart b/test/query_reuse_test.dart index 306a069..44ebbae 100644 --- a/test/query_reuse_test.dart +++ b/test/query_reuse_test.dart @@ -463,7 +463,9 @@ void main() { try { await connection.query('ljkasd'); expect(true, false); - } on PostgreSQLException {} + } on PostgreSQLException { + // ignore + } expect(getQueryCache(connection).isEmpty, true); }); @@ -478,7 +480,9 @@ void main() { .query(string, substitutionValues: {'i1': 'foo', 'i2': 'bar'}); expect(true, false); - } on PostgreSQLException {} + } on PostgreSQLException { + // ignore + } expect(getQueryCache(connection).length, 0); }); @@ -493,7 +497,9 @@ void main() { .query(string, substitutionValues: {'i1': 'foo', 'i2': 'bar'}); expect(true, false); - } on PostgreSQLException {} + } on PostgreSQLException { + // ignores + } expect(hasCachedQueryNamed(connection, string), false); @@ -528,7 +534,9 @@ void main() { try { await connection.query(string, substitutionValues: {'i': 'foo'}); - } on FormatException {} + } on FormatException { + // ignore + } results = await connection.query(string, substitutionValues: {'i': 2}); expect(results, [ diff --git a/test/timeout_test.dart b/test/timeout_test.dart index 21a5f7f..5ff8f32 100644 --- a/test/timeout_test.dart +++ b/test/timeout_test.dart @@ -26,7 +26,9 @@ void main() { try { await conn.query('SELECT 1', timeoutInSeconds: 1); fail('unreachable'); - } on TimeoutException {} + } on TimeoutException { + // ignore + } expect(f, completes); }); @@ -38,7 +40,9 @@ void main() { await ctx.query('SELECT pg_sleep(2)', timeoutInSeconds: 1); }); fail('unreachable'); - } on TimeoutException {} + } on TimeoutException { + // ignore + } expect(await conn.query('SELECT * from t'), hasLength(0)); }); @@ -52,7 +56,9 @@ void main() { await ctx.query('INSERT INTO t (id) VALUES (1)'); }); fail('unreachable'); - } on TimeoutException {} + } on TimeoutException { + // ignore + } expect(await conn.query('SELECT * from t'), hasLength(0)); }); @@ -63,7 +69,9 @@ void main() { try { await conn.query('SELECT pg_sleep(2)', timeoutInSeconds: 1); fail('unreachable'); - } on TimeoutException {} + } on TimeoutException { + // ignore + } }); test('Query times out, next query in the queue runs', () async { diff --git a/test/transaction_test.dart b/test/transaction_test.dart index a409756..c9f7b8c 100644 --- a/test/transaction_test.dart +++ b/test/transaction_test.dart @@ -384,7 +384,9 @@ void main() { await c.query('INSERT INTO t (id) VALUES (1)'); }); expect(true, false); - } on PostgreSQLException {} + } on PostgreSQLException { + // ignore + } final result = await conn.transaction((ctx) async { return await ctx.query('SELECT id FROM t'); @@ -414,7 +416,9 @@ void main() { throw Exception('foo'); }); expect(true, false); - } on Exception {} + } on Exception { + // ignore + } final noRows = await conn.query('SELECT id FROM t'); expect(noRows, []); @@ -465,7 +469,9 @@ void main() { throw Exception('foo'); }); expect(true, false); - } on Exception {} + } on Exception { + // ignore + } final result = await conn.transaction((ctx) async { return await ctx.query('SELECT id FROM t'); @@ -505,7 +511,9 @@ void main() { await c.query('INSERT INTO t (id) VALUES (2)'); }); fail('unreachable'); - } on PostgreSQLException {} + } on PostgreSQLException { + // ignore + } expect(reached, false); final res = await conn.query('SELECT * FROM t'); @@ -524,7 +532,9 @@ void main() { await c.query('INSERT INTO t (id) VALUES (2)'); }); fail('unreachable'); - } on PostgreSQLException {} + } on PostgreSQLException { + // ignore + } final res = await conn.query('SELECT * FROM t'); expect(res, []); From 2e194f6066d34332b5b98f5ef3d58115da387132 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Istv=C3=A1n=20So=C3=B3s?= Date: Sun, 26 May 2019 01:41:36 +0200 Subject: [PATCH 14/20] Refactor mapifyRows (#96) --- lib/src/connection.dart | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/lib/src/connection.dart b/lib/src/connection.dart index f344782..c22ef4f 100644 --- a/lib/src/connection.dart +++ b/lib/src/connection.dart @@ -329,7 +329,7 @@ class Notification { class _OidCache { final _tableOIDNameMap = {}; - Future _resolveOids( + Future _resolveTableNames( PostgreSQLExecutionContext c, List columns) async { //todo (joeconwaystk): If this was a cached query, resolving is table oids is unnecessary. // It's not a significant impact here, but an area for optimization. This includes @@ -345,6 +345,10 @@ class _OidCache { if (unresolvedTableOIDs.isNotEmpty) { await _resolveTableOIDs(c, unresolvedTableOIDs); } + + columns.forEach((desc) { + desc.resolvedTableName = _tableOIDNameMap[desc.tableID]; + }); } Future _resolveTableOIDs(PostgreSQLExecutionContext c, List oids) async { @@ -415,7 +419,7 @@ abstract class _PostgreSQLExecutionContextMixin final rows = await _enqueue(query, timeoutInSeconds: timeoutInSeconds); final columns = query.fieldDescriptions; - await _connection._oidCache._resolveOids(this, columns); + await _connection._oidCache._resolveTableNames(this, columns); return _mapifyRows(rows, columns); } @@ -440,23 +444,15 @@ abstract class _PostgreSQLExecutionContextMixin List>> _mapifyRows( List> rows, List columns) { - columns.forEach((desc) { - desc.resolvedTableName = - _connection._oidCache._tableOIDNameMap[desc.tableID]; - }); - - final tableNames = columns.map((c) => c.resolvedTableName).toSet().toList(); return rows.map((row) { - final rowMap = Map>.fromIterable(tableNames, - key: (name) => name as String, value: (_) => {}); - - final iterator = columns.iterator; - row.forEach((column) { - iterator.moveNext(); - rowMap[iterator.current.resolvedTableName][iterator.current.fieldName] = - column; + final rowMap = >{}; + columns.forEach((c) { + rowMap.putIfAbsent(c.resolvedTableName, () => {}); }); - + for (int i = 0; i < columns.length; i++) { + final col = columns[i]; + rowMap[col.resolvedTableName][col.fieldName] = row[i]; + } return rowMap; }).toList(); } From d32d38ab66f78c271606495d5bcefc0b74021594 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Istv=C3=A1n=20So=C3=B3s?= Date: Mon, 27 May 2019 22:26:06 +0200 Subject: [PATCH 15/20] Table OIDs are always resolved to table names. (#97) * allowReuse is set only in the implementation method * Table OIDs are always resolved to table names. * Prevent NPE in certain cases. * Fix map_return_test. * Don't reuse OID selects. * Changelog update. --- CHANGELOG.md | 5 ++ lib/src/connection.dart | 96 ++++++++++++++++++++++++---------- lib/src/execution_context.dart | 8 +-- test/map_return_test.dart | 62 ++++++++++------------ 4 files changed, 106 insertions(+), 65 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 25598e8..a17e104 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,11 @@ - `PostgreSQLResult` and `PostgreSQLResultRow` as the return value of a query. - Returned lists are protected with `UnmodifiableListView`. - `PostgreSQLConnection` and `_TransactionProxy` share the OID cache. +- default value for `query(allowReuse = true)` is set only in the implementation method. + +**Breaking behaviour** + +- Table OIDs are always resolved to table names (and not only with mapped queries). ## 1.0.2 - Add connection queue size diff --git a/lib/src/connection.dart b/lib/src/connection.dart index c22ef4f..16754aa 100644 --- a/lib/src/connection.dart +++ b/lib/src/connection.dart @@ -328,9 +328,18 @@ class Notification { class _OidCache { final _tableOIDNameMap = {}; + int _queryCount = 0; - Future _resolveTableNames( - PostgreSQLExecutionContext c, List columns) async { + int get queryCount => _queryCount; + + void clear() { + _queryCount = 0; + _tableOIDNameMap.clear(); + } + + Future _resolveTableNames(_PostgreSQLExecutionContextMixin c, + List columns) async { + if (columns == null) return; //todo (joeconwaystk): If this was a cached query, resolving is table oids is unnecessary. // It's not a significant impact here, but an area for optimization. This includes // assigning resolvedTableName @@ -351,10 +360,15 @@ class _OidCache { }); } - Future _resolveTableOIDs(PostgreSQLExecutionContext c, List oids) async { + Future _resolveTableOIDs( + _PostgreSQLExecutionContextMixin c, List oids) async { + _queryCount++; final unresolvedIDString = oids.join(','); - final orderedTableNames = await c.query( - "SELECT relname FROM pg_class WHERE relkind='r' AND oid IN ($unresolvedIDString) ORDER BY oid ASC"); + final orderedTableNames = await c._query( + "SELECT relname FROM pg_class WHERE relkind='r' AND oid IN ($unresolvedIDString) ORDER BY oid ASC", + allowReuse: false, // inlined OIDs would make it difficult anyway + resolveOids: false, + ); final iterator = oids.iterator; orderedTableNames.forEach((tableName) { @@ -378,11 +392,30 @@ abstract class _PostgreSQLExecutionContextMixin int get queueSize => _queue.length; @override - Future query(String fmtString, - {Map substitutionValues, - bool allowReuse = true, - int timeoutInSeconds}) async { + Future query( + String fmtString, { + Map substitutionValues, + bool allowReuse, + int timeoutInSeconds, + }) => + _query( + fmtString, + substitutionValues: substitutionValues, + allowReuse: allowReuse, + timeoutInSeconds: timeoutInSeconds, + ); + + Future _query( + String fmtString, { + Map substitutionValues, + bool allowReuse, + int timeoutInSeconds, + bool resolveOids, + }) async { + allowReuse ??= true; timeoutInSeconds ??= _connection.queryTimeoutInSeconds; + resolveOids ??= true; + if (_connection.isClosed) { throw PostgreSQLException( 'Attempting to execute query, but connection is not open.'); @@ -395,32 +428,31 @@ abstract class _PostgreSQLExecutionContextMixin } final rows = await _enqueue(query, timeoutInSeconds: timeoutInSeconds); + final columnDescriptions = query.fieldDescriptions; + if (resolveOids) { + await _connection._oidCache._resolveTableNames(this, columnDescriptions); + } + return _PostgreSQLResult( - rows.map((columns) => _PostgreSQLResultRow(columns)).toList()); + columnDescriptions, + rows + .map((columns) => _PostgreSQLResultRow(columnDescriptions, columns)) + .toList()); } @override Future>>> mappedResultsQuery( String fmtString, {Map substitutionValues, - bool allowReuse = true, + bool allowReuse, int timeoutInSeconds}) async { - timeoutInSeconds ??= _connection.queryTimeoutInSeconds; - if (_connection.isClosed) { - throw PostgreSQLException( - 'Attempting to execute query, but connection is not open.'); - } - - final query = Query>>( - fmtString, substitutionValues, _connection, _transaction); - if (allowReuse) { - query.statementIdentifier = _connection._cache.identifierForQuery(query); - } - - final rows = await _enqueue(query, timeoutInSeconds: timeoutInSeconds); - final columns = query.fieldDescriptions; - await _connection._oidCache._resolveTableNames(this, columns); - return _mapifyRows(rows, columns); + final rs = await query( + fmtString, + substitutionValues: substitutionValues, + allowReuse: allowReuse, + timeoutInSeconds: timeoutInSeconds, + ); + return _mapifyRows(rs, rs.columnDescriptions); } @override @@ -486,10 +518,16 @@ abstract class _PostgreSQLExecutionContextMixin class _PostgreSQLResult extends UnmodifiableListView implements PostgreSQLResult { - _PostgreSQLResult(List rows) : super(rows); + @override + final List columnDescriptions; + + _PostgreSQLResult(this.columnDescriptions, List rows) + : super(rows); } class _PostgreSQLResultRow extends UnmodifiableListView implements PostgreSQLResultRow { - _PostgreSQLResultRow(List columns) : super(columns); + final List columnDescriptions; + + _PostgreSQLResultRow(this.columnDescriptions, List columns) : super(columns); } diff --git a/lib/src/execution_context.dart b/lib/src/execution_context.dart index 18cdbd8..a65fce5 100644 --- a/lib/src/execution_context.dart +++ b/lib/src/execution_context.dart @@ -31,7 +31,7 @@ abstract class PostgreSQLExecutionContext { /// the unique identifier to look up reuse information.) You can disable reuse by passing false for [allowReuse]. Future query(String fmtString, {Map substitutionValues, - bool allowReuse = true, + bool allowReuse, int timeoutInSeconds}); /// Executes a query on this context. @@ -83,7 +83,7 @@ abstract class PostgreSQLExecutionContext { Future>>> mappedResultsQuery( String fmtString, {Map substitutionValues, - bool allowReuse = true, + bool allowReuse, int timeoutInSeconds}); } @@ -95,4 +95,6 @@ abstract class PostgreSQLResultRow implements List {} /// The query result. /// /// Rows can be accessed through the [] [List] accessor. -abstract class PostgreSQLResult implements List {} +abstract class PostgreSQLResult implements List { + List get columnDescriptions; +} diff --git a/test/map_return_test.dart b/test/map_return_test.dart index f38d89d..b0135ed 100644 --- a/test/map_return_test.dart +++ b/test/map_return_test.dart @@ -1,12 +1,13 @@ -import 'dart:async'; +import 'dart:mirrors'; + import 'package:postgres/postgres.dart'; import 'package:test/test.dart'; void main() { - InterceptingConnection connection; + PostgreSQLConnection connection; setUp(() async { - connection = InterceptingConnection('localhost', 5432, 'dart_test', + connection = PostgreSQLConnection('localhost', 5432, 'dart_test', username: 'dart', password: 'dart'); await connection.open(); @@ -103,29 +104,21 @@ void main() { }); test('Table names get cached', () async { - final regex = RegExp( - "SELECT relname FROM pg_class WHERE relkind='r' AND oid IN \\(([0-9]*)\\) ORDER BY oid ASC"); - final oids = []; + clearOidQueryCount(connection); + expect(getOidQueryCount(connection), 0); await connection.mappedResultsQuery('SELECT id FROM t'); - expect(connection.queries.length, 1); - var match = regex.firstMatch(connection.queries.first); - oids.add(match.group(1)); - connection.queries.clear(); + expect(getOidQueryCount(connection), 1); await connection.mappedResultsQuery('SELECT id FROM t'); - expect(connection.queries.length, 0); + expect(getOidQueryCount(connection), 1); await connection.mappedResultsQuery( 'SELECT t.id, u.id FROM t LEFT OUTER JOIN u ON t.id=u.t_id'); - expect(connection.queries.length, 1); - match = regex.firstMatch(connection.queries.first); - expect(oids.contains(match.group(1)), false); - oids.add(match.group(1)); - connection.queries.clear(); + expect(getOidQueryCount(connection), 2); await connection.mappedResultsQuery('SELECT u.id FROM u'); - expect(connection.queries.length, 0); + expect(getOidQueryCount(connection), 2); }); test('Non-table mappedResultsQuery succeeds', () async { @@ -138,20 +131,23 @@ void main() { }); } -class InterceptingConnection extends PostgreSQLConnection { - InterceptingConnection(String host, int port, String databaseName, - {String username, String password}) - : super(host, port, databaseName, username: username, password: password); - - List queries = []; - - @override - Future query(String fmtString, - {Map substitutionValues, - bool allowReuse = true, - int timeoutInSeconds}) { - queries.add(fmtString); - return super.query(fmtString, - substitutionValues: substitutionValues, allowReuse: allowReuse); - } +void clearOidQueryCount(PostgreSQLConnection connection) { + final oidCacheMirror = reflect(connection) + .type + .declarations + .values + .firstWhere((DeclarationMirror dm) => + dm.simpleName.toString().contains('_oidCache')); + (reflect(connection).getField(oidCacheMirror.simpleName).reflectee).clear(); +} + +int getOidQueryCount(PostgreSQLConnection connection) { + final oidCacheMirror = reflect(connection) + .type + .declarations + .values + .firstWhere((DeclarationMirror dm) => + dm.simpleName.toString().contains('_oidCache')); + return (reflect(connection).getField(oidCacheMirror.simpleName).reflectee) + .queryCount as int; } From cb748f623865dddf014f84711be0c148ce646967 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Istv=C3=A1n=20So=C3=B3s?= Date: Mon, 27 May 2019 22:59:34 +0200 Subject: [PATCH 16/20] Proper ColumnDefinition interface (#98) --- CHANGELOG.md | 1 + lib/src/connection.dart | 26 ++++++++++++---------- lib/src/execution_context.dart | 11 +++++++++- lib/src/query.dart | 40 ++++++++++++++++++++++------------ test/query_test.dart | 8 +++++++ 5 files changed, 59 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a17e104..7f58609 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - Updated codebase to Dart 2.2. - `PostgreSQLResult` and `PostgreSQLResultRow` as the return value of a query. - Returned lists are protected with `UnmodifiableListView`. + - Exposing column metadata through `ColumnDescription`. - `PostgreSQLConnection` and `_TransactionProxy` share the OID cache. - default value for `query(allowReuse = true)` is set only in the implementation method. diff --git a/lib/src/connection.dart b/lib/src/connection.dart index 16754aa..7ec2b42 100644 --- a/lib/src/connection.dart +++ b/lib/src/connection.dart @@ -337,9 +337,10 @@ class _OidCache { _tableOIDNameMap.clear(); } - Future _resolveTableNames(_PostgreSQLExecutionContextMixin c, + Future> _resolveTableNames( + _PostgreSQLExecutionContextMixin c, List columns) async { - if (columns == null) return; + if (columns == null) return null; //todo (joeconwaystk): If this was a cached query, resolving is table oids is unnecessary. // It's not a significant impact here, but an area for optimization. This includes // assigning resolvedTableName @@ -355,9 +356,9 @@ class _OidCache { await _resolveTableOIDs(c, unresolvedTableOIDs); } - columns.forEach((desc) { - desc.resolvedTableName = _tableOIDNameMap[desc.tableID]; - }); + return columns + .map((c) => c.change(tableName: _tableOIDNameMap[c.tableID])) + .toList(); } Future _resolveTableOIDs( @@ -428,9 +429,10 @@ abstract class _PostgreSQLExecutionContextMixin } final rows = await _enqueue(query, timeoutInSeconds: timeoutInSeconds); - final columnDescriptions = query.fieldDescriptions; + List columnDescriptions = query.fieldDescriptions; if (resolveOids) { - await _connection._oidCache._resolveTableNames(this, columnDescriptions); + columnDescriptions = await _connection._oidCache + ._resolveTableNames(this, columnDescriptions); } return _PostgreSQLResult( @@ -475,15 +477,15 @@ abstract class _PostgreSQLExecutionContextMixin void cancelTransaction({String reason}); List>> _mapifyRows( - List> rows, List columns) { + List> rows, List columns) { return rows.map((row) { final rowMap = >{}; columns.forEach((c) { - rowMap.putIfAbsent(c.resolvedTableName, () => {}); + rowMap.putIfAbsent(c.tableName, () => {}); }); for (int i = 0; i < columns.length; i++) { final col = columns[i]; - rowMap[col.resolvedTableName][col.fieldName] = row[i]; + rowMap[col.tableName][col.columnName] = row[i]; } return rowMap; }).toList(); @@ -519,7 +521,7 @@ abstract class _PostgreSQLExecutionContextMixin class _PostgreSQLResult extends UnmodifiableListView implements PostgreSQLResult { @override - final List columnDescriptions; + final List columnDescriptions; _PostgreSQLResult(this.columnDescriptions, List rows) : super(rows); @@ -527,7 +529,7 @@ class _PostgreSQLResult extends UnmodifiableListView class _PostgreSQLResultRow extends UnmodifiableListView implements PostgreSQLResultRow { - final List columnDescriptions; + final List columnDescriptions; _PostgreSQLResultRow(this.columnDescriptions, List columns) : super(columns); } diff --git a/lib/src/execution_context.dart b/lib/src/execution_context.dart index a65fce5..40e76fb 100644 --- a/lib/src/execution_context.dart +++ b/lib/src/execution_context.dart @@ -87,6 +87,15 @@ abstract class PostgreSQLExecutionContext { int timeoutInSeconds}); } +/// A description of a column. +abstract class ColumnDescription { + /// The name of the column returned by the query. + String get columnName; + + /// The resolved name of the referenced table. + String get tableName; +} + /// A single row of a query result. /// /// Column values can be accessed through the [] [List] accessor. @@ -96,5 +105,5 @@ abstract class PostgreSQLResultRow implements List {} /// /// Rows can be accessed through the [] [List] accessor. abstract class PostgreSQLResult implements List { - List get columnDescriptions; + List get columnDescriptions; } diff --git a/lib/src/query.dart b/lib/src/query.dart index 2b9c812..2c4fec7 100644 --- a/lib/src/query.dart +++ b/lib/src/query.dart @@ -223,10 +223,11 @@ class ParameterValue { final int length; } -class FieldDescription { +class FieldDescription implements ColumnDescription { final Converter converter; - final String fieldName; + @override + final String columnName; final int tableID; final int columnID; final int typeID; @@ -234,17 +235,20 @@ class FieldDescription { final int typeModifier; final int formatCode; - String resolvedTableName; + @override + final String tableName; FieldDescription._( - this.converter, - this.fieldName, - this.tableID, - this.columnID, - this.typeID, - this.dataTypeSize, - this.typeModifier, - this.formatCode); + this.converter, + this.columnName, + this.tableID, + this.columnID, + this.typeID, + this.dataTypeSize, + this.typeModifier, + this.formatCode, + this.tableName, + ); factory FieldDescription.read(ByteDataReader reader) { final buf = StringBuffer(); @@ -266,13 +270,21 @@ class FieldDescription { final formatCode = reader.readUint16(); final converter = PostgresBinaryDecoder(typeID); - return FieldDescription._(converter, fieldName, tableID, columnID, typeID, - dataTypeSize, typeModifier, formatCode); + return FieldDescription._( + converter, fieldName, tableID, columnID, typeID, + dataTypeSize, typeModifier, formatCode, + null, // tableName + ); + } + + FieldDescription change({String tableName}) { + return FieldDescription._(converter, columnName, tableID, columnID, typeID, + dataTypeSize, typeModifier, formatCode, tableName ?? this.tableName); } @override String toString() { - return '$fieldName $tableID $columnID $typeID $dataTypeSize $typeModifier $formatCode'; + return '$columnName $tableID $columnID $typeID $dataTypeSize $typeModifier $formatCode'; } } diff --git a/test/query_test.dart b/test/query_test.dart index dcc5164..091e945 100644 --- a/test/query_test.dart +++ b/test/query_test.dart @@ -39,6 +39,9 @@ void main() { expect(result, [expectedRow]); result = await connection.query('select t from t'); + expect(result.columnDescriptions, hasLength(1)); + expect(result.columnDescriptions.single.tableName, 't'); + expect(result.columnDescriptions.single.columnName, 't'); expect(result, [expectedRow]); }); @@ -152,6 +155,11 @@ void main() { {'a': 'b'}, '01234567-89ab-cdef-0123-0123456789ab' ]; + expect(result.columnDescriptions, hasLength(14)); + expect(result.columnDescriptions.first.tableName, 't'); + expect(result.columnDescriptions.first.columnName, 'i'); + expect(result.columnDescriptions.last.tableName, 't'); + expect(result.columnDescriptions.last.columnName, 'u'); expect(result, [expectedRow]); result = await connection.query( 'select i,s, bi, bs, bl, si, t, f, d, dt, ts, tsz, j, u from t'); From 238beccc81648ddacc21fd099c324fb8db5eff08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Istv=C3=A1n=20So=C3=B3s?= Date: Mon, 27 May 2019 23:24:58 +0200 Subject: [PATCH 17/20] row-level column map functions (#99) --- CHANGELOG.md | 1 + lib/src/connection.dart | 75 +++++++++++++++++++++++----------- lib/src/execution_context.dart | 12 +++++- 3 files changed, 64 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f58609..23ddc99 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - `PostgreSQLResult` and `PostgreSQLResultRow` as the return value of a query. - Returned lists are protected with `UnmodifiableListView`. - Exposing column metadata through `ColumnDescription`. + - row-level `toTableColumnMap` and `toColumnMap` - `PostgreSQLConnection` and `_TransactionProxy` share the OID cache. - default value for `query(allowReuse = true)` is set only in the implementation method. diff --git a/lib/src/connection.dart b/lib/src/connection.dart index 7ec2b42..386559c 100644 --- a/lib/src/connection.dart +++ b/lib/src/connection.dart @@ -434,11 +434,12 @@ abstract class _PostgreSQLExecutionContextMixin columnDescriptions = await _connection._oidCache ._resolveTableNames(this, columnDescriptions); } + final metaData = _PostgreSQLResultMetaData(columnDescriptions); return _PostgreSQLResult( - columnDescriptions, + metaData, rows - .map((columns) => _PostgreSQLResultRow(columnDescriptions, columns)) + .map((columns) => _PostgreSQLResultRow(metaData, columns)) .toList()); } @@ -454,7 +455,7 @@ abstract class _PostgreSQLExecutionContextMixin allowReuse: allowReuse, timeoutInSeconds: timeoutInSeconds, ); - return _mapifyRows(rs, rs.columnDescriptions); + return rs.map((row) => row.toTableColumnMap()).toList(); } @override @@ -476,21 +477,6 @@ abstract class _PostgreSQLExecutionContextMixin @override void cancelTransaction({String reason}); - List>> _mapifyRows( - List> rows, List columns) { - return rows.map((row) { - final rowMap = >{}; - columns.forEach((c) { - rowMap.putIfAbsent(c.tableName, () => {}); - }); - for (int i = 0; i < columns.length; i++) { - final col = columns[i]; - rowMap[col.tableName][col.columnName] = row[i]; - } - return rowMap; - }).toList(); - } - Future _enqueue(Query query, {int timeoutInSeconds = 30}) async { if (_queue.add(query)) { _connection._transitionToState(_connection._connectionState.awake()); @@ -518,18 +504,61 @@ abstract class _PostgreSQLExecutionContextMixin Future _onQueryError(Query query, dynamic error, [StackTrace trace]) async {} } +class _PostgreSQLResultMetaData { + final List columnDescriptions; + List _tableNames; + + _PostgreSQLResultMetaData(this.columnDescriptions); + + List get tableNames { + _tableNames ??= + columnDescriptions.map((column) => column.tableName).toSet().toList(); + return _tableNames; + } +} + class _PostgreSQLResult extends UnmodifiableListView implements PostgreSQLResult { - @override - final List columnDescriptions; + final _PostgreSQLResultMetaData _metaData; - _PostgreSQLResult(this.columnDescriptions, List rows) + _PostgreSQLResult(this._metaData, List rows) : super(rows); + + @override + List get columnDescriptions => + _metaData.columnDescriptions; } class _PostgreSQLResultRow extends UnmodifiableListView implements PostgreSQLResultRow { - final List columnDescriptions; + final _PostgreSQLResultMetaData _metaData; + + _PostgreSQLResultRow(this._metaData, List columns) : super(columns); + + @override + List get columnDescriptions => + _metaData.columnDescriptions; + + @override + Map> toTableColumnMap() { + final rowMap = >{}; + _metaData.tableNames.forEach((tableName) { + rowMap[tableName] = {}; + }); + for (int i = 0; i < _metaData.columnDescriptions.length; i++) { + final col = _metaData.columnDescriptions[i]; + rowMap[col.tableName][col.columnName] = this[i]; + } + return rowMap; + } - _PostgreSQLResultRow(this.columnDescriptions, List columns) : super(columns); + @override + Map toColumnMap() { + final rowMap = {}; + for (int i = 0; i < _metaData.columnDescriptions.length; i++) { + final col = _metaData.columnDescriptions[i]; + rowMap[col.columnName] = this[i]; + } + return rowMap; + } } diff --git a/lib/src/execution_context.dart b/lib/src/execution_context.dart index 40e76fb..2a62f79 100644 --- a/lib/src/execution_context.dart +++ b/lib/src/execution_context.dart @@ -99,7 +99,17 @@ abstract class ColumnDescription { /// A single row of a query result. /// /// Column values can be accessed through the [] [List] accessor. -abstract class PostgreSQLResultRow implements List {} +abstract class PostgreSQLResultRow implements List { + List get columnDescriptions; + + /// Returns a two-level map that on the first level contains the resolved + /// table name, and on the second level the column name (or its alias). + Map> toTableColumnMap(); + + /// Returns a single-level map that maps the column name (or its alias) to the + /// value returned on that position. + Map toColumnMap(); +} /// The query result. /// From 33f9e2c14fc17a677dc81e4a3e2a91d492236eac Mon Sep 17 00:00:00 2001 From: Istvan Soos Date: Wed, 3 Jul 2019 22:50:05 +0200 Subject: [PATCH 18/20] Updated Changelog version --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 23ddc99..4a84872 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## 2.0.0-dev1 +## 2.0.0-dev1.0 - Restricted field access on [PostgreSQLConnection]. - Connection-level default query timeout. From 1e626e5d8f61d5f3e476aec26d37c9d2b876ef33 Mon Sep 17 00:00:00 2001 From: Istvan Soos Date: Fri, 2 Aug 2019 23:22:00 +0200 Subject: [PATCH 19/20] Deleting ci/script.sh --- .travis.yml | 1 - ci/script.sh | 9 --------- 2 files changed, 10 deletions(-) delete mode 100644 ci/script.sh diff --git a/.travis.yml b/.travis.yml index f2e6fdb..9d14a9a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,7 +16,6 @@ before_script: - psql -c 'create user darttrust with createdb;' -U postgres - psql -c 'grant all on database dart_test to darttrust;' -U postgres - pub get -#script: bash ci/script.sh dart_task: - test: --run-skipped -r expanded -j 1 - dartfmt diff --git a/ci/script.sh b/ci/script.sh deleted file mode 100644 index 356e7dc..0000000 --- a/ci/script.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash -set -e - -pub run test -j 1 -r expanded - -# if [[ "$TRAVIS_BRANCH" == "master" ]]; then -# pub global activate -sgit https://github.com/stablekernel/codecov_dart.git -# dart_codecov_generator --report-on=lib/ --verbose --no-html -# fi From aa9807f1f2a74bcb323027d7eceb1a2fb23fd308 Mon Sep 17 00:00:00 2001 From: Istvan Soos Date: Wed, 30 Oct 2019 18:10:20 +0100 Subject: [PATCH 20/20] Remove unnecessary const. --- lib/src/constants.dart | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/src/constants.dart b/lib/src/constants.dart index 4d7d872..2e1c0ae 100644 --- a/lib/src/constants.dart +++ b/lib/src/constants.dart @@ -1,7 +1,7 @@ class UTF8ByteConstants { - static const user = const [117, 115, 101, 114, 0]; - static const database = const [100, 97, 116, 97, 98, 97, 115, 101, 0]; - static const clientEncoding = const [ + static const user = [117, 115, 101, 114, 0]; + static const database = [100, 97, 116, 97, 98, 97, 115, 101, 0]; + static const clientEncoding = [ 99, 108, 105, @@ -19,6 +19,6 @@ class UTF8ByteConstants { 103, 0 ]; - static const utf8 = const [85, 84, 70, 56, 0]; - static const timeZone = const [84, 105, 109, 101, 90, 111, 110, 101, 0]; + static const utf8 = [85, 84, 70, 56, 0]; + static const timeZone = [84, 105, 109, 101, 90, 111, 110, 101, 0]; }