diff --git a/CHANGELOG.md b/CHANGELOG.md index f31844a9..1162b310 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 2.4.5 + +- Added support for boolean arrays. [#41](https://github.com/isoos/postgresql-dart/pull/41) by [slightfoot](https://github.com/slightfoot). + ## 2.4.4 - Added support for varchar arrays. [#39](https://github.com/isoos/postgresql-dart/pull/39) by [paschalisp](https://github.com/paschalisp). diff --git a/lib/src/binary_codec.dart b/lib/src/binary_codec.dart index 8d335ee2..b4a836d6 100644 --- a/lib/src/binary_codec.dart +++ b/lib/src/binary_codec.dart @@ -244,6 +244,16 @@ class PostgresBinaryEncoder extends Converter { 'Invalid type for parameter value. Expected: PgPoint Got: ${value.runtimeType}'); } + case PostgreSQLDataType.booleanArray: + { + if (value is List) { + return writeListBytes( + value, 16, (_) => 1, (writer, item) => writer.writeUint8(item ? 1 : 0)); + } + throw FormatException( + 'Invalid type for parameter value. Expected: List Got: ${value.runtimeType}'); + } + case PostgreSQLDataType.integerArray: { if (value is List) { @@ -495,6 +505,9 @@ class PostgresBinaryDecoder extends Converter { case PostgreSQLDataType.point: return PgPoint(buffer.getFloat64(0), buffer.getFloat64(8)); + case PostgreSQLDataType.booleanArray: + return readListBytes(value, (reader, _) => reader.readUint8() != 0); + case PostgreSQLDataType.integerArray: return readListBytes(value, (reader, _) => reader.readInt32()); @@ -565,6 +578,7 @@ class PostgresBinaryDecoder extends Converter { 600: PostgreSQLDataType.point, 700: PostgreSQLDataType.real, 701: PostgreSQLDataType.double, + 1000: PostgreSQLDataType.booleanArray, 1007: PostgreSQLDataType.integerArray, 1009: PostgreSQLDataType.textArray, 1015: PostgreSQLDataType.varCharArray, diff --git a/lib/src/query.dart b/lib/src/query.dart index eb2642a6..64e242df 100644 --- a/lib/src/query.dart +++ b/lib/src/query.dart @@ -332,6 +332,7 @@ class PostgreSQLFormatIdentifier { 'uuid': PostgreSQLDataType.uuid, 'json': PostgreSQLDataType.json, 'point': PostgreSQLDataType.point, + '_bool': PostgreSQLDataType.booleanArray, '_int4': PostgreSQLDataType.integerArray, '_text': PostgreSQLDataType.textArray, '_float8': PostgreSQLDataType.doubleArray, diff --git a/lib/src/substituter.dart b/lib/src/substituter.dart index 16f29684..bf7125f5 100644 --- a/lib/src/substituter.dart +++ b/lib/src/substituter.dart @@ -55,6 +55,8 @@ class PostgreSQLFormat { return 'point'; case PostgreSQLDataType.json: return 'json'; + case PostgreSQLDataType.booleanArray: + return '_bool'; case PostgreSQLDataType.integerArray: return '_int4'; case PostgreSQLDataType.textArray: diff --git a/lib/src/text_codec.dart b/lib/src/text_codec.dart index 81b9113a..198a5d98 100644 --- a/lib/src/text_codec.dart +++ b/lib/src/text_codec.dart @@ -183,6 +183,10 @@ class PostgresTextEncoder { } }); + if (type == bool) { + return '{${value.cast().map((s) => s.toString()).join(',')}}'; + } + if (type == int || type == double) { return '{${value.cast().map((s) => s is double ? _encodeDouble(s) : _encodeNumber(s)).join(',')}}'; } diff --git a/lib/src/types.dart b/lib/src/types.dart index 452a936b..bbb50b28 100644 --- a/lib/src/types.dart +++ b/lib/src/types.dart @@ -3,8 +3,10 @@ 1. add item to this enumeration 2. update all switch statements on this type - 3. add pg type code -> enumeration item in PostgresBinaryDecoder.typeMap (lookup type code: https://doxygen.postgresql.org/include_2catalog_2pg__type_8h_source.html) + 3. add pg type code -> enumeration item in PostgresBinaryDecoder.typeMap + (lookup type code: https://doxygen.postgresql.org/pg__type_8h_source.html) 4. add identifying key to PostgreSQLFormatIdentifier.typeStringToCodeMap. + 5. add identifying key to PostgreSQLFormat.dataTypeStringForDataType */ /// Supported data types. @@ -80,6 +82,9 @@ enum PostgreSQLDataType { /// Must be a [PgPoint] point, + /// Must be a [List] + booleanArray, + /// Must be a [List] integerArray, diff --git a/pubspec.yaml b/pubspec.yaml index a6bb65db..b4a0b031 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: postgres description: PostgreSQL database driver. Supports statement reuse and binary protocol. -version: 2.4.4 +version: 2.4.5 homepage: https://github.com/isoos/postgresql-dart environment: diff --git a/test/decode_test.dart b/test/decode_test.dart index e9b89ec8..5f86fd27 100644 --- a/test/decode_test.dart +++ b/test/decode_test.dart @@ -7,28 +7,33 @@ import 'package:test/test.dart'; void main() { late PostgreSQLConnection connection; setUp(() async { - connection = 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, n numeric, j jsonb, ba bytea, - u uuid, v varchar, p point, jj json, ia _int4, ta _text, da _float8, ja _jsonb, va varchar(20)[]) + u uuid, v varchar, p point, jj json, ia _int4, ta _text, da _float8, ja _jsonb, va varchar(20)[], + boola _bool + ) '''); await connection.execute( - 'INSERT INTO t (i, bi, bl, si, t, f, d, dt, ts, tsz, n, j, ba, u, v, p, jj, ia, ta, da, ja, va) ' + 'INSERT INTO t (i, bi, bl, si, t, f, d, dt, ts, tsz, n, j, ba, u, v, p, jj, ia, ta, da, ja, va, boola) ' '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', " "'-1234567890.0987654321', " "'{\"key\":\"value\"}', E'\\\\000', '00000000-0000-0000-0000-000000000000', " "'abcdef', '(0.01, 12.34)', '{\"key\": \"value\"}', '{}', '{}', '{}', '{}', " - "'{\"a\", \"b\", \"c\", \"d\", \"e\", \"f\"}')"); + "'{\"a\", \"b\", \"c\", \"d\", \"e\", \"f\"}', " + "'{true, false, false}'" + ')'); + await connection.execute( - 'INSERT INTO t (i, bi, bl, si, t, f, d, dt, ts, tsz, n, j, ba, u, v, p, jj, ia, ta, da, ja, va) ' + 'INSERT INTO t (i, bi, bl, si, t, f, d, dt, ts, tsz, n, j, ba, u, v, p, jj, ia, ta, da, ja, va, boola) ' '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', " @@ -37,18 +42,20 @@ void main() { "'[{\"key\":1}]', E'\\\\377', 'FFFFFFFF-ffff-ffff-ffff-ffffffffffff', " "'01234', '(0.2, 100)', '{}', '{-123, 999}', '{\"a\", \"lorem ipsum\", \"\"}', " "'{1, 2, 4.5, 1234.5}', '{1, \"\\\"test\\\"\", \"{\\\"a\\\": \\\"b\\\"}\"}', " - "'{\"a\", \"b\", \"c\", \"d\", \"e\", \"f\"}')"); + "'{\"a\", \"b\", \"c\", \"d\", \"e\", \"f\"}', " + "'{false, false, true}' " + ')'); await connection.execute( - 'INSERT INTO t (i, bi, bl, si, t, f, d, dt, ts, tsz, n, j, ba, u, v, p, jj, ia, ta, da, ja, va) ' + 'INSERT INTO t (i, bi, bl, si, t, f, d, dt, ts, tsz, n, j, ba, u, v, p, jj, ia, ta, da, ja, va, boola) ' 'VALUES (null, null, null, null, null, null, null, null, null, null, null, null, null, ' - 'null, null, null, null, null, null, null, null, null)'); + 'null, null, null, null, null, null, null, null, null, null)'); }); tearDown(() async { await connection.close(); }); - test('Fetch em', () async { + test(' Fetch em', () async { final res = await connection.query('select * from t'); final row1 = res[0]; @@ -83,6 +90,8 @@ void main() { expect(row1[22], equals([])); expect(row1[23] is List, true); expect(row1[23], equals(['a', 'b', 'c', 'd', 'e', 'f'])); + expect(row1[24] is List, true); + expect(row1[24], equals([true, false, false])); // upper bound row expect(row2[0], equals(2147483647)); @@ -91,10 +100,8 @@ 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); @@ -102,8 +109,7 @@ void main() { 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('1000000000000000000000000000.0000000000000000000000000001')); + expect(row2[12], equals('1000000000000000000000000000.0000000000000000000000000001')); expect( row2[13], equals([ @@ -124,6 +130,10 @@ void main() { 'test', {'a': 'b'} ])); + expect(row2[23] is List, true); + expect(row2[23], equals(['a', 'b', 'c', 'd', 'e', 'f'])); + expect(row2[24] is List, true); + expect(row2[24], equals([false, false, true])); // all null row expect(row3[0], isNull); @@ -150,13 +160,13 @@ void main() { expect(row3[21], isNull); expect(row3[22], isNull); expect(row3[23], isNull); + expect(row3[24], 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': ''}); + var results = await connection + .query('INSERT INTO u (t) VALUES (@t:text) returning t', substitutionValues: {'t': ''}); expect(results, [ [''] ]); @@ -169,9 +179,8 @@ void main() { test('Fetch/insert null value', () async { await connection.execute('CREATE TEMPORARY TABLE u (t text)'); - var results = await connection.query( - 'INSERT INTO u (t) VALUES (@t:text) returning t', - substitutionValues: {'t': null}); + var results = await connection + .query('INSERT INTO u (t) VALUES (@t:text) returning t', substitutionValues: {'t': null}); expect(results, [ [null] ]); @@ -185,26 +194,7 @@ void main() { test('Decode Numeric to String', () { final binaries = { '-123400000.20000': [0, 4, 0, 2, 64, 0, 0, 5, 0, 1, 9, 36, 0, 0, 7, 208], - '-123400001.00002': [ - 0, - 5, - 0, - 2, - 64, - 0, - 0, - 5, - 0, - 1, - 9, - 36, - 0, - 1, - 0, - 0, - 7, - 208 - ], + '-123400001.00002': [0, 5, 0, 2, 64, 0, 0, 5, 0, 1, 9, 36, 0, 1, 0, 0, 7, 208], '0.00001': [0, 1, 255, 254, 0, 0, 0, 5, 3, 232], '10000.000000000': [0, 1, 0, 1, 0, 0, 0, 9, 0, 1], 'NaN': [0, 0, 0, 0, 192, 0, 0, 0], diff --git a/test/encoding_test.dart b/test/encoding_test.dart index 40e1fd3d..ce4030b1 100644 --- a/test/encoding_test.dart +++ b/test/encoding_test.dart @@ -375,6 +375,19 @@ void main() { } }); + test('booleanArray', () async { + await expectInverse([], PostgreSQLDataType.booleanArray); + await expectInverse([false, true], PostgreSQLDataType.booleanArray); + await expectInverse([true], PostgreSQLDataType.booleanArray); + try { + await conn.query('INSERT INTO t (v) VALUES (@v:_bool)', + substitutionValues: {'v': 'not-list-bool'}); + fail('unreachable'); + } on FormatException catch (e) { + expect(e.toString(), contains('Expected: List')); + } + }); + test('integerArray', () async { await expectInverse([], PostgreSQLDataType.integerArray); await expectInverse([-1, 0, 200], PostgreSQLDataType.integerArray); diff --git a/test/query_test.dart b/test/query_test.dart index f5bad919..cadf4b4f 100644 --- a/test/query_test.dart +++ b/test/query_test.dart @@ -14,7 +14,9 @@ void main() { 'bs bigserial, bl boolean, si smallint, ' 't text, f real, d double precision, ' 'dt date, ts timestamp, tsz timestamptz, j jsonb, u uuid, ' - 'v varchar, p point, jj json, ia _int4, ta _text, da _float8, ja _jsonb, va _varchar(20))'); + 'v varchar, p point, jj json, ia _int4, ta _text, da _float8, ja _jsonb, va _varchar(20), ' + 'ba _bool' + ')'); await connection.execute( 'CREATE TEMPORARY TABLE u (i1 int not null, i2 int not null);'); await connection @@ -109,7 +111,7 @@ void main() { 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, v, p, jj, ia, ta, da, ja, va) values ' + 'INSERT INTO t (i, bi, bl, si, t, f, d, dt, ts, tsz, j, u, v, p, jj, ia, ta, da, ja, va, ba) values ' '(${PostgreSQLFormat.id('i')},' '${PostgreSQLFormat.id('bi')},' '${PostgreSQLFormat.id('bl')},' @@ -129,8 +131,9 @@ void main() { '${PostgreSQLFormat.id('ta')},' '${PostgreSQLFormat.id('da')},' '${PostgreSQLFormat.id('ja')},' - '${PostgreSQLFormat.id('va')}' - ') returning i,s, bi, bs, bl, si, t, f, d, dt, ts, tsz, j, u, v, p, jj, ia, ta, da, ja, va', + '${PostgreSQLFormat.id('va')},' + '${PostgreSQLFormat.id('ba')}' + ') returning i,s, bi, bs, bl, si, t, f, d, dt, ts, tsz, j, u, v, p, jj, ia, ta, da, ja, va, ba', substitutionValues: { 'i': 1, 'bi': 2, @@ -155,7 +158,8 @@ void main() { 'a"\'\\"', {'k': 'v"\'\\"'} ], - 'va': ['a', 'b', 'c', 'd', 'e', 'f'] + 'va': ['a', 'b', 'c', 'd', 'e', 'f'], + 'ba': [false, true, false], }); final expectedRow = [ @@ -184,22 +188,23 @@ void main() { 'a"\'\\"', {'k': 'v"\'\\"'} ], - ['a', 'b', 'c', 'd', 'e', 'f'] + ['a', 'b', 'c', 'd', 'e', 'f'], + [false, true, false] ]; - expect(result.columnDescriptions, hasLength(22)); + expect(result.columnDescriptions, hasLength(23)); expect(result.columnDescriptions.first.tableName, 't'); expect(result.columnDescriptions.first.columnName, 'i'); expect(result.columnDescriptions.last.tableName, 't'); - expect(result.columnDescriptions.last.columnName, 'va'); + expect(result.columnDescriptions.last.columnName, 'ba'); expect(result, [expectedRow]); result = await connection.query( - 'select i,s, bi, bs, bl, si, t, f, d, dt, ts, tsz, j, u, v, p, jj, ia, ta, da, ja, va from t'); + 'select i,s, bi, bs, bl, si, t, f, d, dt, ts, tsz, j, u, v, p, jj, ia, ta, da, ja, va, ba from t'); expect(result, [expectedRow]); }); 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, v, p, jj, ia, ta, da, ja, va) values ' + 'INSERT INTO t (i, bi, bl, si, t, f, d, dt, ts, tsz, j, u, v, p, jj, ia, ta, da, ja, va, ba) values ' '(${PostgreSQLFormat.id('i', type: PostgreSQLDataType.integer)},' '${PostgreSQLFormat.id('bi', type: PostgreSQLDataType.bigInteger)},' '${PostgreSQLFormat.id('bl', type: PostgreSQLDataType.boolean)},' @@ -219,8 +224,9 @@ void main() { '${PostgreSQLFormat.id('ta', type: PostgreSQLDataType.textArray)},' '${PostgreSQLFormat.id('da', type: PostgreSQLDataType.doubleArray)},' '${PostgreSQLFormat.id('ja', type: PostgreSQLDataType.jsonbArray)},' - '${PostgreSQLFormat.id('va', type: PostgreSQLDataType.varCharArray)}' - ') returning i,s, bi, bs, bl, si, t, f, d, dt, ts, tsz, j, u, v, p, jj, ia, ta, da, ja, va', + '${PostgreSQLFormat.id('va', type: PostgreSQLDataType.varCharArray)},' + '${PostgreSQLFormat.id('ba', type: PostgreSQLDataType.booleanArray)}' + ') returning i,s, bi, bs, bl, si, t, f, d, dt, ts, tsz, j, u, v, p, jj, ia, ta, da, ja, va, ba', substitutionValues: { 'i': 1, 'bi': 2, @@ -245,7 +251,8 @@ void main() { 'a', {'k': 'v'} ], - 'va': ['a', 'b', 'c', 'd', 'e', 'f'] + 'va': ['a', 'b', 'c', 'd', 'e', 'f'], + 'ba': [false, true, true, false], }); final expectedRow = [ @@ -274,12 +281,13 @@ void main() { 'a', {'k': 'v'} ], - ['a', 'b', 'c', 'd', 'e', 'f'] + ['a', 'b', 'c', 'd', 'e', 'f'], + [false, true, true, false], ]; expect(result, [expectedRow]); result = await connection.query( - 'select i,s, bi, bs, bl, si, t, f, d, dt, ts, tsz, j, u, v, p, jj, ia, ta, da, ja, va from t'); + 'select i,s, bi, bs, bl, si, t, f, d, dt, ts, tsz, j, u, v, p, jj, ia, ta, da, ja, va, ba from t'); expect(result, [expectedRow]); });