From 028f14cdca9b5408e36910d483d88d184c4ec828 Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Sat, 17 Oct 2026 12:01:43 +0300 Subject: [PATCH] fix(mongodb): find never reads an unbounded result; .limit(0) is capped and the summary says so (#1357) --- lib/core/database/mongodb_service.dart | 15 +++++++++--- .../mongodb/mongo_query_executor.dart | 14 ++++++++++- test/core/database/mongo_service_test.dart | 14 +++++++++++ .../mongodb/mongo_find_summary_test.dart | 24 +++++++++++++++++++ 4 files changed, 63 insertions(+), 4 deletions(-) create mode 100644 test/features/mongodb/mongo_find_summary_test.dart diff --git a/lib/core/database/mongodb_service.dart b/lib/core/database/mongodb_service.dart index 82ad0e04..e7c0634c 100644 --- a/lib/core/database/mongodb_service.dart +++ b/lib/core/database/mongodb_service.dart @@ -30,6 +30,16 @@ void _throwIfMongoWriteFailed( } /// Service for managing MongoDB connections. +/// How many documents `find` returns when the caller gives no limit (or `0`, +/// which MongoDB reads as "no limit"). Decoding is about 64 us per document on +/// the UI isolate: 50,000 documents are a three-second wait and a 40-60 ms +/// stall (#1357). +const int kMongoDefaultFindLimit = 1000; + +/// The limit `find` really applies for a requested [limit]. +int effectiveMongoFindLimit(int? limit) => + limit == null || limit <= 0 ? kMongoDefaultFindLimit : limit; + class MongoService { MongoService._(); @@ -170,9 +180,8 @@ class MongoService { if (skip != null && skip > 0) { selector.skip(skip); } - if (limit != null && limit > 0) { - selector.limit(limit); - } + // Never unbounded: `stream.toList()` below holds every document. + selector.limit(effectiveMongoFindLimit(limit)); final stream = coll.find(selector); return await stream.toList(); diff --git a/lib/features/mongodb/mongo_query_executor.dart b/lib/features/mongodb/mongo_query_executor.dart index 1c1a2b3f..d81393b6 100644 --- a/lib/features/mongodb/mongo_query_executor.dart +++ b/lib/features/mongodb/mongo_query_executor.dart @@ -19,6 +19,18 @@ class MongoQueryResult { int get count => documents.length; } +/// "Fetched N documents", and a note when `.limit(0)` (no limit in MongoDB) +/// was cut at [kMongoDefaultFindLimit] so the user knows there may be more. +String mongoFindSummary(int fetched, int? requestedLimit) { + final capped = requestedLimit != null && + requestedLimit <= 0 && + fetched >= kMongoDefaultFindLimit; + return capped + ? 'Fetched $fetched documents (capped at $kMongoDefaultFindLimit; ' + 'give .limit(n) for more)' + : 'Fetched $fetched documents'; +} + /// Abstract contract for executing parsed [MqlCommand]s against MongoDB. abstract class MongoQueryExecutor { Future execute({ @@ -66,7 +78,7 @@ class DefaultMongoQueryExecutor implements MongoQueryExecutor { return MongoQueryResult( documents: docs, elapsed: stopwatch.elapsed, - summary: 'Fetched ${docs.length} documents', + summary: mongoFindSummary(docs.length, command.limit), ); case MqlMethod.findOne: diff --git a/test/core/database/mongo_service_test.dart b/test/core/database/mongo_service_test.dart index 84feb5ca..a913a076 100644 --- a/test/core/database/mongo_service_test.dart +++ b/test/core/database/mongo_service_test.dart @@ -176,4 +176,18 @@ void main() { expect(MongoService.instance.getConnection(302), isNull); }); }); + + group('effectiveMongoFindLimit (#1357)', () { + test('no limit, or 0 (MongoDB: unlimited), becomes the default cap', () { + expect(effectiveMongoFindLimit(null), kMongoDefaultFindLimit); + expect(effectiveMongoFindLimit(0), kMongoDefaultFindLimit); + expect(effectiveMongoFindLimit(-5), kMongoDefaultFindLimit); + }); + + test('an explicit positive limit is kept', () { + expect(effectiveMongoFindLimit(1), 1); + expect(effectiveMongoFindLimit(100), 100); + expect(effectiveMongoFindLimit(50000), 50000); + }); + }); } diff --git a/test/features/mongodb/mongo_find_summary_test.dart b/test/features/mongodb/mongo_find_summary_test.dart new file mode 100644 index 00000000..381c3df2 --- /dev/null +++ b/test/features/mongodb/mongo_find_summary_test.dart @@ -0,0 +1,24 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:querya_desktop/core/database/mongodb_service.dart'; +import 'package:querya_desktop/features/mongodb/mongo_query_executor.dart'; + +void main() { + group('mongoFindSummary (#1357)', () { + test('an ordinary find just says how many documents came back', () { + expect(mongoFindSummary(25, null), 'Fetched 25 documents'); + expect(mongoFindSummary(100, 100), 'Fetched 100 documents'); + expect(mongoFindSummary(kMongoDefaultFindLimit, 5000), 'Fetched 1000 documents'); + }); + + test('.limit(0) that hit the cap says so', () { + expect( + mongoFindSummary(kMongoDefaultFindLimit, 0), + contains('capped at $kMongoDefaultFindLimit'), + ); + }); + + test('.limit(0) that did not reach the cap is not marked', () { + expect(mongoFindSummary(12, 0), 'Fetched 12 documents'); + }); + }); +}