From eb61e4dcdc765fe4884ac0b6ef266e010d01b329 Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Sun, 25 Oct 2026 21:28:02 +0300 Subject: [PATCH] fix(mongodb): binary, regex and 64-bit values read as values in the Table and Tree views --- .../mongodb/mongo_document_tree_view.dart | 17 ++---- .../mongodb/mongo_documents_table_view.dart | 5 +- lib/features/mongodb/mongo_value_display.dart | 51 ++++++++++++++++ .../mongodb/mongo_value_display_test.dart | 61 +++++++++++++++++++ 4 files changed, 119 insertions(+), 15 deletions(-) create mode 100644 lib/features/mongodb/mongo_value_display.dart create mode 100644 test/features/mongodb/mongo_value_display_test.dart diff --git a/lib/features/mongodb/mongo_document_tree_view.dart b/lib/features/mongodb/mongo_document_tree_view.dart index 8568bd56..9c8da95f 100644 --- a/lib/features/mongodb/mongo_document_tree_view.dart +++ b/lib/features/mongodb/mongo_document_tree_view.dart @@ -3,6 +3,7 @@ import 'dart:convert'; import 'package:flutter/material.dart' as material; import 'package:flutter/services.dart'; +import 'package:querya_desktop/features/mongodb/mongo_value_display.dart'; import 'package:mongo_dart/mongo_dart.dart' show ObjectId; import 'package:querya_desktop/shared/widgets/querya_text_field.dart'; import 'package:querya_desktop/core/theme/querya_ui_colors.dart'; @@ -708,7 +709,7 @@ class _MongoDocumentTreeViewState extends State { if (value is String) return '"$value"'; if (value is ObjectId) return 'ObjectId("${value.oid}")'; if (value is DateTime) return 'ISODate("${value.toUtc().toIso8601String()}")'; - return value.toString(); + return mongoValueText(value); } Color _leafValueColor( @@ -718,22 +719,12 @@ class _MongoDocumentTreeViewState extends State { ) { if (value == null) return palette.muted; if (value is String) return palette.type1; - if (value is num) return palette.type2; + if (mongoValueIsNumber(value)) return palette.type2; if (value is bool) return palette.type3; if (value is ObjectId) return cs.primary; if (value is DateTime) return palette.type4; return cs.foreground; } - String _leafTypeName(Object? value) { - if (value == null) return 'null'; - if (value is String) return 'str'; - if (value is int) return 'int'; - if (value is double) return 'dbl'; - if (value is bool) return 'bool'; - if (value is ObjectId) return 'oid'; - if (value is DateTime) return 'date'; - return value.runtimeType.toString(); - } + String _leafTypeName(Object? value) => mongoValueTypeChip(value); } - diff --git a/lib/features/mongodb/mongo_documents_table_view.dart b/lib/features/mongodb/mongo_documents_table_view.dart index 8eb62bd8..08a120ea 100644 --- a/lib/features/mongodb/mongo_documents_table_view.dart +++ b/lib/features/mongodb/mongo_documents_table_view.dart @@ -4,6 +4,7 @@ import 'package:flutter/services.dart'; import 'package:mongo_dart/mongo_dart.dart' show ObjectId; import 'package:querya_desktop/core/theme/querya_theme_scope.dart'; import 'package:querya_desktop/features/mongodb/mongo_ejson.dart'; +import 'package:querya_desktop/features/mongodb/mongo_value_display.dart'; import 'package:querya_desktop/features/workspace/result_grid_view.dart'; import 'package:querya_desktop/shared/widgets/app_toast.dart'; import 'package:gap/gap.dart'; @@ -85,7 +86,7 @@ class MongoTableGridData { if (val is bool) return 'Boolean'; if (val is DateTime) return 'Date'; if (val is String) return 'String'; - return val.runtimeType.toString(); + return mongoValueTypeName(val); } static String _formatCell(Object? val) { @@ -101,7 +102,7 @@ class MongoTableGridData { return '[ $n ${n == 1 ? "item" : "items"} ]'; } if (val is String) return val; - return val.toString(); + return mongoValueText(val); } } diff --git a/lib/features/mongodb/mongo_value_display.dart b/lib/features/mongodb/mongo_value_display.dart new file mode 100644 index 00000000..ca48b47f --- /dev/null +++ b/lib/features/mongodb/mongo_value_display.dart @@ -0,0 +1,51 @@ +import 'package:mongo_dart/mongo_dart.dart' show BsonBinary, ObjectId; + +/// How a BSON value reads in the Table and Tree views. The driver hands back +/// Dart objects for some types (`BsonBinary`, a regular expression, `Int64`, +/// `Decimal`); their `toString()` is a Dart debug string (`BsonBinary(deadbeef)`, +/// `RegExp: pattern=…`), not something to show a user (#1826). +String mongoValueText(Object? value) { + if (value == null) return 'null'; + if (value is BsonBinary) return 'Binary(0x${value.hexString})'; + if (value is RegExp) return mongoRegexText(value); + return value.toString(); +} + +/// `/pattern/flags`, as the mongo shell writes a regular expression. +String mongoRegexText(RegExp regex) { + final flags = '${regex.isCaseSensitive ? '' : 'i'}' + '${regex.isMultiLine ? 'm' : ''}' + '${regex.isDotAll ? 's' : ''}'; + return '/${regex.pattern}/$flags'; +} + +/// Whether [value] is a number of any BSON numeric type. `Int64` and `Decimal` +/// are not Dart [num]s. +bool mongoValueIsNumber(Object? value) => + value is num || _isNamed(value, const {'Int64', 'Decimal'}); + +/// The short type name of the chip in the Tree view. +String mongoValueTypeChip(Object? value) { + if (value == null) return 'null'; + if (value is String) return 'str'; + if (value is int) return 'int'; + if (value is double) return 'dbl'; + if (value is bool) return 'bool'; + if (value is ObjectId) return 'oid'; + if (value is DateTime) return 'date'; + if (value is BsonBinary) return 'bin'; + if (value is RegExp) return 'regex'; + if (_isNamed(value, const {'Int64'})) return 'long'; + if (_isNamed(value, const {'Decimal'})) return 'dec'; + return value.runtimeType.toString(); +} + +/// The type shown in the Table view's column tooltip. +String mongoValueTypeName(Object value) { + if (value is BsonBinary) return 'Binary'; + if (value is RegExp) return 'Regex'; + return value.runtimeType.toString(); +} + +bool _isNamed(Object? value, Set names) => + value != null && names.contains(value.runtimeType.toString()); diff --git a/test/features/mongodb/mongo_value_display_test.dart b/test/features/mongodb/mongo_value_display_test.dart new file mode 100644 index 00000000..7557f255 --- /dev/null +++ b/test/features/mongodb/mongo_value_display_test.dart @@ -0,0 +1,61 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:mongo_dart/mongo_dart.dart' show BsonBinary, ObjectId; +import 'package:querya_desktop/features/mongodb/mongo_value_display.dart'; + +/// Stand-ins for the driver's `Int64` (fixnum) and `Decimal` (decimal), which +/// the app does not depend on directly: the display code goes by type name. +class Int64 { + const Int64(this.v); + final int v; + @override + String toString() => '$v'; +} + +class Decimal { + const Decimal(this.v); + final String v; + @override + String toString() => v; +} + +void main() { + test('binary reads as Binary(0x…), not BsonBinary(…)', () { + expect(mongoValueText(BsonBinary.from([0xde, 0xad, 0xbe, 0xef])), + 'Binary(0xdeadbeef)'); + expect(mongoValueText(BsonBinary.from(const [])), 'Binary(0x)'); + expect(mongoValueTypeChip(BsonBinary.from([1])), 'bin'); + expect(mongoValueTypeName(BsonBinary.from([1])), 'Binary'); + }); + + test('a regular expression reads as /pattern/flags', () { + expect(mongoValueText(RegExp('^querya')), '/^querya/'); + expect(mongoValueText(RegExp('^querya', caseSensitive: false)), '/^querya/i'); + expect(mongoValueText(RegExp('a', multiLine: true, caseSensitive: false)), + '/a/im'); + expect(mongoValueTypeChip(RegExp('a')), 'regex'); + // The private Dart class name never reaches the screen. + expect(mongoValueTypeName(RegExp('a')), 'Regex'); + }); + + test('Int64 and Decimal are numbers with their own chips', () { + expect(mongoValueIsNumber(const Int64(9223372036854775807)), isTrue); + expect(mongoValueIsNumber(const Decimal('12345678901234.5678')), isTrue); + expect(mongoValueIsNumber(1), isTrue); + expect(mongoValueIsNumber(1.5), isTrue); + expect(mongoValueIsNumber('1'), isFalse); + expect(mongoValueIsNumber(null), isFalse); + expect(mongoValueTypeChip(const Int64(1)), 'long'); + expect(mongoValueTypeChip(const Decimal('1.5')), 'dec'); + expect(mongoValueText(const Int64(7)), '7'); + }); + + test('the other chips are unchanged', () { + expect(mongoValueTypeChip(null), 'null'); + expect(mongoValueTypeChip('x'), 'str'); + expect(mongoValueTypeChip(1), 'int'); + expect(mongoValueTypeChip(1.5), 'dbl'); + expect(mongoValueTypeChip(true), 'bool'); + expect(mongoValueTypeChip(ObjectId()), 'oid'); + expect(mongoValueTypeChip(DateTime.utc(2026)), 'date'); + }); +}