diff --git a/CHANGELOG.md b/CHANGELOG.md index cc1717fd..9b36ce3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 2.3.1 + +- Added support for types varchar, point, integerArray, doubleArray, textArray and jsonArray. + ## 2.3.0 - Finalized null-safe release. diff --git a/lib/postgres.dart b/lib/postgres.dart index 01c9235f..fdde21da 100644 --- a/lib/postgres.dart +++ b/lib/postgres.dart @@ -2,5 +2,6 @@ library postgres; export 'src/connection.dart'; export 'src/execution_context.dart'; +export 'src/models.dart'; export 'src/substituter.dart'; export 'src/types.dart'; diff --git a/lib/src/binary_codec.dart b/lib/src/binary_codec.dart index aa4c75b9..66055eaa 100644 --- a/lib/src/binary_codec.dart +++ b/lib/src/binary_codec.dart @@ -82,6 +82,7 @@ class PostgresBinaryEncoder extends Converter { } case PostgreSQLDataType.name: case PostgreSQLDataType.text: + case PostgreSQLDataType.varChar: { if (value is String) { return castBytes(utf8.encode(value)); @@ -144,7 +145,7 @@ class PostgresBinaryEncoder extends Converter { 'Invalid type for parameter value. Expected: DateTime Got: ${value.runtimeType}'); } - case PostgreSQLDataType.json: + case PostgreSQLDataType.jsonb: { final jsonBytes = utf8.encode(json.encode(value)); final writer = ByteDataWriter(bufferLength: jsonBytes.length + 1); @@ -153,6 +154,9 @@ class PostgresBinaryEncoder extends Converter { return writer.toBytes(); } + case PostgreSQLDataType.json: + return castBytes(utf8.encode(json.encode(value))); + case PostgreSQLDataType.byteArray: { if (value is List) { @@ -199,10 +203,90 @@ class PostgresBinaryEncoder extends Converter { } return outBuffer; } + + case PostgreSQLDataType.point: + { + if (value is PgPoint) { + final bd = ByteData(16); + bd.setFloat64(0, value.latitude); + bd.setFloat64(8, value.longitude); + return bd.buffer.asUint8List(); + } + throw FormatException( + 'Invalid type for parameter value. Expected: PgPoint Got: ${value.runtimeType}'); + } + + case PostgreSQLDataType.integerArray: + { + if (value is List) { + return writeListBytes( + value, 23, (_) => 4, (writer, item) => writer.writeInt32(item)); + } + throw FormatException( + 'Invalid type for parameter value. Expected: List Got: ${value.runtimeType}'); + } + + case PostgreSQLDataType.textArray: + { + if (value is List) { + final bytesArray = value.map((v) => utf8.encode(v)); + return writeListBytes>(bytesArray, 25, + (item) => item.length, (writer, item) => writer.write(item)); + } + throw FormatException( + 'Invalid type for parameter value. Expected: List Got: ${value.runtimeType}'); + } + + case PostgreSQLDataType.doubleArray: + { + if (value is List) { + return writeListBytes(value, 701, (_) => 8, + (writer, item) => writer.writeFloat64(item)); + } + throw FormatException( + 'Invalid type for parameter value. Expected: List Got: ${value.runtimeType}'); + } + + case PostgreSQLDataType.jsonbArray: + { + if (value is List) { + final objectsArray = value.map((v) => utf8.encode(json.encode(v))); + return writeListBytes>( + objectsArray, 3802, (item) => item.length + 1, (writer, item) { + writer.writeUint8(1); + writer.write(item); + }); + } + throw FormatException( + 'Invalid type for parameter value. Expected: List Got: ${value.runtimeType}'); + } + default: throw PostgreSQLException('Unsupported datatype'); } } + + Uint8List writeListBytes( + Iterable value, + int type, + int Function(T item) lengthEncoder, + void Function(ByteDataWriter writer, T item) valueEncoder) { + final writer = ByteDataWriter(); + + writer.writeInt32(1); // dimension + writer.writeInt32(0); // ign + writer.writeInt32(type); // type + writer.writeInt32(value.length); // size + writer.writeInt32(1); // index + + for (var i in value) { + final len = lengthEncoder(i); + writer.writeInt32(len); + valueEncoder(writer, i); + } + + return writer.toBytes(); + } } class PostgresBinaryDecoder extends Converter { @@ -224,6 +308,7 @@ class PostgresBinaryDecoder extends Converter { switch (dataType) { case PostgreSQLDataType.name: case PostgreSQLDataType.text: + case PostgreSQLDataType.varChar: return utf8.decode(value); case PostgreSQLDataType.boolean: return buffer.getInt8(0) != 0; @@ -247,7 +332,7 @@ class PostgresBinaryDecoder extends Converter { case PostgreSQLDataType.date: return DateTime.utc(2000).add(Duration(days: buffer.getInt32(0))); - case PostgreSQLDataType.json: + case PostgreSQLDataType.jsonb: { // Removes version which is first character and currently always '1' final bytes = value.buffer @@ -255,6 +340,9 @@ class PostgresBinaryDecoder extends Converter { return json.decode(utf8.decode(bytes)); } + case PostgreSQLDataType.json: + return json.decode(utf8.decode(value)); + case PostgreSQLDataType.byteArray: return value; @@ -277,6 +365,29 @@ class PostgresBinaryDecoder extends Converter { return buf.toString(); } + + case PostgreSQLDataType.point: + return PgPoint(buffer.getFloat64(0), buffer.getFloat64(8)); + + case PostgreSQLDataType.integerArray: + return readListBytes(value, (reader, _) => reader.readInt32()); + + case PostgreSQLDataType.textArray: + return readListBytes(value, (reader, length) { + return utf8.decode(length > 0 ? reader.read(length) : []); + }); + + case PostgreSQLDataType.doubleArray: + return readListBytes( + value, (reader, _) => reader.readFloat64()); + + case PostgreSQLDataType.jsonbArray: + return readListBytes(value, (reader, length) { + reader.read(1); + final bytes = reader.read(length - 1); + return json.decode(utf8.decode(bytes)); + }); + default: { // We'll try and decode this as a utf8 string and return that @@ -292,6 +403,28 @@ class PostgresBinaryDecoder extends Converter { } } + List readListBytes(Uint8List data, + T Function(ByteDataReader reader, int length) valueDecoder) { + if (data.length < 16) { + return []; + } + + final reader = ByteDataReader()..add(data); + reader.read(12); // header + + final decoded = [].cast(); + final size = reader.readInt32(); + + reader.read(4); // index + + for (var i = 0; i < size; i++) { + final len = reader.readInt32(); + decoded.add(valueDecoder(reader, len)); + } + + return decoded; + } + static final Map typeMap = { 16: PostgreSQLDataType.boolean, 17: PostgreSQLDataType.byteArray, @@ -300,12 +433,19 @@ class PostgresBinaryDecoder extends Converter { 21: PostgreSQLDataType.smallInteger, 23: PostgreSQLDataType.integer, 25: PostgreSQLDataType.text, + 114: PostgreSQLDataType.json, + 600: PostgreSQLDataType.point, 700: PostgreSQLDataType.real, 701: PostgreSQLDataType.double, + 1007: PostgreSQLDataType.integerArray, + 1009: PostgreSQLDataType.textArray, + 1043: PostgreSQLDataType.varChar, + 1022: PostgreSQLDataType.doubleArray, 1082: PostgreSQLDataType.date, 1114: PostgreSQLDataType.timestampWithoutTimezone, 1184: PostgreSQLDataType.timestampWithTimezone, 2950: PostgreSQLDataType.uuid, - 3802: PostgreSQLDataType.json, + 3802: PostgreSQLDataType.jsonb, + 3807: PostgreSQLDataType.jsonbArray, }; } diff --git a/lib/src/models.dart b/lib/src/models.dart new file mode 100644 index 00000000..55c6285f --- /dev/null +++ b/lib/src/models.dart @@ -0,0 +1,16 @@ +class PgPoint { + final double latitude; + final double longitude; + const PgPoint(this.latitude, this.longitude); + + @override + bool operator ==(Object other) => + identical(this, other) || + other is PgPoint && + runtimeType == other.runtimeType && + latitude == other.latitude && + longitude == other.longitude; + + @override + int get hashCode => latitude.hashCode ^ longitude.hashCode; +} diff --git a/lib/src/query.dart b/lib/src/query.dart index 9b493459..848e5dab 100644 --- a/lib/src/query.dart +++ b/lib/src/query.dart @@ -320,10 +320,17 @@ class PostgreSQLFormatIdentifier { 'date': PostgreSQLDataType.date, 'timestamp': PostgreSQLDataType.timestampWithoutTimezone, 'timestamptz': PostgreSQLDataType.timestampWithTimezone, - 'jsonb': PostgreSQLDataType.json, + 'jsonb': PostgreSQLDataType.jsonb, 'bytea': PostgreSQLDataType.byteArray, 'name': PostgreSQLDataType.name, - 'uuid': PostgreSQLDataType.uuid + 'uuid': PostgreSQLDataType.uuid, + 'json': PostgreSQLDataType.json, + 'point': PostgreSQLDataType.point, + '_int4': PostgreSQLDataType.integerArray, + '_text': PostgreSQLDataType.textArray, + '_float8': PostgreSQLDataType.doubleArray, + 'varchar': PostgreSQLDataType.varChar, + '_jsonb': PostgreSQLDataType.jsonbArray, }; factory PostgreSQLFormatIdentifier(String t) { diff --git a/lib/src/substituter.dart b/lib/src/substituter.dart index 472383fb..fc22db48 100644 --- a/lib/src/substituter.dart +++ b/lib/src/substituter.dart @@ -39,7 +39,7 @@ class PostgreSQLFormat { return 'timestamptz'; case PostgreSQLDataType.date: return 'date'; - case PostgreSQLDataType.json: + case PostgreSQLDataType.jsonb: return 'jsonb'; case PostgreSQLDataType.byteArray: return 'bytea'; @@ -47,6 +47,20 @@ class PostgreSQLFormat { return 'name'; case PostgreSQLDataType.uuid: return 'uuid'; + case PostgreSQLDataType.point: + return 'point'; + case PostgreSQLDataType.json: + return 'json'; + case PostgreSQLDataType.integerArray: + return '_int4'; + case PostgreSQLDataType.textArray: + return '_text'; + case PostgreSQLDataType.doubleArray: + return '_float8'; + case PostgreSQLDataType.varChar: + return 'varchar'; + case PostgreSQLDataType.jsonbArray: + return '_jsonb'; default: return null; } diff --git a/lib/src/text_codec.dart b/lib/src/text_codec.dart index 8eca302e..f912002f 100644 --- a/lib/src/text_codec.dart +++ b/lib/src/text_codec.dart @@ -32,6 +32,14 @@ class PostgresTextEncoder { return _encodeJSON(value); } + if (value is PgPoint) { + return _encodePoint(value); + } + + if (value is List) { + return _encodeList(value); + } + // TODO: use custom type encoders throw PostgreSQLException("Could not infer type of value '$value'."); @@ -155,4 +163,46 @@ class PostgresTextEncoder { return json.encode(value); } + + String _encodePoint(PgPoint value) { + return '(${_encodeDouble(value.latitude)}, ${_encodeDouble(value.longitude)})'; + } + + String _encodeList(List value) { + if (value.isEmpty) { + return '{}'; + } + + final type = value.fold(value.first.runtimeType, (type, item) { + if (type == item.runtimeType) { + return type; + } else if ((type == int || type == double) && item is num) { + return double; + } else { + return Map; + } + }); + + if (type == int || type == double) { + return '{${value.cast().map((s) => s is double ? _encodeDouble(s) : _encodeNumber(s)).join(',')}}'; + } + + if (type == String) { + return '{${value.cast().map((s) { + final escaped = s.replaceAll(r'\', r'\\').replaceAll('"', r'\"'); + return '"$escaped"'; + }).join(',')}}'; + } + + if (type == Map) { + return '{${value.map((s) { + final escaped = + json.encode(s).replaceAll(r'\', r'\\').replaceAll('"', r'\"'); + + return '"$escaped"'; + }).join(',')}}'; + } + + throw PostgreSQLException("Could not infer array type of value '$value'."); + } } diff --git a/lib/src/types.dart b/lib/src/types.dart index ee76281f..a53ab6bd 100644 --- a/lib/src/types.dart +++ b/lib/src/types.dart @@ -45,6 +45,11 @@ enum PostgreSQLDataType { /// Must be a [DateTime] (contains year, month and day only) date, + /// Must be encodable via [json.encode]. + /// + /// Values will be encoded via [json.encode] before being sent to the database. + jsonb, + /// Must be encodable via [json.encode]. /// /// Values will be encoded via [json.encode] before being sent to the database. @@ -64,5 +69,23 @@ 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 + uuid, + + /// Must be a [PgPoint] + point, + + /// Must be a [List] + integerArray, + + /// Must be a [List] + textArray, + + /// Must be a [List] + doubleArray, + + /// Must be a [String] + varChar, + + /// Must be a [List] of encodable objects + jsonbArray, } diff --git a/test/decode_test.dart b/test/decode_test.dart index 7a44c1d8..478502e7 100644 --- a/test/decode_test.dart +++ b/test/decode_test.dart @@ -12,26 +12,30 @@ void main() { 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) + u uuid, v varchar, p point, jj json, ia _int4, ta _text, da _float8, ja _jsonb) '''); await connection.execute( - 'INSERT INTO t (i, bi, bl, si, t, f, d, dt, ts, tsz, j, ba, u) ' + 'INSERT INTO t (i, bi, bl, si, t, f, d, dt, ts, tsz, j, ba, u, v, p, jj, ia, ta, da, ja) ' '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')"); + "'{\"key\":\"value\"}', E'\\\\000', '00000000-0000-0000-0000-000000000000', " + "'abcdef', '(0.01, 12.34)', '{\"key\": \"value\"}', '{}', '{}', '{}', '{}')"); await connection.execute( - 'INSERT INTO t (i, bi, bl, si, t, f, d, dt, ts, tsz, j, ba, u) ' + 'INSERT INTO t (i, bi, bl, si, t, f, d, dt, ts, tsz, j, ba, u, v, p, jj, ia, ta, da, ja) ' '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')"); + "'[{\"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\\\"}\"}')"); 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)'); + 'INSERT INTO t (i, bi, bl, si, t, f, d, dt, ts, tsz, j, ba, u, v, p, jj, ia, ta, da, ja) ' + 'VALUES (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(); @@ -62,6 +66,13 @@ void main() { expect(row1[12], equals({'key': 'value'})); expect(row1[13], equals([0])); expect(row1[14], equals('00000000-0000-0000-0000-000000000000')); + expect(row1[15], equals('abcdef')); + expect(row1[16], equals(PgPoint(0.01, 12.34))); + expect(row1[17], equals({'key': 'value'})); + expect(row1[18], equals([])); + expect(row1[19], equals([])); + expect(row1[20], equals([])); + expect(row1[21], equals([])); // upper bound row expect(row2[0], equals(2147483647)); @@ -88,6 +99,19 @@ void main() { ])); expect(row2[13], equals([255])); expect(row2[14], equals('ffffffff-ffff-ffff-ffff-ffffffffffff')); + expect(row2[15], equals('01234')); + expect(row2[16], equals(PgPoint(0.2, 100))); + expect(row2[17], equals({})); + expect(row2[18], equals([-123, 999])); + expect(row2[19], equals(['a', 'lorem ipsum', ''])); + expect(row2[20], equals([1, 2, 4.5, 1234.5])); + expect( + row2[21], + equals([ + 1, + 'test', + {'a': 'b'} + ])); // all null row expect(row3[0], isNull); @@ -105,6 +129,13 @@ void main() { expect(row3[12], isNull); expect(row3[13], isNull); expect(row3[14], isNull); + expect(row3[15], isNull); + expect(row3[16], isNull); + expect(row3[17], isNull); + expect(row3[18], isNull); + expect(row3[19], isNull); + expect(row3[20], isNull); + expect(row3[21], isNull); }); test('Fetch/insert empty string', () async { diff --git a/test/encoding_test.dart b/test/encoding_test.dart index b71cd2b2..32ff3381 100644 --- a/test/encoding_test.dart +++ b/test/encoding_test.dart @@ -1,13 +1,12 @@ import 'dart:async'; import 'dart:convert'; -import 'package:test/test.dart'; - import 'package:postgres/postgres.dart'; import 'package:postgres/src/binary_codec.dart'; import 'package:postgres/src/text_codec.dart'; import 'package:postgres/src/types.dart'; import 'package:postgres/src/utf8_backed_string.dart'; +import 'package:test/test.dart'; late PostgreSQLConnection conn; @@ -184,14 +183,14 @@ void main() { }); test('jsonb', () async { - await expectInverse('string', PostgreSQLDataType.json); - await expectInverse(2, PostgreSQLDataType.json); - await expectInverse(['foo'], PostgreSQLDataType.json); + await expectInverse('string', PostgreSQLDataType.jsonb); + await expectInverse(2, PostgreSQLDataType.jsonb); + await expectInverse(['foo'], PostgreSQLDataType.jsonb); await expectInverse({ 'key': 'val', 'key1': 1, 'array': ['foo'] - }, PostgreSQLDataType.json); + }, PostgreSQLDataType.jsonb); try { await conn.query('INSERT INTO t (v) VALUES (@v:jsonb)', @@ -228,6 +227,123 @@ void main() { expect(e.toString(), contains('Expected: String')); } }); + + test('varchar', () async { + await expectInverse('', PostgreSQLDataType.varChar); + await expectInverse('foo', PostgreSQLDataType.varChar); + await expectInverse('foo\n', PostgreSQLDataType.varChar); + await expectInverse('foo\nbar;s', PostgreSQLDataType.varChar); + try { + await conn.query('INSERT INTO t (v) VALUES (@v:varchar)', + substitutionValues: {'v': 0}); + fail('unreachable'); + } on FormatException catch (e) { + expect(e.toString(), contains('Expected: String')); + } + }); + + test('json', () async { + await expectInverse('string', PostgreSQLDataType.json); + await expectInverse(2, PostgreSQLDataType.json); + await expectInverse(['foo'], PostgreSQLDataType.json); + await expectInverse({ + 'key': 'val', + 'key1': 1, + 'array': ['foo'] + }, PostgreSQLDataType.json); + + try { + await conn.query('INSERT INTO t (v) VALUES (@v:json)', + substitutionValues: {'v': DateTime.now()}); + fail('unreachable'); + } on JsonUnsupportedObjectError catch (_) {} + }); + + test('point', () async { + await expectInverse(PgPoint(0, 0), PostgreSQLDataType.point); + await expectInverse(PgPoint(100, 123.456), PostgreSQLDataType.point); + await expectInverse(PgPoint(0.001, -999), PostgreSQLDataType.point); + + try { + await conn.query('INSERT INTO t (v) VALUES (@v:point)', + substitutionValues: {'v': 'text'}); + fail('unreachable'); + } on FormatException catch (e) { + expect(e.toString(), contains('Expected: PgPoint')); + } + }); + + test('integerArray', () async { + await expectInverse([], PostgreSQLDataType.integerArray); + await expectInverse([-1, 0, 200], PostgreSQLDataType.integerArray); + await expectInverse([-123], PostgreSQLDataType.integerArray); + try { + await conn.query('INSERT INTO t (v) VALUES (@v:_int4)', + substitutionValues: {'v': 'not-list-int'}); + fail('unreachable'); + } on FormatException catch (e) { + expect(e.toString(), contains('Expected: List')); + } + }); + + test('doubleArray', () async { + await expectInverse([], PostgreSQLDataType.doubleArray); + await expectInverse([-123.0, 0.0, 1.0], PostgreSQLDataType.doubleArray); + await expectInverse([0.001, 45.678], PostgreSQLDataType.doubleArray); + try { + await conn.query('INSERT INTO t (v) VALUES (@v:_float8)', + substitutionValues: {'v': 'not-list-double'}); + fail('unreachable'); + } on FormatException catch (e) { + expect(e.toString(), contains('Expected: List')); + } + }); + + test('textArray', () async { + await expectInverse([], PostgreSQLDataType.textArray); + await expectInverse(['', 'foo', 'foo\n'], PostgreSQLDataType.textArray); + await expectInverse(['foo\nbar;s', '"\'"'], PostgreSQLDataType.textArray); + try { + await conn.query('INSERT INTO t (v) VALUES (@v:_text)', + substitutionValues: {'v': 0}); + fail('unreachable'); + } on FormatException catch (e) { + expect(e.toString(), contains('Expected: List')); + } + }); + + test('jsonbArray', () async { + await expectInverse(['string', 2, 0.1], PostgreSQLDataType.jsonbArray); + await expectInverse([ + 1, + {}, + {'a': 'b'} + ], PostgreSQLDataType.jsonbArray); + await expectInverse([ + ['foo'], + [ + 1, + { + 'a': ['b'] + } + ] + ], PostgreSQLDataType.jsonbArray); + await expectInverse([ + { + 'key': 'val', + 'key1': 1, + 'array': ['foo'] + } + ], PostgreSQLDataType.jsonbArray); + + try { + await conn + .query('INSERT INTO t (v) VALUES (@v:_jsonb)', substitutionValues: { + 'v': [DateTime.now()] + }); + fail('unreachable'); + } on JsonUnsupportedObjectError catch (_) {} + }); }); group('Text encoders', () { @@ -334,7 +450,7 @@ void main() { test('Attempt to infer unknown type throws exception', () { try { - encoder.convert([]); + encoder.convert(Object()); fail('unreachable'); } on PostgreSQLException catch (e) { expect(e.toString(), contains('Could not infer type')); diff --git a/test/query_test.dart b/test/query_test.dart index 418ee431..a036f4a0 100644 --- a/test/query_test.dart +++ b/test/query_test.dart @@ -1,6 +1,6 @@ import 'package:postgres/postgres.dart'; -import 'package:test/test.dart'; import 'package:postgres/src/types.dart'; +import 'package:test/test.dart'; void main() { group('Successful queries', () { @@ -14,7 +14,8 @@ void main() { '(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)'); + 'dt date, ts timestamp, tsz timestamptz, j jsonb, u uuid, ' + 'v varchar, p point, jj json, ia _int4, ta _text, da _float8, ja _jsonb)'); await connection.execute( 'CREATE TEMPORARY TABLE u (i1 int not null, i2 int not null);'); await connection @@ -109,7 +110,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) values ' + 'INSERT INTO t (i, bi, bl, si, t, f, d, dt, ts, tsz, j, u, v, p, jj, ia, ta, da, ja) values ' '(${PostgreSQLFormat.id('i')},' '${PostgreSQLFormat.id('bi')},' '${PostgreSQLFormat.id('bl')},' @@ -121,8 +122,15 @@ void main() { '${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', + '${PostgreSQLFormat.id('u')},' + '${PostgreSQLFormat.id('v')},' + '${PostgreSQLFormat.id('p')},' + '${PostgreSQLFormat.id('jj')},' + '${PostgreSQLFormat.id('ia')},' + '${PostgreSQLFormat.id('ta')},' + '${PostgreSQLFormat.id('da')},' + '${PostgreSQLFormat.id('ja')}' + ') returning i,s, bi, bs, bl, si, t, f, d, dt, ts, tsz, j, u, v, p, jj, ia, ta, da, ja', substitutionValues: { 'i': 1, 'bi': 2, @@ -135,7 +143,18 @@ void main() { 'ts': DateTime.utc(2000, 2), 'tsz': DateTime.utc(2000, 3), 'j': {'a': 'b'}, - 'u': '01234567-89ab-cdef-0123-0123456789ab' + 'u': '01234567-89ab-cdef-0123-0123456789ab', + 'v': 'abcdef', + 'p': PgPoint(1.0, 0.1), + 'jj': {'k': 'v'}, + 'ia': [1, 2, 3], + 'ta': ['a', 'b"\'\\"'], + 'da': [0.1, 2.3, 1], + 'ja': [ + 1, + 'a"\'\\"', + {'k': 'v"\'\\"'} + ], }); final expectedRow = [ @@ -152,22 +171,33 @@ void main() { DateTime.utc(2000, 2), DateTime.utc(2000, 3), {'a': 'b'}, - '01234567-89ab-cdef-0123-0123456789ab' + '01234567-89ab-cdef-0123-0123456789ab', + 'abcdef', + PgPoint(1.0, 0.1), + {'k': 'v'}, + [1, 2, 3], + ['a', 'b"\'\\"'], + [0.1, 2.3, 1], + [ + 1, + 'a"\'\\"', + {'k': 'v"\'\\"'} + ] ]; - expect(result.columnDescriptions, hasLength(14)); + expect(result.columnDescriptions, hasLength(21)); 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.columnDescriptions.last.columnName, 'ja'); expect(result, [expectedRow]); result = await connection.query( - 'select i,s, bi, bs, bl, si, t, f, d, dt, ts, tsz, j, u from t'); + 'select i,s, bi, bs, bl, si, t, f, d, dt, ts, tsz, j, u, v, p, jj, ia, ta, da, ja 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) values ' + 'INSERT INTO t (i, bi, bl, si, t, f, d, dt, ts, tsz, j, u, v, p, jj, ia, ta, da, ja) values ' '(${PostgreSQLFormat.id('i', type: PostgreSQLDataType.integer)},' '${PostgreSQLFormat.id('bi', type: PostgreSQLDataType.bigInteger)},' '${PostgreSQLFormat.id('bl', type: PostgreSQLDataType.boolean)},' @@ -178,9 +208,16 @@ void main() { '${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', + '${PostgreSQLFormat.id('j', type: PostgreSQLDataType.jsonb)},' + '${PostgreSQLFormat.id('u', type: PostgreSQLDataType.uuid)},' + '${PostgreSQLFormat.id('v', type: PostgreSQLDataType.varChar)},' + '${PostgreSQLFormat.id('p', type: PostgreSQLDataType.point)},' + '${PostgreSQLFormat.id('jj', type: PostgreSQLDataType.json)},' + '${PostgreSQLFormat.id('ia', type: PostgreSQLDataType.integerArray)},' + '${PostgreSQLFormat.id('ta', type: PostgreSQLDataType.textArray)},' + '${PostgreSQLFormat.id('da', type: PostgreSQLDataType.doubleArray)},' + '${PostgreSQLFormat.id('ja', type: PostgreSQLDataType.jsonbArray)}' + ') returning i,s, bi, bs, bl, si, t, f, d, dt, ts, tsz, j, u, v, p, jj, ia, ta, da, ja', substitutionValues: { 'i': 1, 'bi': 2, @@ -193,7 +230,18 @@ void main() { 'ts': DateTime.utc(2000, 2), 'tsz': DateTime.utc(2000, 3), 'j': {'key': 'value'}, - 'u': '01234567-89ab-cdef-0123-0123456789ab' + 'u': '01234567-89ab-cdef-0123-0123456789ab', + 'v': 'abcdef', + 'p': PgPoint(1.0, 0.1), + 'jj': {'k': 'v'}, + 'ia': [1, 2, 3], + 'ta': ['a', 'b'], + 'da': [0.1, 2.3, 1.0], + 'ja': [ + 1, + 'a', + {'k': 'v'} + ], }); final expectedRow = [ @@ -210,12 +258,23 @@ void main() { DateTime.utc(2000, 2), DateTime.utc(2000, 3), {'key': 'value'}, - '01234567-89ab-cdef-0123-0123456789ab' + '01234567-89ab-cdef-0123-0123456789ab', + 'abcdef', + PgPoint(1.0, 0.1), + {'k': 'v'}, + [1, 2, 3], + ['a', 'b'], + [0.1, 2.3, 1], + [ + 1, + 'a', + {'k': 'v'} + ], ]; expect(result, [expectedRow]); result = await connection.query( - 'select i,s, bi, bs, bl, si, t, f, d, dt, ts, tsz, j, u from t'); + 'select i,s, bi, bs, bl, si, t, f, d, dt, ts, tsz, j, u, v, p, jj, ia, ta, da, ja from t'); expect(result, [expectedRow]); });