Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 4 additions & 13 deletions lib/features/mongodb/mongo_document_tree_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -708,7 +709,7 @@ class _MongoDocumentTreeViewState extends State<MongoDocumentTreeView> {
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(
Expand All @@ -718,22 +719,12 @@ class _MongoDocumentTreeViewState extends State<MongoDocumentTreeView> {
) {
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);
}

5 changes: 3 additions & 2 deletions lib/features/mongodb/mongo_documents_table_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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) {
Expand All @@ -101,7 +102,7 @@ class MongoTableGridData {
return '[ $n ${n == 1 ? "item" : "items"} ]';
}
if (val is String) return val;
return val.toString();
return mongoValueText(val);
}
}

Expand Down
51 changes: 51 additions & 0 deletions lib/features/mongodb/mongo_value_display.dart
Original file line number Diff line number Diff line change
@@ -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<String> names) =>
value != null && names.contains(value.runtimeType.toString());
61 changes: 61 additions & 0 deletions test/features/mongodb/mongo_value_display_test.dart
Original file line number Diff line number Diff line change
@@ -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');
});
}
Loading