Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
- Fix `runTx` silently rolling back after `ROLLBACK TO SAVEPOINT` recovery: clear stale `_transactionException` when PostgreSQL confirms a healthy transaction state.
- Fix connection permanently blocked when `BEGIN` fails inside `runTx` (stale `_activeTransaction` state).
- Fix connection left in undefined PostgreSQL state when `ROLLBACK` fails after a transaction error.
- Fix untyped `Uint8List` parameters being encoded as an integer array literal: they are now encoded as a `bytea` hex literal.

## 3.5.11

Expand Down
12 changes: 12 additions & 0 deletions lib/src/types/text_codec.dart
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ class PostgresTextEncoder {
return _encodePoint(input);
}

if (input is Uint8List) {
return _encodeBytea(input, escapeStrings);
}

if (input is List) {
return _encodeList(input);
}
Expand Down Expand Up @@ -200,6 +204,14 @@ class PostgresTextEncoder {
return '(${_encodeDouble(value.latitude)}, ${_encodeDouble(value.longitude)})';
}

String _encodeBytea(Uint8List value, bool escapeStrings) {
final hex = StringBuffer(r'\x');
for (final byte in value) {
hex.write(byte.toRadixString(16).padLeft(2, '0'));
}
return _encodeString(hex.toString(), escapeStrings);
}

String _encodeList(List value) {
if (value.isEmpty) {
return '{}';
Expand Down
23 changes: 23 additions & 0 deletions test/bytes_example_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,28 @@ void main() {
final bytes = rs2.single.single;
expect(bytes, [0, 1, 2]);
});

test('write and read untyped parameter', () async {
final conn = await server.newConnection();
await conn.execute('''
CREATE TABLE IF NOT EXISTS blobs (
id SERIAL PRIMARY KEY,
data BYTEA NOT NULL
);
''');

final data = Uint8List.fromList([0, 1, 127, 255]);
final rs1 = await conn.execute(
Sql.named('INSERT INTO blobs (data) VALUES (@data) RETURNING id'),
parameters: {'data': data},
);
final id = rs1.single.single;

final rs2 = await conn.execute(
r'SELECT data FROM blobs WHERE id=$1',
parameters: [id],
);
expect(rs2.single.single, data);
});
});
}
19 changes: 19 additions & 0 deletions test/encoding_test.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'dart:async';
import 'dart:convert';
import 'dart:typed_data';

import 'package:postgres/postgres.dart';
import 'package:postgres/src/types/text_codec.dart';
Expand Down Expand Up @@ -954,6 +955,24 @@ void main() {
);
});

test('Encode Uint8List as bytea, not as an int array literal', () {
expect(
encoder.convert(
Uint8List.fromList([0, 1, 127, 255]),
escapeStrings: false,
),
r'\x00017fff',
);

// sp E ' \ \ x 0 0 f f '
expect(
utf8.encode(encoder.convert(Uint8List.fromList([0, 255]))),
equals([32, 69, 39, 92, 92, 120, 48, 48, 102, 102, 39]),
);

expect(encoder.convert(Uint8List(0), escapeStrings: false), r'\x');
});

test('Encode DateTime', () {
// Get users current timezone
final tz = DateTime(2001, 2, 3).timeZoneOffset;
Expand Down
Loading