diff --git a/CHANGELOG.md b/CHANGELOG.md index d5191e0..744e48b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/lib/src/types/text_codec.dart b/lib/src/types/text_codec.dart index 81c7c02..dcd19a1 100644 --- a/lib/src/types/text_codec.dart +++ b/lib/src/types/text_codec.dart @@ -47,6 +47,10 @@ class PostgresTextEncoder { return _encodePoint(input); } + if (input is Uint8List) { + return _encodeBytea(input, escapeStrings); + } + if (input is List) { return _encodeList(input); } @@ -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 '{}'; diff --git a/test/bytes_example_test.dart b/test/bytes_example_test.dart index 734357e..4e55195 100644 --- a/test/bytes_example_test.dart +++ b/test/bytes_example_test.dart @@ -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); + }); }); } diff --git a/test/encoding_test.dart b/test/encoding_test.dart index 2c3016b..41007a3 100644 --- a/test/encoding_test.dart +++ b/test/encoding_test.dart @@ -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'; @@ -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;