diff --git a/.travis.yml b/.travis.yml index 172431b..9d14a9a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,7 +16,11 @@ 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 + - dartanalyzer: --fatal-infos --fatal-warnings . + #after_success: bash ci/after_script.sh branches: only: diff --git a/CHANGELOG.md b/CHANGELOG.md index e5f968d..4a84872 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,23 @@ # Changelog -## 1.0.3 +## 2.0.0-dev1.0 - 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. +- `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. + +**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/analysis_options.yaml b/analysis_options.yaml index 4205311..ab0596b 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -3,18 +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: true -# 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/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 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 cc4c2eb..89c75ce 100644 --- a/lib/src/binary_codec.dart +++ b/lib/src/binary_codec.dart @@ -1,14 +1,37 @@ 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'; + +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 { - const PostgresBinaryEncoder(this.dataType); + final PostgreSQLDataType _dataType; - final PostgreSQLDataType dataType; + const PostgresBinaryEncoder(this._dataType); @override Uint8List convert(dynamic value) { @@ -16,154 +39,143 @@ class PostgresBinaryEncoder extends Converter { return null; } - switch (dataType) { + 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) { + return value ? _bool1 : _bool0; } - - 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); - outBuffer[0] = 1; - for (var i = 0; i < jsonBytes.length; i++) { - outBuffer[i + 1] = jsonBytes[i]; - } - - return outBuffer; + final jsonBytes = utf8.encode(json.encode(value)); + final writer = ByteDataWriter(bufferLength: jsonBytes.length + 1); + writer.writeUint8(1); + writer.write(jsonBytes); + return writer.toBytes(); } 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 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( + throw FormatException( "Invalid UUID string. There must be exactly 32 hexadecimal (0-9 and a-f) characters and any number of '-' characters."); } @@ -174,21 +186,22 @@ 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); - for (var i = 0; i < hexBytes.length; i += 2) { + final outBuffer = Uint8List(16); + 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; } } - throw new PostgreSQLException("Unsupported datatype"); + throw PostgreSQLException('Unsupported datatype'); } } @@ -205,12 +218,13 @@ 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: 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: @@ -227,41 +241,37 @@ 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: { // 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; 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 = new StringBuffer(); + 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); } } diff --git a/lib/src/client_messages.dart b/lib/src/client_messages.dart index 85b852f..03910c5 100644 --- a/lib/src/client_messages.dart +++ b/lib/src/client_messages.dart @@ -1,9 +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; @@ -18,227 +21,158 @@ abstract class ClientMessage { static const int SyncIdentifier = 83; static const int PasswordIdentifier = 112; - 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; - } - - int applyBytesToBuffer(List bytes, ByteData buffer, int offset) { - var postStringOffset = bytes.fold(offset, (idx, unit) { - buffer.setInt8(idx, unit); - return idx + 1; - }); - - return postStringOffset; - } - - int applyToBuffer(ByteData aggregateBuffer, int offsetIntoAggregateBuffer); + void applyToBuffer(ByteDataWriter buffer); Uint8List asBytes() { - var buffer = new ByteData(length); - applyToBuffer(buffer, 0); - return buffer.buffer.asUint8List(); + final buffer = 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(); + final buffer = ByteDataWriter(); + messages.forEach((cm) => cm.applyToBuffer(buffer)); + return buffer.toBytes(); } } -class StartupMessage extends ClientMessage { - StartupMessage(String databaseName, String timeZone, {String username}) { - this.databaseName = new UTF8BackedString(databaseName); - this.timeZone = new UTF8BackedString(timeZone); - if (username != null) { - this.username = new UTF8BackedString(username); - } - } - - UTF8BackedString username = null; - UTF8BackedString databaseName; - UTF8BackedString timeZone; - - ByteData buffer; +void _applyStringToBuffer(UTF8BackedString string, ByteDataWriter buffer) { + buffer.write(string.utf8Bytes); + buffer.writeInt8(0); +} - int get length { - var fixedLength = 53; - var variableLength = (username?.utf8Length ?? 0) + - databaseName.utf8Length + - timeZone.utf8Length + +class StartupMessage extends ClientMessage { + final UTF8BackedString _username; + final UTF8BackedString _databaseName; + final UTF8BackedString _timeZone; + + StartupMessage(String databaseName, String timeZone, {String username}) + : _databaseName = UTF8BackedString(databaseName), + _timeZone = UTF8BackedString(timeZone), + _username = username == null ? null : UTF8BackedString(username); + + @override + void applyToBuffer(ByteDataWriter buffer) { + final fixedLength = 53; + final variableLength = (_username?.utf8Length ?? 0) + + _databaseName.utf8Length + + _timeZone.utf8Length + 3; - return fixedLength + variableLength; - } - - int applyToBuffer(ByteData buffer, int offset) { - buffer.setInt32(offset, length); - offset += 4; - buffer.setInt32(offset, ClientMessage.ProtocolVersion); - offset += 4; + buffer.writeInt32(fixedLength + variableLength); + buffer.writeInt32(ClientMessage.ProtocolVersion); - if (username != null) { - offset = applyBytesToBuffer((UTF8ByteConstants.user), buffer, offset); - offset = applyStringToBuffer(username, buffer, offset); + if (_username != null) { + buffer.write(UTF8ByteConstants.user); + _applyStringToBuffer(_username, buffer); } - offset = applyBytesToBuffer(UTF8ByteConstants.database, buffer, offset); - offset = applyStringToBuffer(databaseName, buffer, offset); - - offset = - applyBytesToBuffer(UTF8ByteConstants.clientEncoding, buffer, offset); - offset = applyBytesToBuffer(UTF8ByteConstants.utf8, buffer, offset); + buffer.write(UTF8ByteConstants.database); + _applyStringToBuffer(_databaseName, buffer); - offset = applyBytesToBuffer(UTF8ByteConstants.timeZone, buffer, offset); - offset = applyStringToBuffer(timeZone, buffer, offset); + buffer.write(UTF8ByteConstants.clientEncoding); + buffer.write(UTF8ByteConstants.utf8); - buffer.setInt8(offset, 0); - offset += 1; + buffer.write(UTF8ByteConstants.timeZone); + _applyStringToBuffer(_timeZone, buffer); - return offset; + buffer.writeInt8(0); } } 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()); - } + UTF8BackedString _hashedAuthString; - UTF8BackedString hashedAuthString; - - int get length { - return 6 + hashedAuthString.utf8Length; + 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'); } - 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; + @override + void applyToBuffer(ByteDataWriter buffer) { + buffer.writeUint8(ClientMessage.PasswordIdentifier); + final length = 5 + _hashedAuthString.utf8Length; + buffer.writeUint32(length); + _applyStringToBuffer(_hashedAuthString, buffer); } } class QueryMessage extends ClientMessage { - QueryMessage(String queryString) { - this.queryString = new UTF8BackedString(queryString); - } + final UTF8BackedString _queryString; - UTF8BackedString queryString; + QueryMessage(String queryString) + : _queryString = UTF8BackedString(queryString); - int get length { - 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; + @override + void applyToBuffer(ByteDataWriter buffer) { + buffer.writeUint8(ClientMessage.QueryIdentifier); + final length = 5 + _queryString.utf8Length; + buffer.writeUint32(length); + _applyStringToBuffer(_queryString, buffer); } } class ParseMessage extends ClientMessage { - ParseMessage(String statement, {String statementName: ""}) { - this.statement = new UTF8BackedString(statement); - this.statementName = new UTF8BackedString(statementName); - } - - UTF8BackedString statementName; - UTF8BackedString statement; - - int get length { - 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; + final UTF8BackedString _statementName; + final UTF8BackedString _statement; + + ParseMessage(String statement, {String statementName = ''}) + : _statement = UTF8BackedString(statement), + _statementName = UTF8BackedString(statementName); + + @override + void applyToBuffer(ByteDataWriter buffer) { + buffer.writeUint8(ClientMessage.ParseIdentifier); + final length = 8 + _statement.utf8Length + _statementName.utf8Length; + buffer.writeUint32(length); // 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); } } class DescribeMessage extends ClientMessage { - DescribeMessage({String statementName: ""}) { - this.statementName = new UTF8BackedString(statementName); - } + final UTF8BackedString _statementName; - UTF8BackedString statementName; + DescribeMessage({String statementName = ''}) + : _statementName = UTF8BackedString(statementName); - int get length { - 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; + @override + void applyToBuffer(ByteDataWriter buffer) { + buffer.writeUint8(ClientMessage.DescribeIdentifier); + final length = 6 + _statementName.utf8Length; + buffer.writeUint32(length); + buffer.writeUint8(83); + _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 = new 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); + 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) { + _cachedLength += + _parameters.fold(0, (len, ParameterValue paramValue) { if (paramValue.bytes == null) { return len + 4; } else { @@ -249,102 +183,66 @@ 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; + @override + void applyToBuffer(ByteDataWriter buffer) { + buffer.writeUint8(ClientMessage.BindIdentifier); + buffer.writeUint32(length - 1); // Name of portal - currently unnamed portal. - offset = applyBytesToBuffer([0], buffer, offset); + buffer.writeUint8(0); // 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); + if (_typeSpecCount == _parameters.length) { + 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 - } else if (typeSpecCount == 0) { - buffer.setUint16(offset, 1); + buffer.writeUint16(ClientMessage.FormatBinary); + } else if (_typeSpecCount == 0) { + 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; - parameters.forEach((p) { - buffer.setUint16(offset, + buffer.writeUint16(_parameters.length); + _parameters.forEach((p) { + 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 - parameters.forEach((p) { + 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); } } class ExecuteMessage extends ClientMessage { - ExecuteMessage(); - - int get length { - 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; + @override + void applyToBuffer(ByteDataWriter buffer) { + buffer.writeUint8(ClientMessage.ExecuteIdentifier); + buffer.writeUint32(9); + buffer.writeUint8(0); // Portal name + buffer.writeUint32(0); } } class SyncMessage extends ClientMessage { - SyncMessage(); - - int get length { - return 5; - } - - int applyToBuffer(ByteData buffer, int offset) { - buffer.setUint8(offset, ClientMessage.SyncIdentifier); - offset += 1; - buffer.setUint32(offset, 4); - offset += 4; - return offset; + @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 d6d93c6..386559c 100644 --- a/lib/src/connection.dart +++ b/lib/src/connection.dart @@ -1,18 +1,19 @@ library postgres.connection; import 'dart:async'; +import 'dart:collection'; 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'; @@ -24,7 +25,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 +39,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}) { - _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(); + final StreamController _notifications = + StreamController.broadcast(); /// Hostname of database this connection refers to. final String host; @@ -84,7 +93,7 @@ class PostgreSQLConnection extends Object with _PostgreSQLExecutionContextMixin /// 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; @@ -94,18 +103,22 @@ class PostgreSQLConnection extends Object with _PostgreSQLExecutionContextMixin /// Prior to connection, it is the empty map. final Map settings = {}; - QueryCache _cache = new QueryCache(); + final _cache = QueryCache(); + final _oidCache = _OidCache(); Socket _socket; - MessageFramer _framer = new MessageFramer(); + MessageFramer _framer = MessageFramer(); int _processID; + // ignore: unused_field int _secretKey; List _salt; bool _hasConnectedPreviously = false; _PostgreSQLConnectionState _connectionState; + @override PostgreSQLExecutionContext get _transaction => null; + @override PostgreSQLConnection get _connection => this; /// Establishes a connection with a PostgreSQL database. @@ -118,26 +131,31 @@ 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 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)); + _socket = await Socket.connect(host, port) + .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)); + _transitionToState( + _PostgreSQLConnectionStateSocketConnected(connectionComplete)); - await connectionComplete.future.timeout(new Duration(seconds: timeoutInSeconds)); + await connectionComplete.future + .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) { @@ -180,19 +198,22 @@ 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 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); + await _enqueue(proxy._beginQuery); - return await proxy.completer.future; + return await proxy.future; } - void cancelTransaction({String reason: null}) { + @override + void cancelTransaction({String reason}) { // Default is no-op } @@ -213,7 +234,7 @@ class PostgreSQLConnection extends Object with _PostgreSQLExecutionContextMixin } Future _close([dynamic error, StackTrace trace]) async { - _connectionState = new _PostgreSQLConnectionStateClosed(); + _connectionState = _PostgreSQLConnectionStateClosed(); await _socket?.close(); await _notifications?.close(); @@ -227,14 +248,15 @@ class PostgreSQLConnection extends Object with _PostgreSQLExecutionContextMixin // 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(); try { 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(Notification(msg.processID, msg.channel, msg.payload)); } else { _transitionToState(_connectionState.onMessage(msg)); } @@ -244,38 +266,39 @@ class PostgreSQLConnection extends Object with _PostgreSQLExecutionContextMixin } } - 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)).then((responseByte) { + return sslCompleter.future + .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)); + return SecureSocket.secure(originalSocket, + onBadCertificate: (certificate) => true) + .timeout(Duration(seconds: timeout)); }); } } @@ -303,115 +326,164 @@ class Notification { final String payload; } -abstract class _PostgreSQLExecutionContextMixin implements PostgreSQLExecutionContext { - Map _tableOIDNameMap = {}; - QueryQueue _queue = new QueryQueue(); +class _OidCache { + final _tableOIDNameMap = {}; + int _queryCount = 0; - PostgreSQLConnection get _connection; + int get queryCount => _queryCount; - PostgreSQLExecutionContext get _transaction; + void clear() { + _queryCount = 0; + _tableOIDNameMap.clear(); + } - int get queueSize => _queue.length; + Future> _resolveTableNames( + _PostgreSQLExecutionContextMixin c, + List columns) async { + 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 + final unresolvedTableOIDs = columns + .map((f) => f.tableID) + .toSet() + .where((oid) => + oid != null && oid > 0 && !_tableOIDNameMap.containsKey(oid)) + .toList() + ..sort(); - Future>> query(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."); + if (unresolvedTableOIDs.isNotEmpty) { + await _resolveTableOIDs(c, unresolvedTableOIDs); } - var query = new Query>>(fmtString, substitutionValues, _connection, _transaction); - if (allowReuse) { - query.statementIdentifier = _connection._cache.identifierForQuery(query); - } + return columns + .map((c) => c.change(tableName: _tableOIDNameMap[c.tableID])) + .toList(); + } - return _enqueue(query, timeoutInSeconds: timeoutInSeconds); + 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", + allowReuse: false, // inlined OIDs would make it difficult anyway + resolveOids: false, + ); + + final iterator = oids.iterator; + orderedTableNames.forEach((tableName) { + iterator.moveNext(); + if (tableName.first != null) { + _tableOIDNameMap[iterator.current] = tableName.first as String; + } + }); } +} - Future>>> mappedResultsQuery(String fmtString, - {Map substitutionValues: null, bool allowReuse: true, int timeoutInSeconds}) async { +abstract class _PostgreSQLExecutionContextMixin + implements PostgreSQLExecutionContext { + final _queue = QueryQueue(); + + PostgreSQLConnection get _connection; + + PostgreSQLExecutionContext get _transaction; + + @override + int get queueSize => _queue.length; + + @override + 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 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); if (allowReuse) { query.statementIdentifier = _connection._cache.identifierForQuery(query); } final rows = await _enqueue(query, timeoutInSeconds: timeoutInSeconds); + List columnDescriptions = query.fieldDescriptions; + if (resolveOids) { + columnDescriptions = await _connection._oidCache + ._resolveTableNames(this, columnDescriptions); + } + final metaData = _PostgreSQLResultMetaData(columnDescriptions); - return _mapifyRows(rows, query.fieldDescriptions); + return _PostgreSQLResult( + metaData, + rows + .map((columns) => _PostgreSQLResultRow(metaData, columns)) + .toList()); } - Future execute(String fmtString, {Map substitutionValues: null, int timeoutInSeconds}) { + @override + Future>>> mappedResultsQuery( + String fmtString, + {Map substitutionValues, + bool allowReuse, + int timeoutInSeconds}) async { + final rs = await query( + fmtString, + substitutionValues: substitutionValues, + allowReuse: allowReuse, + timeoutInSeconds: timeoutInSeconds, + ); + return rs.map((row) => row.toTableColumnMap()).toList(); + } + + @override + Future execute(String fmtString, + {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) - ..onlyReturnAffectedRowCount = true; + 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 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); - } - - columns.forEach((desc) { - desc.resolvedTableName = _tableOIDNameMap[desc.tableID]; - }); - - final tableNames = tableOIDs.map((oid) => _tableOIDNameMap[oid]).toList(); - return rows.map((row) { - 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; - }); - - return rowMap; - }).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; - } - }); - } - - 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)); + final result = + await query.future.timeout(Duration(seconds: timeoutInSeconds)); _connection._cache.add(query); _queue.remove(query); return result; @@ -425,9 +497,68 @@ abstract class _PostgreSQLExecutionContextMixin implements PostgreSQLExecutionCo // 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); } } 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 { + final _PostgreSQLResultMetaData _metaData; + + _PostgreSQLResult(this._metaData, List rows) + : super(rows); + + @override + List get columnDescriptions => + _metaData.columnDescriptions; +} + +class _PostgreSQLResultRow extends UnmodifiableListView + implements PostgreSQLResultRow { + 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; + } + + @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/connection_fsm.dart b/lib/src/connection_fsm.dart index 8d04550..3dbb2b4 100644 --- a/lib/src/connection_fsm.dart +++ b/lib/src/connection_fsm.dart @@ -16,10 +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(); + if (exception.severity == PostgreSQLSeverity.fatal || + exception.severity == PostgreSQLSeverity.panic) { + return _PostgreSQLConnectionStateClosed(); } return this; @@ -38,44 +39,49 @@ 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; + @override _PostgreSQLConnectionState onEnter() { - var startupMessage = - new StartupMessage(connection.databaseName, connection.timeZone, username: connection.username); + final startupMessage = StartupMessage( + connection.databaseName, connection.timeZone, + username: connection.username); connection._socket.add(startupMessage.asBytes()); 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(); } } @@ -83,27 +89,32 @@ class _PostgreSQLConnectionStateSocketConnected extends _PostgreSQLConnectionSta Authenticating state */ -class _PostgreSQLConnectionStateAuthenticating extends _PostgreSQLConnectionState { +class _PostgreSQLConnectionStateAuthenticating + extends _PostgreSQLConnectionState { _PostgreSQLConnectionStateAuthenticating(this.completer); Completer completer; + @override _PostgreSQLConnectionState onEnter() { - var authMessage = new AuthMD5Message(connection.username, connection.password, connection._salt); + final authMessage = AuthMD5Message( + connection.username, connection.password, connection._salt); connection._socket.add(authMessage.asBytes()); 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; @@ -112,7 +123,7 @@ class _PostgreSQLConnectionStateAuthenticating extends _PostgreSQLConnectionStat connection._secretKey = message.secretKey; } else if (message is ReadyForQueryMessage) { if (message.state == ReadyForQueryMessage.StateIdle) { - return new _PostgreSQLConnectionStateIdle(openCompleter: completer); + return _PostgreSQLConnectionStateIdle(openCompleter: completer); } } @@ -124,19 +135,22 @@ class _PostgreSQLConnectionStateAuthenticating extends _PostgreSQLConnectionStat Authenticated state */ -class _PostgreSQLConnectionStateAuthenticated extends _PostgreSQLConnectionState { +class _PostgreSQLConnectionStateAuthenticated + extends _PostgreSQLConnectionState { _PostgreSQLConnectionStateAuthenticated(this.completer); 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; @@ -145,7 +159,7 @@ class _PostgreSQLConnectionStateAuthenticated extends _PostgreSQLConnectionState connection._secretKey = message.secretKey; } else if (message is ReadyForQueryMessage) { if (message.state == ReadyForQueryMessage.StateIdle) { - return new _PostgreSQLConnectionStateIdle(openCompleter: completer); + return _PostgreSQLConnectionStateIdle(openCompleter: completer); } } @@ -162,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); } @@ -175,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; } @@ -211,33 +228,37 @@ 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(); + if (exception.severity == PostgreSQLSeverity.fatal || + exception.severity == PostgreSQLSeverity.panic) { + 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. - // 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 _PostgreSQLConnectionStateReadyInTransaction( + query.transaction as _TransactionProxy); } if (returningException != null) { @@ -247,10 +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) { @@ -258,7 +280,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); + final validationException = + query.validateParameters(message.parameterTypeIDs); if (validationException != null) { query.cache = null; } @@ -271,17 +294,20 @@ class _PostgreSQLConnectionStateBusy extends _PostgreSQLConnectionState { /* Idle Transaction State */ -class _PostgreSQLConnectionStateReadyInTransaction extends _PostgreSQLConnectionState { +class _PostgreSQLConnectionStateReadyInTransaction + extends _PostgreSQLConnectionState { _PostgreSQLConnectionStateReadyInTransaction(this.transaction); _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); } @@ -293,13 +319,13 @@ class _PostgreSQLConnectionStateReadyInTransaction extends _PostgreSQLConnection 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); @@ -314,4 +340,5 @@ class _PostgreSQLConnectionStateReadyInTransaction extends _PostgreSQLConnection Hack for deferred error */ -class _PostgreSQLConnectionStateDeferredFailure extends _PostgreSQLConnectionState {} +class _PostgreSQLConnectionStateDeferredFailure + extends _PostgreSQLConnectionState {} diff --git a/lib/src/constants.dart b/lib/src/constants.dart index 1364c63..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]; } diff --git a/lib/src/exceptions.dart b/lib/src/exceptions.dart index 153905f..44e6a00 100644 --- a/lib/src/exceptions.dart +++ b/lib/src/exceptions.dart @@ -34,21 +34,18 @@ 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)); - 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; @@ -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 79c5066..2a62f79 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 @@ -28,8 +29,10 @@ 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, - {Map substitutionValues: null, bool allowReuse: true, int timeoutInSeconds}); + Future query(String fmtString, + {Map substitutionValues, + bool allowReuse, + int timeoutInSeconds}); /// Executes a query on this context. /// @@ -38,13 +41,14 @@ 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, 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]. /// @@ -76,6 +80,40 @@ 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, + bool allowReuse, + 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. +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. +/// +/// Rows can be accessed through the [] [List] accessor. +abstract class PostgreSQLResult implements List { + List get columnDescriptions; +} diff --git a/lib/src/message_window.dart b/lib/src/message_window.dart index e466d2e..899f5f5 100644 --- a/lib/src/message_window.dart +++ b/lib/src/message_window.dart @@ -1,158 +1,73 @@ import 'dart:collection'; -import 'dart:io'; import 'dart:typed_data'; -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() - }; - - int get bytesAvailable => packets.fold(0, (sum, v) => sum + v.lengthInBytes); - List packets = []; - bool get hasReadHeader => type != null; - int type; - int expectedLength; +import 'package:buffer/buffer.dart'; - bool get isComplete => data != null || expectedLength == 0; - Uint8List data; - - ByteData consumeNextBytes(int length) { - if (length == 0) { - return null; - } +import 'server_messages.dart'; - if (bytesAvailable >= length) { - var firstPacket = packets.first; +const int _headerByteSize = 5; +final _emptyData = Uint8List(0); + +typedef ServerMessage _ServerMessageFn(Uint8List data); + +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), +}; - // 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); - } +class MessageFramer { + final _reader = ByteDataReader(); + final messageQueue = Queue(); - 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); + int _type; + int _expectedLength; - return bytesNeeded; - } + bool get _hasReadHeader => _type != null; + bool get _canReadHeader => _reader.remainingLength >= _headerByteSize; - // 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. + bool get _isComplete => + _expectedLength == 0 || _expectedLength <= _reader.remainingLength; - var builder = new BytesBuilder(copy: false); - var bytesNeeded = length - builder.length; - while (bytesNeeded > 0) { - var packet = packets.removeAt(0); - var bytesRemaining = packet.lengthInBytes; + void addBytes(Uint8List bytes) { + _reader.add(bytes); - 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)); - } + bool evaluateNextMessage = true; + while (evaluateNextMessage) { + evaluateNextMessage = false; - bytesNeeded = length - builder.length; + if (!_hasReadHeader && _canReadHeader) { + _type = _reader.readUint8(); + _expectedLength = _reader.readUint32() - 4; } - 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; + 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; } - - 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; - - ServerMessage msg = msgMaker(); - msg.readBytes(data); - return msg; - } -} - -class MessageFramer { - MessageFrame messageInProgress = new MessageFrame(); - final messageQueue = new Queue(); - - void addBytes(Uint8List bytes) { - var offsetIntoBytesRead = 0; - - do { - var byteList = new Uint8List.view(bytes.buffer, offsetIntoBytesRead); - offsetIntoBytesRead += messageInProgress.addBytes(byteList); - - if (messageInProgress.isComplete) { - messageQueue.add(messageInProgress); - messageInProgress = new MessageFrame(); - } - } while (offsetIntoBytesRead != bytes.length); } 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 1d045f6..2c4fec7 100644 --- a/lib/src/query.dart +++ b/lib/src/query.dart @@ -3,19 +3,26 @@ 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, this.transaction); + Query( + this.statement, + this.substitutionValues, + this.connection, + this.transaction, { + this.onlyReturnAffectedRowCount = false, + }); - bool onlyReturnAffectedRowCount = false; + final bool onlyReturnAffectedRowCount; String statementIdentifier; @@ -26,29 +33,30 @@ class Query { final PostgreSQLExecutionContext transaction; final PostgreSQLConnection connection; - List specifiedParameterTypeCodes; - List> rows = []; + List _specifiedParameterTypeCodes; + final rows = >[]; 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); @@ -56,76 +64,85 @@ 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(); + _specifiedParameterTypeCodes = + formatIdentifiers.map((i) => i.type).toList(); - var parameterList = formatIdentifiers.map((id) => new ParameterValue(id, substitutionValues)).toList(); + 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)); } - void sendCachedQuery(Socket socket, CachedQuery cacheQuery, Map substitutionValues) { - var statementName = cacheQuery.preparedStatementName; - var parameterList = - cacheQuery.orderedParameters.map((identifier) => new ParameterValue(identifier, substitutionValues)).toList(); + void sendCachedQuery(Socket socket, CachedQuery cacheQuery, + Map 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 = specifiedParameterTypeCodes.map((specifiedType) { + final actualParameterTypeCodeIterator = parameterTypeIDs.iterator; + final 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); 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; } - void addRow(List rawRowData) { + void addRow(List rawRowData) { if (onlyReturnAffectedRowCount) { 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.convert(bd?.buffer?.asUint8List(bd.offsetInBytes, bd.lengthInBytes)); + return iterator.current.converter.convert(bd); }); rows.add(lazyDecodedData.toList()); @@ -152,100 +169,127 @@ class Query { _onComplete.completeError(error, stackTrace); } + @override String toString() => statement; } class CachedQuery { CachedQuery(this.preparedStatementName, this.orderedParameters); - String preparedStatementName; - List orderedParameters; + final String preparedStatementName; + final List orderedParameters; 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 ParameterValue.text(substitutionValues[identifier.name]); } - return new ParameterValue.binary(substitutionValues[identifier.name], identifier.type); + return ParameterValue.binary( + substitutionValues[identifier.name], identifier.type); } - ParameterValue.binary(dynamic value, PostgreSQLDataType postgresType) : isBinary = true { - final converter = new PostgresBinaryEncoder(postgresType); - bytes = converter.convert(value); - length = bytes?.length ?? 0; + factory ParameterValue.binary( + dynamic value, PostgreSQLDataType postgresType) { + final converter = PostgresBinaryEncoder(postgresType); + 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 = new PostgresTextEncoder(false); - bytes = utf8.encode(converter.convert(value)); + 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; - - String fieldName; - int tableID; - int columnID; - int typeID; - int dataTypeSize; - int typeModifier; - int formatCode; - - String resolvedTableName; - - int parse(ByteData byteData, int initialOffset) { - var offset = initialOffset; - var buf = new StringBuffer(); - var byte = 0; +class FieldDescription implements ColumnDescription { + final Converter converter; + + @override + final String columnName; + final int tableID; + final int columnID; + final int typeID; + final int dataTypeSize; + final int typeModifier; + final int formatCode; + + @override + final String tableName; + + FieldDescription._( + 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(); + 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; - - converter = new PostgresBinaryDecoder(typeID); + final fieldName = buf.toString(); + + final tableID = reader.readUint32(); + final columnID = reader.readUint16(); + final typeID = reader.readUint32(); + final dataTypeSize = reader.readUint16(); + final typeModifier = reader.readInt32(); + final formatCode = reader.readUint16(); + + final converter = PostgresBinaryDecoder(typeID); + return FieldDescription._( + converter, fieldName, tableID, columnID, typeID, + dataTypeSize, typeModifier, formatCode, + null, // tableName + ); + } - return offset; + 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'; } } -typedef String SQLReplaceIdentifierFunction(PostgreSQLFormatIdentifier identifier, int index); +typedef String SQLReplaceIdentifierFunction( + PostgreSQLFormatIdentifier identifier, int index); enum PostgreSQLFormatTokenType { text, variable } @@ -253,57 +297,64 @@ 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("::"); + factory PostgreSQLFormatIdentifier(String t) { + String name; + PostgreSQLDataType type; + String typeCast; + + 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("Invalid type code in substitution variable '$t'"); + 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 @ 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/query_cache.dart b/lib/src/query_cache.dart index 7c83680..2acf5d9 100644 --- a/lib/src/query_cache.dart +++ b/lib/src/query_cache.dart @@ -1,8 +1,11 @@ -import 'package:postgres/src/query.dart'; +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,28 +13,28 @@ class QueryCache { } if (query.cache.isValid) { - queries[query.statement] = query.cache; + _queries[query.statement] = query.cache; } } - operator [](String statementId) { + CachedQuery operator [](String statementId) { if (statementId == null) { return null; } - return queries[statementId]; + return _queries[statementId]; } 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++; + _idCounter++; return string; } -} \ No newline at end of file +} diff --git a/lib/src/query_queue.dart b/lib/src/query_queue.dart index 2135f17..29514cb 100644 --- a/lib/src/query_queue.dart +++ b/lib/src/query_queue.dart @@ -1,14 +1,17 @@ import 'dart:async'; import 'dart:collection'; -import 'package:postgres/postgres.dart'; -import 'package:postgres/src/query.dart'; +import '../postgres.dart'; -class QueryQueue extends ListBase> implements List> { - List> _inner = []; +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) { @@ -21,7 +24,7 @@ class QueryQueue extends ListBase> implements List _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/server_messages.dart b/lib/src/server_messages.dart index b6e5668..9d48d49 100644 --- a/lib/src/server_messages.dart +++ b/lib/src/server_messages.dart @@ -1,27 +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 = [new ErrorField()]; - - void readBytes(Uint8List bytes) { - var lastByteRemovedList = new 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(new ErrorField()); - }); + } + if (identificationToken != null && sb != null) { + fields.add(ErrorField(identificationToken, sb.toString())); + } } } @@ -35,176 +46,174 @@ class AuthenticationMessage implements ServerMessage { static const int KindGSSContinue = 8; static const int KindSSPI = 9; - int type; - - List salt; + final int type; + final List salt; - void readBytes(Uint8List bytes) { - var view = new 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 = new 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; + + ParameterStatusMessage._(this.name, this.value); - 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))); + 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); } } 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; + final String state; - 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; - void readBytes(Uint8List bytes) { - var view = new ByteData.view(bytes.buffer, bytes.offsetInBytes); - processID = view.getUint32(0); - secretKey = view.getUint32(4); + BackendKeyMessage._(this.processID, this.secretKey); + + factory BackendKeyMessage(Uint8List bytes) { + final view = ByteData.view(bytes.buffer, bytes.offsetInBytes); + final processID = view.getUint32(0); + final secretKey = view.getUint32(4); + return BackendKeyMessage._(processID, secretKey); } } class RowDescriptionMessage extends ServerMessage { - List fieldDescriptions; + final fieldDescriptions = []; - void readBytes(Uint8List bytes) { - var view = new ByteData.view(bytes.buffer, bytes.offsetInBytes); - var offset = 0; - var fieldCount = view.getInt16(offset); - offset += 2; + RowDescriptionMessage(Uint8List bytes) { + final reader = ByteDataReader()..add(bytes); + final fieldCount = reader.readInt16(); - fieldDescriptions = []; for (var i = 0; i < fieldCount; i++) { - var rowDesc = new FieldDescription(); - offset = rowDesc.parse(view, offset); + final rowDesc = FieldDescription.read(reader); fieldDescriptions.add(rowDesc); } } } class DataRowMessage extends ServerMessage { - List values = []; + final values = []; - void readBytes(Uint8List bytes) { - var view = new ByteData.view(bytes.buffer, bytes.offsetInBytes); - var offset = 0; - var 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++) { - var dataSize = view.getInt32(offset); - offset += 4; + final dataSize = reader.readInt32(); if (dataSize == 0) { - values.add(new ByteData(0)); + values.add(Uint8List(0)); } else if (dataSize == -1) { values.add(null); } else { - var rawBytes = new ByteData.view(bytes.buffer, bytes.offsetInBytes + offset, dataSize); + final rawBytes = reader.read(dataSize); values.add(rawBytes); - offset += dataSize; } } } - String toString() => "Data Row Message: ${values}"; + @override + String toString() => 'Data Row Message: $values'; } class NotificationResponseMessage extends ServerMessage { - int processID; - String channel; - String payload; - - void readBytes(Uint8List bytes) { - 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))); + final int processID; + final String channel; + final String payload; + + NotificationResponseMessage._(this.processID, this.channel, this.payload); + + factory NotificationResponseMessage(Uint8List bytes) { + final view = ByteData.view(bytes.buffer, bytes.offsetInBytes); + 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 = new RegExp(r"[A-Z ]*"); + static RegExp identifierExpression = RegExp(r'[A-Z ]*'); - void readBytes(Uint8List bytes) { - var str = utf8.decode(bytes.sublist(0, bytes.length - 1)); + CommandCompleteMessage._(this.rowsAffected); - var match = identifierExpression.firstMatch(str); + 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; + rowsAffected = int.parse(str.split(' ').last); } + return CommandCompleteMessage._(rowsAffected); } } class ParseCompleteMessage extends ServerMessage { - void readBytes(Uint8List bytes) {} + ParseCompleteMessage(); - String toString() => "Parse Complete Message"; + @override + String toString() => 'Parse Complete Message'; } class BindCompleteMessage extends ServerMessage { - void readBytes(Uint8List bytes) {} + BindCompleteMessage(); - String toString() => "Bind Complete Message"; + @override + String toString() => 'Bind Complete Message'; } class ParameterDescriptionMessage extends ServerMessage { - List parameterTypeIDs; - - void readBytes(Uint8List bytes) { - var view = new ByteData.view(bytes.buffer, bytes.offsetInBytes); + final parameterTypeIDs = []; - var offset = 0; - var 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++) { - var v = view.getUint32(offset); - offset += 4; - parameterTypeIDs.add(v); + parameterTypeIDs.add(reader.readUint32()); } } } class NoDataMessage extends ServerMessage { - void readBytes(Uint8List bytes) {} + NoDataMessage(); - String toString() => "No Data Message"; + @override + String toString() => 'No Data Message'; } class UnknownMessage extends ServerMessage { - Uint8List bytes; - int code; + final int code; + final Uint8List bytes; - void readBytes(Uint8List bytes) { - this.bytes = bytes; - } + UnknownMessage(this.code, this.bytes); @override int get hashCode { @@ -252,37 +261,29 @@ 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; } return PostgreSQLSeverity.unknown; } - int identificationToken; - - String get text => _buffer.toString(); - StringBuffer _buffer = new 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/lib/src/substituter.dart b/lib/src/substituter.dart index 48b34aa..35ce77b 100644 --- a/lib/src/substituter.dart +++ b/lib/src/substituter.dart @@ -1,96 +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); - values ??= {}; + {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) { - currentPtr = new PostgreSQLFormatToken(PostgreSQLFormatTokenType.variable); + if (iterator.current == _atSignCodeUnit) { + currentPtr = + 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) { - currentPtr = new PostgreSQLFormatToken(PostgreSQLFormatTokenType.variable); + if (iterator.current == _atSignCodeUnit) { + currentPtr = + 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); } @@ -98,7 +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); } @@ -114,41 +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 844c15e..215f6a4 100644 --- a/lib/src/text_codec.dart +++ b/lib/src/text_codec.dart @@ -3,14 +3,14 @@ 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) { if (value == null) { - return "null"; + return 'null'; } if (value is int) { @@ -22,7 +22,7 @@ class PostgresTextEncoder extends Converter { } if (value is String) { - return encodeString(value, escapeStrings); + return encodeString(value, _escapeStrings); } if (value is DateTime) { @@ -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,36 +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); } @@ -148,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 e39ad52..294266d 100644 --- a/lib/src/transaction_proxy.dart +++ b/lib/src/transaction_proxy.dart @@ -1,25 +1,32 @@ part of postgres.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; - - beginQuery.future.then(startTransaction).catchError((err, st) { - new Future(() { - completer.completeError(err, st); +typedef Future _TransactionQuerySignature( + PostgreSQLExecutionContext connection); + +class _TransactionProxy extends Object + with _PostgreSQLExecutionContextMixin + implements PostgreSQLExecutionContext { + _TransactionProxy( + this._connection, this.executionBlock, this.commitTimeoutInSeconds) { + _beginQuery = Query('BEGIN', {}, _connection, this, + onlyReturnAffectedRowCount: true); + + _beginQuery.future.then(startTransaction).catchError((err, StackTrace st) { + Future(() { + _completer.completeError(err, st); }); }); } - Query beginQuery; - Completer completer = new Completer(); + Query _beginQuery; + final _completer = Completer(); - Future get future => completer.future; + Future get future => _completer.future; + @override final PostgreSQLConnection _connection; + @override PostgreSQLExecutionContext get _transaction => this; final _TransactionQuerySignature executionBlock; @@ -27,18 +34,19 @@ class _TransactionProxy extends Object with _PostgreSQLExecutionContextMixin imp 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); @@ -57,8 +65,8 @@ class _TransactionProxy extends Object with _PostgreSQLExecutionContextMixin imp } if (!_hasRolledBack && !_hasFailed) { - await execute("COMMIT", timeoutInSeconds: commitTimeoutInSeconds); - completer.complete(result); + await execute('COMMIT', timeoutInSeconds: commitTimeoutInSeconds); + _completer.complete(result); } } @@ -74,26 +82,27 @@ class _TransactionProxy extends Object with _PostgreSQLExecutionContextMixin imp 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)..onlyReturnAffectedRowCount = true; + 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); + _completer.completeError(object, trace); } } @@ -122,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/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/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 c1d9e09..63ddf08 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,16 +1,18 @@ 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: environment: - sdk: ">=2.0.0 <3.0.0" + sdk: ">=2.2.0 <3.0.0" dependencies: + 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 fef74bc..d95b7d0 100644 --- a/test/connection_test.dart +++ b/test/connection_test.dart @@ -1,145 +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((DeclarationMirror dm) => dm.simpleName.toString().contains("_socket")); - var underlyingSocket = reflect(conn).getField(socketMirror.simpleName).reflectee; + expect(await conn.execute('select 1'), equals(1)); + final socketMirror = reflect(conn).type.declarations.values.firstWhere( + (DeclarationMirror dm) => + 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", () async { - conn = new PostgreSQLConnection("localhost", 5432, "dart_test", username: "darttrust"); + test('Closing idle connection succeeds, closes underlying socket', + () async { + conn = 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; + final socketMirror = reflect(conn).type.declarations.values.firstWhere( + (DeclarationMirror dm) => + 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", () async { - conn = new PostgreSQLConnection("localhost", 5432, "dart_test", username: "darttrust", useSSL: true); + test('SSL Closing idle connection succeeds, closes underlying socket', + () async { + conn = 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; + final socketMirror = reflect(conn).type.declarations.values.firstWhere( + (DeclarationMirror dm) => + 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"))); + 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", + '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"))); + expect(errors.map((e) => e.message), + 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(); }); @@ -147,44 +160,48 @@ 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), + 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", () 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) + test( + 'Issuing multiple queries without awaiting are returned with appropriate values', + () async { + 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, [ [ @@ -206,8 +223,8 @@ void main() { }); }); - group("Unintended user-error situations", () { - PostgreSQLConnection conn = null; + group('Unintended user-error situations', () { + PostgreSQLConnection conn; Future openFuture; tearDown(() async { @@ -215,119 +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", () async { - conn = new PostgreSQLConnection("localhost", 5432, "dart_test", username: "darttrust"); + test('Starting transaction while opening connection triggers error', + () async { + 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", () async { - conn = new PostgreSQLConnection("localhost", 5432, "dart_test", username: "darttrust", useSSL: true); + test('SSL Starting transaction while opening connection triggers error', + () async { + 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", () async { - conn = new PostgreSQLConnection("localhost", 5432, "dart_test", username: "dart", password: "notdart"); + test('Invalid password reports error, conn is closed, disables conn', + () async { + 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", () 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 = 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", () async { - conn = new PostgreSQLConnection("localhost", 5432, "dart_test", username: "darttrust"); + test('A query error maintains connectivity, allows future queries', + () async { + 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", () async { - conn = new PostgreSQLConnection("localhost", 5432, "dart_test", username: "darttrust"); + test( + 'A query error maintains connectivity, continues processing pending queries', + () async { + 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, [ [ @@ -341,35 +372,37 @@ 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; + final queueMirror = reflect(conn).type.instanceMembers.values.firstWhere( + (DeclarationMirror dm) => + 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", () async { - conn = new PostgreSQLConnection("localhost", 5432, "dart_test", username: "darttrust"); + test( + 'A query error maintains connectivity, continues processing pending transactions', + () async { + 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); @@ -379,20 +412,23 @@ 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 = 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, [ [ @@ -403,180 +439,210 @@ 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; + final queueMirror = reflect(conn).type.instanceMembers.values.firstWhere( + (DeclarationMirror dm) => + 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(); await socket?.close(); }); - test("Socket fails to connect reports error, disables connection for future use", () async { - var conn = new PostgreSQLConnection("localhost", 5431, "dart_test"); + test( + 'Socket fails to connect reports error, disables connection for future use', + () async { + final conn = PostgreSQLConnection('localhost', 5431, 'dart_test'); try { await conn.open(); expect(true, false); - } on SocketException {} + } on SocketException { + // ignore + } 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 { + final conn = + PostgreSQLConnection('localhost', 5431, 'dart_test', useSSL: true); try { await conn.open(); expect(true, false); - } on SocketException {} + } on SocketException { + // ignore + } 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); + final conn = PostgreSQLConnection('localhost', 5433, 'dart_test', + timeoutInSeconds: 2); try { await conn.open(); fail('unreachable'); - } on TimeoutException {} + } on TimeoutException { + // ignore + } 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); + final conn = PostgreSQLConnection('localhost', 5433, 'dart_test', + timeoutInSeconds: 2, useSSL: true); try { await conn.open(); fail('unreachable'); - } on TimeoutException {} + } on TimeoutException { + // ignore + } await expectConnectionIsInvalid(conn); }); - test("Connection that times out triggers future for pending queries", () async { - var openCompleter = new Completer(); - serverSocket = await ServerSocket.bind(InternetAddress.loopbackIPv4, 5433); + test('Connection that times out triggers future for pending queries', + () async { + 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", timeoutInSeconds: 2); + 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", () async { - var openCompleter = new Completer(); - serverSocket = await ServerSocket.bind(InternetAddress.loopbackIPv4, 5433); + test('SSL Connection that times out triggers future for pending queries', + () async { + 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", timeoutInSeconds: 2, useSSL: true); - conn.open().catchError((e) { return null;}); + final conn = PostgreSQLConnection('localhost', 5433, 'dart_test', + timeoutInSeconds: 2, useSSL: true); + conn.open().catchError((e) { + return null; + }); 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 { await conn.open(); expect(true, false); - } on PostgreSQLException {} + } on PostgreSQLException { + // ignore + } }); }); - 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", () 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 = 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); @@ -587,16 +653,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 2da41c1..27096fb 100644 --- a/test/decode_test.dart +++ b/test/decode_test.dart @@ -4,41 +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, " + 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) " - "VALUES (2147483647, 9223372036854775807, FALSE, 32767, " + 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) " - "VALUES (null, null, null, null, null, null, null, null, null, null, null, null, null)"); + 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 { 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)); @@ -47,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)); @@ -66,22 +70,24 @@ 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); 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); @@ -101,29 +107,31 @@ void main() { expect(row3[14], isNull); }); - 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": ""}); + 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': ''}); 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)"); - var results = - await connection.query("INSERT INTO u (t) VALUES (@t:text) returning t", substitutionValues: {"t": null}); + 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}); 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 03367cf..877f1cf 100644 --- a/test/encoding_test.dart +++ b/test/encoding_test.dart @@ -12,9 +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(); }); @@ -27,303 +28,333 @@ 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), PostgreSQLDataType.timestampWithoutTimezone); - await expectInverse(new DateTime.utc(2120, 10, 5), PostgreSQLDataType.timestampWithoutTimezone); + test('timestamp', () async { + await expectInverse(DateTime.utc(1920, 10, 1), + PostgreSQLDataType.timestampWithoutTimezone); + 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([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 { - await expectInverse("00000000-0000-0000-0000-000000000000", PostgreSQLDataType.uuid); - await expectInverse("12345678-abcd-efab-cdef-012345678901", PostgreSQLDataType.uuid); + test('uuid', () async { + 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': 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])); + 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", () { + 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", () { - var u = new UTF8BackedString("abcd"); - var v = new UTF8BackedString("abcd"); + test('UTF8String caches string regardless of which method is called first', + () { + final u = UTF8BackedString('abcd'); + final v = UTF8BackedString('abcd'); u.utf8Length; v.utf8Bytes; @@ -332,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')); } }); } @@ -367,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)"); - final result = await conn.query("INSERT INTO t (v) VALUES (${PostgreSQLFormat.id("v", type: dataType)}) RETURNING v", substitutionValues: { - "v": value - }); + 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}); expect(result.first.first, equals(value)); - final encoder = new PostgresBinaryEncoder(dataType); + final encoder = PostgresBinaryEncoder(dataType); final encodedValue = encoder.convert(value); if (dataType == PostgreSQLDataType.serial) { @@ -381,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..79139e3 100644 --- a/test/framer_test.dart +++ b/test/framer_test.dart @@ -1,69 +1,64 @@ +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.toList(); expect(messages, [ - new UnknownMessage() - ..code = 1 - ..bytes = new Uint8List.fromList([1, 2, 3]) + UnknownMessage(1, 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.toList(); expect(messages, [ - new UnknownMessage() - ..code = 1 - ..bytes = new Uint8List.fromList([1, 2, 3]), - new UnknownMessage() - ..code = 2 - ..bytes = new Uint8List.fromList([1, 2, 3, 4]) + UnknownMessage(1, Uint8List.fromList([1, 2, 3])), + UnknownMessage(2, 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.toList(); expect(messages, [ - new UnknownMessage() - ..code = 1 - ..bytes = new Uint8List.fromList([1, 2, 3]) + UnknownMessage(1, 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 +68,16 @@ void main() { framer.addBytes(fragments.last); - var messages = framer.messageQueue.map((f) => f.message).toList(); + final messages = framer.messageQueue.toList(); expect(messages, [ - new UnknownMessage() - ..code = 1 - ..bytes = new Uint8List.fromList([1, 2, 3]) + UnknownMessage(1, 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 +85,17 @@ void main() { framer.addBytes(message2Fragments.last); - var messages = framer.messageQueue.map((f) => f.message).toList(); + final messages = framer.messageQueue.toList(); expect(messages, [ - new UnknownMessage() - ..code = 1 - ..bytes = new Uint8List.fromList([1, 2, 3]), - new UnknownMessage() - ..code = 2 - ..bytes = new Uint8List.fromList([2, 2, 3]), + UnknownMessage(1, Uint8List.fromList([1, 2, 3])), + UnknownMessage(2, 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 +103,32 @@ void main() { framer.addBytes(message2Fragments.last); - var messages = framer.messageQueue.map((f) => f.message).toList(); + final messages = framer.messageQueue.toList(); expect(messages, [ - new UnknownMessage() - ..code = 1 - ..bytes = new Uint8List.fromList([1, 2, 3]), - new UnknownMessage() - ..code = 2 - ..bytes = new Uint8List.fromList([2, 2, 3]), + UnknownMessage(1, Uint8List.fromList([1, 2, 3])), + UnknownMessage(2, 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.toList(); expect(messages, [ - new UnknownMessage() - ..code = 1 - ..bytes = new Uint8List.fromList([1, 2, 3, 4, 5, 6, 7]) + UnknownMessage(1, 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 +136,16 @@ void main() { framer.addBytes(fragmentedMessageBuffer(message, 8).last); - var messages = framer.messageQueue.map((f) => f.message).toList(); + final messages = framer.messageQueue.toList(); expect(messages, [ - new UnknownMessage() - ..code = 0 - ..bytes = new Uint8List.fromList([1, 2]), - new UnknownMessage() - ..code = 1 - ..bytes = new 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])) ]); }); - 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 +161,40 @@ void main() { fragmentedMessageBuffer(fragmentedMessageBuffer(message, 3).last, 6) .last); - var messages = framer.messageQueue.map((f) => f.message).toList(); + final messages = framer.messageQueue.toList(); expect(messages, [ - new UnknownMessage() - ..code = 0 - ..bytes = new Uint8List.fromList([1, 2]), - new UnknownMessage() - ..code = 1 - ..bytes = - new 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", () { + 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.toList(); + expect(messages, [UnknownMessage(10, Uint8List(0))]); }); } 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 +203,8 @@ flush(MessageFramer framer) { messageWithBytes([1, 2, 3], 1) ])); - var messages = framer.messageQueue.map((f) => f.message).toList(); + final messages = framer.messageQueue.toList(); expect(messages, [ - new UnknownMessage() - ..code = 1 - ..bytes = new Uint8List.fromList([1, 2, 3]) + UnknownMessage(1, Uint8List.fromList([1, 2, 3])), ]); } diff --git a/test/interpolation_test.dart b/test/interpolation_test.dart index 6203950..5f57756 100644 --- a/test/interpolation_test.dart +++ b/test/interpolation_test.dart @@ -4,100 +4,105 @@ 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); + 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); expect(PostgreSQLFormatIdentifier.typeStringToCodeMap[code], t); }); }); - test("Ensure bigserial gets translated to int8", () { - expect(PostgreSQLFormat.dataTypeStringForDataType(PostgreSQLDataType.serial), "int4"); + test('Ensure bigserial gets translated to int8', () { + expect( + PostgreSQLFormat.dataTypeStringForDataType(PostgreSQLDataType.serial), + 'int4'); }); - test("Ensure serial gets translated to int4", () { - expect(PostgreSQLFormat.dataTypeStringForDataType(PostgreSQLDataType.bigSerial), "int8"); + test('Ensure serial gets translated to int4', () { + expect( + PostgreSQLFormat.dataTypeStringForDataType( + PostgreSQLDataType.bigSerial), + '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("SELECT * FROM t WHERE id=@id:int2 WHERE blob=@blob::jsonb AND blob='{\"a\":1}'::jsonb", { - "id": 2, - "blob": "{\"key\":\"value\"}" - }); + 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"}'}); - 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\"}" - }); + 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), [ @@ -125,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 eaa7587..cee5b27 100644 --- a/test/json_test.dart +++ b/test/json_test.dart @@ -5,81 +5,148 @@ 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 { - 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"]]); + 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'] + ]); + result = await connection.query('SELECT j FROM t'); + 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"]]); - result = await connection.query("SELECT j FROM t"); - 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'] + ]); + result = await connection.query('SELECT j FROM t'); + 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]]); - result = await connection.query("SELECT j FROM t"); - expect(result, [[4]]); + 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'); + 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]]); - result = await connection.query("SELECT j FROM t"); - 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] + ]); + result = await connection.query('SELECT j FROM t'); + 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}]]); - result = await connection.query("SELECT j FROM t"); - expect(result, [[{"a":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} + ] + ]); + result = await connection.query('SELECT j FROM t'); + 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}]]); - result = await connection.query("SELECT j FROM t"); - 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} + ] + ]); + result = await connection.query('SELECT j FROM t'); + 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}]]]); - result = await connection.query("SELECT j FROM t"); - 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} + ] + ] + ]); + result = await connection.query('SELECT j FROM t'); + 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}]]]); - result = await connection.query("SELECT j FROM t"); - 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} + ] + ] + ]); + result = await connection.query('SELECT j FROM t'); + expect(result, [ + [ + [ + {'a': 4} + ] + ] + ]); }); }); } diff --git a/test/map_return_test.dart b/test/map_return_test.dart index ad02f9b..b0135ed 100644 --- a/test/map_return_test.dart +++ b/test/map_return_test.dart @@ -1,140 +1,153 @@ -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 = new InterceptingConnection("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 (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')"); 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 { await connection?.close(); }); - test("Get row map without specifying columns", () async { - final results = await connection.mappedResultsQuery("SELECT * from t ORDER BY id ASC"); + test('Get row map without specifying columns', () async { + final results = + 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 { - final results = await connection.mappedResultsQuery("SELECT name, id from t ORDER BY id ASC"); + test('Get row map by with specified columns', () async { + final results = await connection + .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"); + final nextResults = await connection + .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("SELECT relname FROM pg_class WHERE relkind='r' AND oid IN \\(([0-9]*)\\) ORDER BY oid ASC"); - final oids = []; + test('Table names get cached', () async { + 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(); + await connection.mappedResultsQuery('SELECT id FROM t'); + expect(getOidQueryCount(connection), 1); - await connection.mappedResultsQuery("SELECT id FROM t"); - expect(connection.queries.length, 0); + await connection.mappedResultsQuery('SELECT id FROM t'); + 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(); + await connection.mappedResultsQuery( + 'SELECT t.id, u.id FROM t LEFT OUTER JOIN u ON t.id=u.t_id'); + expect(getOidQueryCount(connection), 2); - await connection.mappedResultsQuery("SELECT u.id FROM u"); - expect(connection.queries.length, 0); + await connection.mappedResultsQuery('SELECT u.id FROM u'); + expect(getOidQueryCount(connection), 2); }); - test("Non-table mappedResultsQuery succeeds", () async { - final result = await connection.mappedResultsQuery("SELECT 1"); - expect(result, [{null: {"?column?": 1}}]); + test('Non-table mappedResultsQuery succeeds', () async { + final result = await connection.mappedResultsQuery('SELECT 1'); + expect(result, [ + { + null: {'?column?': 1} + } + ]); }); } -class InterceptingConnection extends PostgreSQLConnection { - InterceptingConnection(String host, int port, String databaseName, {String username: null, String password: null}) - : super(host, port, databaseName, username: username, password: password); - - List queries = []; +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(); +} - @override - Future>> query(String fmtString, - {Map substitutionValues: null, bool allowReuse: true, int timeoutInSeconds}) { - queries.add(fmtString); - return super.query(fmtString, substitutionValues: substitutionValues, allowReuse: allowReuse); - } +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; } diff --git a/test/notification_test.dart b/test/notification_test.dart index 1aa6017..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,107 +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;" - "NOTIFY $channel, '$payload';"); + 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 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(); - connection.notifications.listen((msg){ - int count = countResponse[msg.channel]; + final finishExecute = Completer(); + connection.notifications.listen((msg) { + final count = countResponse[msg.channel]; countResponse[msg.channel] = (count ?? 0) + 1; totalCountResponse++; - if(totalCountResponse == 20) - finishExecute.complete(); + 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 8469592..44ebbae 100644 --- a/test/query_reuse_test.dart +++ b/test/query_reuse_test.dart @@ -1,406 +1,409 @@ -import 'package:postgres/postgres.dart'; -import 'package:test/test.dart'; import 'dart:async'; import 'dart:mirrors'; +import 'package:postgres/src/query_cache.dart'; +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); + expect(getQueryCache(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); + expect(getQueryCache(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,81 +426,88 @@ 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 {} + } on PostgreSQLException { + // ignore + } - expect(cachedQueryMap(connection).isEmpty, true); + expect(getQueryCache(connection).isEmpty, true); }); 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 {} + } on PostgreSQLException { + // ignore + } - expect(cachedQueryMap(connection).length, 0); + expect(getQueryCache(connection).length, 0); }); 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 {} + } on PostgreSQLException { + // ignores + } 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 +515,30 @@ 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"}); - } on FormatException {} + await connection.query(string, substitutionValues: {'i': 'foo'}); + } on FormatException { + // ignore + } - results = await connection.query(string, substitutionValues: {"i": 2}); + results = await connection.query(string, substitutionValues: {'i': 2}); expect(results, [ [2, 3] ]); @@ -534,37 +546,37 @@ 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] ]); - expect(cachedQueryMap(connection).length, 1); + expect(getQueryCache(connection).length, 1); expect(hasCachedQueryNamed(connection, string), true); }); }); } -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; +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).reflectee + as QueryCache; } bool hasCachedQueryNamed(PostgreSQLConnection connection, String name) { - return cachedQueryMap(connection)[name] != null; + return getQueryCache(connection)[name] != null; } diff --git a/test/query_test.dart b/test/query_test.dart index 4c2e883..091e945 100644 --- a/test/query_test.dart +++ b/test/query_test.dart @@ -3,268 +3,275 @@ 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.columnDescriptions, hasLength(1)); + expect(result.columnDescriptions.single.tableName, 't'); + expect(result.columnDescriptions.single.columnName, '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.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"); + result = await connection.query( + '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"); + result = await connection.query( + '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, [ @@ -272,12 +279,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, [ @@ -285,13 +292,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, [ @@ -299,13 +306,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, [ @@ -313,13 +320,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] @@ -327,17 +331,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 { @@ -345,71 +348,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 @@ -522,4 +519,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 2d2b40e..5ff8f32 100644 --- a/test/timeout_test.dart +++ b/test/timeout_test.dart @@ -1,76 +1,99 @@ -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 { 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)"); + 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 {} + } on TimeoutException { + // ignore + } 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 {} + } on TimeoutException { + // ignore + } - 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", () 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); - 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 {} + } on TimeoutException { + // ignore + } - 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", () 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); + 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 { + 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 { - 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 { - await conn.query("INSERT INTO t (id) VALUES ('foo')", timeoutInSeconds: 1).catchError((_) => null); - expect(new Future.delayed(new Duration(seconds: 2)), completes); + test('Query that fails does not timeout', () async { + await conn + .query("INSERT INTO t (id) VALUES ('foo')", timeoutInSeconds: 1) + .catchError((_) => null); + expect(Future.delayed(Duration(seconds: 2)), completes); }); } diff --git a/test/transaction_test.dart b/test/transaction_test.dart index b9d8a8e..c9f7b8c 100644 --- a/test/transaction_test.dart +++ b/test/transaction_test.dart @@ -1,61 +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 { - return await ctx.query('SELECT * FROM t WHERE id = @id LIMIT 1', substitutionValues: {'id': 1}); - }); + 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", () async { - var outResult = await conn.transaction((c) async { - await c.query("INSERT INTO t (id) VALUES (1)"); + test('Send successful transaction succeeds, returns returned value', + () async { + 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", () async { - var orderEnsurer = []; - var nextCompleter = new Completer.sync(); - var outResult = conn.transaction((c) async { + test('Query during transaction must wait until transaction is finished', + () 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; @@ -63,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, [ @@ -80,31 +86,32 @@ void main() { ]); }); - test("Make sure two simultaneous transactions cannot be interwoven", () async { - var orderEnsurer = []; + test('Make sure two simultaneous transactions cannot be interwoven', + () async { + 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]); @@ -117,36 +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", () async { - var orderEnsurer = []; - var nextCompleter = new Completer.sync(); - var outResult = conn.transaction((c) async { + test('Intentional rollback from outside of a transaction has no impact', + () 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; @@ -156,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, [ @@ -164,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], @@ -183,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], @@ -199,85 +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(pendingQueryError.toString(), contains("failed prior to execution")); - var total = await conn.query("SELECT id FROM t"); + expect(failingQueryError.toString(), contains('invalid input')); + expect( + 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", () async { - var errs = []; + test( + 'A transaction with a rollback and non-await queries rolls back transaction', + () async { + 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", + test( + '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", + test( + '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, []); }); }); @@ -285,75 +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); @@ -361,85 +371,90 @@ 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 {} + } on PostgreSQLException { + // ignore + } - 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 { + // ignore + } - 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); @@ -447,132 +462,146 @@ 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 { + // ignore + } - 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", () 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)"); + 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 {} + } on PostgreSQLException { + // ignore + } 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", () 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); - await c.query("INSERT INTO t (id) VALUES (2)"); + 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)'); }); fail('unreachable'); - } on PostgreSQLException {} + } on PostgreSQLException { + // ignore + } - 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); @@ -580,16 +609,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, []); });