Skip to content

Commit d9c4ef6

Browse files
authored
Merge pull request isoos#7 from Gustl22/50-numeric-datatype
Decoder for numeric / decimal SQL Data type
2 parents 5be4a3e + 7a568f5 commit d9c4ef6

File tree

6 files changed

+58
-0
lines changed

6 files changed

+58
-0
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
## Unreleased
4+
5+
- Decoder for type `numeric` / `decimal`, [#7](https://github.com/isoos/postgresql-dart/pull/7).
6+
37
## 2.3.2
48

59
- Expose `ColumnDescription.typeId`.

lib/src/binary_codec.dart

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,9 @@ class PostgresBinaryDecoder extends Converter<Uint8List, dynamic> {
329329
return DateTime.utc(2000)
330330
.add(Duration(microseconds: buffer.getInt64(0)));
331331

332+
case PostgreSQLDataType.numeric:
333+
return _decodeNumeric(value);
334+
332335
case PostgreSQLDataType.date:
333336
return DateTime.utc(2000).add(Duration(days: buffer.getInt32(0)));
334337

@@ -425,6 +428,7 @@ class PostgresBinaryDecoder extends Converter<Uint8List, dynamic> {
425428
return decoded;
426429
}
427430

431+
/// See: https://github.com/postgres/postgres/blob/master/src/include/catalog/pg_type.dat
428432
static final Map<int, PostgreSQLDataType> typeMap = {
429433
16: PostgreSQLDataType.boolean,
430434
17: PostgreSQLDataType.byteArray,
@@ -444,8 +448,33 @@ class PostgresBinaryDecoder extends Converter<Uint8List, dynamic> {
444448
1082: PostgreSQLDataType.date,
445449
1114: PostgreSQLDataType.timestampWithoutTimezone,
446450
1184: PostgreSQLDataType.timestampWithTimezone,
451+
1700: PostgreSQLDataType.numeric,
447452
2950: PostgreSQLDataType.uuid,
448453
3802: PostgreSQLDataType.jsonb,
449454
3807: PostgreSQLDataType.jsonbArray,
450455
};
456+
457+
/// Decode numeric / decimal to String without loosing precision.
458+
/// See encoding: https://github.com/postgres/postgres/blob/0e39a608ed5545cc6b9d538ac937c3c1ee8cdc36/src/backend/utils/adt/numeric.c#L305
459+
/// See implementation: https://github.com/charmander/pg-numeric/blob/0c310eeb11dc680dffb7747821e61d542831108b/index.js#L13
460+
static String _decodeNumeric(Uint8List value) {
461+
final reader = ByteDataReader()..add(value);
462+
final nDigits = reader.readInt16(); // non-zero digits, data buffer length = 2 * nDigits
463+
var weight = reader.readInt16(); // weight of first digit
464+
final signByte = reader.readInt16(); // NUMERIC_POS, NEG, NAN, PINF, or NINF
465+
final dScale = reader.readInt16(); // display scale
466+
if (signByte == 0xc000) return 'NaN';
467+
final sign = signByte == 0x4000 ? '-' : '';
468+
var intPart = '';
469+
var fractPart = '';
470+
for (var i = 0; i < nDigits; i++) {
471+
if (weight >= 0) {
472+
intPart += reader.readInt16().toString().padLeft(4, '0');
473+
} else {
474+
fractPart += reader.readInt16().toString().padLeft(4, '0');
475+
}
476+
weight--;
477+
}
478+
return '$sign${intPart.replaceAll(RegExp(r'^0+'), '')}.${fractPart.padRight(dScale, '0').substring(0, dScale)}';
479+
}
451480
}

lib/src/query.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,7 @@ class PostgreSQLFormatIdentifier {
321321
'date': PostgreSQLDataType.date,
322322
'timestamp': PostgreSQLDataType.timestampWithoutTimezone,
323323
'timestamptz': PostgreSQLDataType.timestampWithTimezone,
324+
'numeric': PostgreSQLDataType.numeric,
324325
'jsonb': PostgreSQLDataType.jsonb,
325326
'bytea': PostgreSQLDataType.byteArray,
326327
'name': PostgreSQLDataType.name,

lib/src/substituter.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ class PostgreSQLFormat {
3737
return 'timestamp';
3838
case PostgreSQLDataType.timestampWithTimezone:
3939
return 'timestamptz';
40+
case PostgreSQLDataType.numeric:
41+
return 'numeric';
4042
case PostgreSQLDataType.date:
4143
return 'date';
4244
case PostgreSQLDataType.jsonb:

lib/src/types.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ enum PostgreSQLDataType {
4242
/// Must be a [DateTime] (microsecond date and time precision)
4343
timestampWithTimezone,
4444

45+
/// Must be a [List<int>]
46+
numeric,
47+
4548
/// Must be a [DateTime] (contains year, month and day only)
4649
date,
4750

test/decode_test.dart

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1+
import 'dart:typed_data';
2+
13
import 'package:postgres/postgres.dart';
4+
import 'package:postgres/src/binary_codec.dart';
25
import 'package:test/test.dart';
36

47
void main() {
@@ -167,4 +170,20 @@ void main() {
167170
[null]
168171
]);
169172
});
173+
174+
test('Decode Numeric to String', () {
175+
// -123400000.2
176+
final binary1 = [0, 4, 0, 2, 64, 0, 0, 5, 0, 1, 9, 36, 0, 0, 7, 208];
177+
178+
// -123400001.01234
179+
final binary2 = [0, 5, 0, 2, 64, 0, 0, 5, 0, 1, 9, 36, 0, 1, 0, 0, 7, 208];
180+
181+
final decoder = PostgresBinaryDecoder(1700);
182+
final uint8List1 = Uint8List.fromList(binary1);
183+
final uint8List2 = Uint8List.fromList(binary2);
184+
final res1 = decoder.convert(uint8List1);
185+
final res2 = decoder.convert(uint8List2);
186+
expect(res1, '-123400000.20000');
187+
expect(res2, '-123400001.00002');
188+
});
170189
}

0 commit comments

Comments
 (0)