Skip to content

Commit

Permalink
Added support for boolean array fields (#41)
Browse files Browse the repository at this point in the history
  • Loading branch information
slightfoot authored Jun 20, 2022
1 parent b06ba78 commit 19661f0
Show file tree
Hide file tree
Showing 10 changed files with 100 additions and 59 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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).
Expand Down
14 changes: 14 additions & 0 deletions lib/src/binary_codec.dart
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,16 @@ class PostgresBinaryEncoder extends Converter<dynamic, Uint8List?> {
'Invalid type for parameter value. Expected: PgPoint Got: ${value.runtimeType}');
}

case PostgreSQLDataType.booleanArray:
{
if (value is List<bool>) {
return writeListBytes<bool>(
value, 16, (_) => 1, (writer, item) => writer.writeUint8(item ? 1 : 0));
}
throw FormatException(
'Invalid type for parameter value. Expected: List<bool> Got: ${value.runtimeType}');
}

case PostgreSQLDataType.integerArray:
{
if (value is List<int>) {
Expand Down Expand Up @@ -495,6 +505,9 @@ class PostgresBinaryDecoder extends Converter<Uint8List, dynamic> {
case PostgreSQLDataType.point:
return PgPoint(buffer.getFloat64(0), buffer.getFloat64(8));

case PostgreSQLDataType.booleanArray:
return readListBytes<bool>(value, (reader, _) => reader.readUint8() != 0);

case PostgreSQLDataType.integerArray:
return readListBytes<int>(value, (reader, _) => reader.readInt32());

Expand Down Expand Up @@ -565,6 +578,7 @@ class PostgresBinaryDecoder extends Converter<Uint8List, dynamic> {
600: PostgreSQLDataType.point,
700: PostgreSQLDataType.real,
701: PostgreSQLDataType.double,
1000: PostgreSQLDataType.booleanArray,
1007: PostgreSQLDataType.integerArray,
1009: PostgreSQLDataType.textArray,
1015: PostgreSQLDataType.varCharArray,
Expand Down
1 change: 1 addition & 0 deletions lib/src/query.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 2 additions & 0 deletions lib/src/substituter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
4 changes: 4 additions & 0 deletions lib/src/text_codec.dart
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,10 @@ class PostgresTextEncoder {
}
});

if (type == bool) {
return '{${value.cast<bool>().map((s) => s.toString()).join(',')}}';
}

if (type == int || type == double) {
return '{${value.cast<num>().map((s) => s is double ? _encodeDouble(s) : _encodeNumber(s)).join(',')}}';
}
Expand Down
7 changes: 6 additions & 1 deletion lib/src/types.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -80,6 +82,9 @@ enum PostgreSQLDataType {
/// Must be a [PgPoint]
point,

/// Must be a [List<bool>]
booleanArray,

/// Must be a [List<int>]
integerArray,

Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -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:
Expand Down
74 changes: 32 additions & 42 deletions test/decode_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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', "
Expand All @@ -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];
Expand Down Expand Up @@ -83,6 +90,8 @@ void main() {
expect(row1[22], equals([]));
expect(row1[23] is List<String>, true);
expect(row1[23], equals(['a', 'b', 'c', 'd', 'e', 'f']));
expect(row1[24] is List<bool>, true);
expect(row1[24], equals([true, false, false]));

// upper bound row
expect(row2[0], equals(2147483647));
Expand All @@ -91,19 +100,16 @@ 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(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([
Expand All @@ -124,6 +130,10 @@ void main() {
'test',
{'a': 'b'}
]));
expect(row2[23] is List<String>, true);
expect(row2[23], equals(['a', 'b', 'c', 'd', 'e', 'f']));
expect(row2[24] is List<bool>, true);
expect(row2[24], equals([false, false, true]));

// all null row
expect(row3[0], isNull);
Expand All @@ -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, [
['']
]);
Expand All @@ -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]
]);
Expand All @@ -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],
Expand Down
13 changes: 13 additions & 0 deletions test/encoding_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,19 @@ void main() {
}
});

test('booleanArray', () async {
await expectInverse(<bool>[], 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<bool>'));
}
});

test('integerArray', () async {
await expectInverse(<int>[], PostgreSQLDataType.integerArray);
await expectInverse([-1, 0, 200], PostgreSQLDataType.integerArray);
Expand Down
38 changes: 23 additions & 15 deletions test/query_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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')},'
Expand All @@ -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,
Expand All @@ -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 = [
Expand Down Expand Up @@ -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)},'
Expand All @@ -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,
Expand All @@ -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 = [
Expand Down Expand Up @@ -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]);
});

Expand Down

0 comments on commit 19661f0

Please sign in to comment.