diff --git a/package-lock.json b/package-lock.json index 71e5a8b4954..37d8c926ae2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44097,6 +44097,7 @@ "hadron-type-checker": "^7.2.3", "jsondiffpatch": "^0.5.0", "lodash": "^4.17.21", + "mongodb": "^6.9.0", "mongodb-data-service": "^22.23.5", "mongodb-ns": "^2.4.2", "mongodb-query-parser": "^4.2.3", @@ -56149,6 +56150,7 @@ "jsondiffpatch": "^0.5.0", "lodash": "^4.17.21", "mocha": "^10.2.0", + "mongodb": "^6.9.0", "mongodb-data-service": "^22.23.5", "mongodb-instance-model": "^12.24.5", "mongodb-ns": "^2.4.2", diff --git a/packages/collection-model/index.d.ts b/packages/collection-model/index.d.ts index a1059c38dcc..e7facd3f6b0 100644 --- a/packages/collection-model/index.d.ts +++ b/packages/collection-model/index.d.ts @@ -1,4 +1,4 @@ -import type { DataService } from 'mongodb-data-service'; +import type { DataService, CollStatsIndexDetails } from 'mongodb-data-service'; type CollectionMetadata = { /** @@ -76,6 +76,7 @@ interface CollectionProps { free_storage_size: number; index_count: number; index_size: number; + index_details: Record; isTimeSeries: boolean; isView: boolean; sourceName: string | null; diff --git a/packages/collection-model/lib/model.js b/packages/collection-model/lib/model.js index 25f4357cfbd..ece186429f0 100644 --- a/packages/collection-model/lib/model.js +++ b/packages/collection-model/lib/model.js @@ -141,6 +141,7 @@ const CollectionModel = AmpersandModel.extend(debounceActions(['fetch']), { free_storage_size: 'number', index_count: 'number', index_size: 'number', + index_details: 'object' }, derived: { ns: { diff --git a/packages/compass-crud/package.json b/packages/compass-crud/package.json index f2a6da20246..a979c49a58e 100644 --- a/packages/compass-crud/package.json +++ b/packages/compass-crud/package.json @@ -94,6 +94,7 @@ "hadron-type-checker": "^7.2.3", "jsondiffpatch": "^0.5.0", "lodash": "^4.17.21", + "mongodb": "^6.9.0", "mongodb-data-service": "^22.23.5", "mongodb-ns": "^2.4.2", "mongodb-query-parser": "^4.2.3", diff --git a/packages/compass-crud/src/stores/crud-store.spec.ts b/packages/compass-crud/src/stores/crud-store.spec.ts index fcbac4d3d89..6a1e53ea4d6 100644 --- a/packages/compass-crud/src/stores/crud-store.spec.ts +++ b/packages/compass-crud/src/stores/crud-store.spec.ts @@ -134,6 +134,7 @@ const mockCollection = { document_count: 10, free_storage_size: 10, storage_size: 20, + index_details: {}, fetchMetadata() { return Promise.resolve(defaultMetadata); }, @@ -334,6 +335,7 @@ describe('store', function () { avg_document_size: 1, document_count: 10, free_storage_size: 10, + index_details: {}, storage_size: 20, }, }); @@ -2250,7 +2252,16 @@ describe('store', function () { }); it('should call find with $bsonSize projection when mongodb version is >= 4.4, not connected to ADF and csfle is disabled', async function () { - await fetchDocuments(dataService, '5.0.0', false, 'test.test', {}); + await fetchDocuments( + dataService, + { + serverVersion: '5.0.0', + isDataLake: false, + defaultSort: undefined, + }, + 'test.test', + {} + ); expect(find).to.have.been.calledOnce; expect(find.getCall(0)) .to.have.nested.property('args.2.projection') @@ -2261,8 +2272,11 @@ describe('store', function () { findResult = [{ __size: new Int32(42), __doc: { _id: 1 } }]; const docs = await fetchDocuments( dataService, - '4.0.0', - false, + { + serverVersion: '4.0.0', + isDataLake: false, + defaultSort: undefined, + }, 'test.test', {} ); @@ -2272,7 +2286,16 @@ describe('store', function () { }); it('should NOT call find with $bsonSize projection when mongodb version is < 4.4', async function () { - await fetchDocuments(dataService, '4.0.0', false, 'test.test', {}); + await fetchDocuments( + dataService, + { + serverVersion: '4.0.0', + isDataLake: false, + defaultSort: undefined, + }, + 'test.test', + {} + ); expect(find).to.have.been.calledOnce; expect(find.getCall(0)).to.have.nested.property( 'args.2.projection', @@ -2281,7 +2304,16 @@ describe('store', function () { }); it('should NOT call find with $bsonSize projection when connected to ADF', async function () { - await fetchDocuments(dataService, '5.0.0', true, 'test.test', {}); + await fetchDocuments( + dataService, + { + serverVersion: '5.0.0', + isDataLake: true, + defaultSort: undefined, + }, + 'test.test', + {} + ); expect(find).to.have.been.calledOnce; expect(find.getCall(0)).to.have.nested.property( 'args.2.projection', @@ -2291,7 +2323,16 @@ describe('store', function () { it('should NOT call find with $bsonSize projection when csfle is enabled', async function () { csfleMode = 'enabled'; - await fetchDocuments(dataService, '5.0.0', false, 'test.test', {}); + await fetchDocuments( + dataService, + { + serverVersion: '5.0.0', + isDataLake: false, + defaultSort: undefined, + }, + 'test.test', + {} + ); expect(find).to.have.been.calledOnce; expect(find.getCall(0)).to.have.nested.property( 'args.2.projection', @@ -2302,8 +2343,11 @@ describe('store', function () { it('should keep user projection when provided', async function () { await fetchDocuments( dataService, - '5.0.0', - false, + { + serverVersion: '5.0.0', + isDataLake: false, + defaultSort: undefined, + }, 'test.test', {}, { @@ -2326,8 +2370,11 @@ describe('store', function () { const docs = await fetchDocuments( dataService, - '5.0.0', - false, + { + serverVersion: '5.0.0', + isDataLake: false, + defaultSort: undefined, + }, 'test.test', {} ); @@ -2346,7 +2393,16 @@ describe('store', function () { find = sinon.stub().rejects(new TypeError('🤷‍♂️')); try { - await fetchDocuments(dataService, '5.0.0', false, 'test.test', {}); + await fetchDocuments( + dataService, + { + serverVersion: '5.0.0', + isDataLake: false, + defaultSort: undefined, + }, + 'test.test', + {} + ); expect.fail('Expected fetchDocuments to fail with error'); } catch (err) { expect(find).to.have.been.calledOnce; @@ -2358,7 +2414,16 @@ describe('store', function () { find = sinon.stub().rejects(new MongoServerError('Nope')); try { - await fetchDocuments(dataService, '3.0.0', true, 'test.test', {}); + await fetchDocuments( + dataService, + { + serverVersion: '3.0.0', + isDataLake: true, + defaultSort: undefined, + }, + 'test.test', + {} + ); expect.fail('Expected fetchDocuments to fail with error'); } catch (err) { expect(find).to.have.been.calledOnce; diff --git a/packages/compass-crud/src/stores/crud-store.ts b/packages/compass-crud/src/stores/crud-store.ts index 14ef0e39b79..34343607b06 100644 --- a/packages/compass-crud/src/stores/crud-store.ts +++ b/packages/compass-crud/src/stores/crud-store.ts @@ -3,6 +3,7 @@ import Reflux from 'reflux'; import toNS from 'mongodb-ns'; import { findIndex, isEmpty, isEqual } from 'lodash'; import semver from 'semver'; +import type { Sort } from 'mongodb'; import StateMixin from '@mongodb-js/reflux-state-mixin'; import type { Element } from 'hadron-document'; import { Document } from 'hadron-document'; @@ -109,20 +110,26 @@ const INITIAL_BULK_UPDATE_TEXT = `{ }, }`; +type FetchDocumentsOptions = { + serverVersion: string; + isDataLake: boolean; + defaultSort?: Sort; +}; + export const fetchDocuments: ( dataService: DataService, - serverVersion: string, - isDataLake: boolean, + fetchDocumentsOptions: FetchDocumentsOptions, ...args: Parameters ) => Promise = async ( dataService: DataService, - serverVersion, - isDataLake, + fetchDocumentsOptions, ns, filter, options, executionOptions ) => { + const { isDataLake, serverVersion, defaultSort } = fetchDocumentsOptions; + const canCalculateDocSize = // $bsonSize is only supported for mongodb >= 4.4.0 semver.gte(serverVersion, '4.4.0') && @@ -138,6 +145,7 @@ export const fetchDocuments: ( const modifiedOptions = { ...options, + sort: options?.sort ? options.sort : defaultSort, projection: canCalculateDocSize ? { _id: 0, __doc: '$$ROOT', __size: { $bsonSize: '$$ROOT' } } : options?.projection, @@ -174,7 +182,11 @@ export const fetchDocuments: ( type CollectionStats = Pick< Collection, - 'document_count' | 'storage_size' | 'free_storage_size' | 'avg_document_size' + | 'document_count' + | 'storage_size' + | 'free_storage_size' + | 'avg_document_size' + | 'index_details' >; const extractCollectionStats = (collection: Collection): CollectionStats => { const coll = collection.toJSON(); @@ -183,9 +195,20 @@ const extractCollectionStats = (collection: Collection): CollectionStats => { storage_size: coll.storage_size, free_storage_size: coll.free_storage_size, avg_document_size: coll.avg_document_size, + index_details: coll.index_details, }; }; +function getDefaultSort( + collectionStats: CollectionStats | null +): Sort | undefined { + if (collectionStats?.index_details._id_) { + return { _id: -1 }; + } + + return undefined; +} + /** * Default number of docs per page. */ @@ -408,6 +431,8 @@ class CrudStoreImpl const isDataLake = !!this.instance.dataLake.isDataLake; const isReadonly = !!this.options.isReadonly; + const collectionStats = extractCollectionStats(this.collection); + return { ns: this.options.namespace, collection: toNS(this.options.namespace).collection, @@ -439,7 +464,7 @@ class CrudStoreImpl isUpdatePreviewSupported: this.instance.topologyDescription.type !== 'Single', docsPerPage: this.getInitialDocsPerPage(), - collectionStats: extractCollectionStats(this.collection), + collectionStats, }; } @@ -884,8 +909,11 @@ class CrudStoreImpl try { documents = await fetchDocuments( this.dataService, - this.state.version, - this.state.isDataLake, + { + serverVersion: this.state.version, + isDataLake: this.state.isDataLake, + defaultSort: getDefaultSort(this.state.collectionStats), + }, ns, filter ?? {}, opts as any, @@ -1539,8 +1567,9 @@ class CrudStoreImpl } collectionStatsFetched(model: Collection) { + const collectionStats = extractCollectionStats(model); this.setState({ - collectionStats: extractCollectionStats(model), + collectionStats, }); } @@ -1712,8 +1741,11 @@ class CrudStoreImpl ), fetchDocuments( this.dataService, - this.state.version, - this.state.isDataLake, + { + serverVersion: this.state.version, + isDataLake: this.state.isDataLake, + defaultSort: getDefaultSort(this.state.collectionStats), + }, ns, query.filter ?? {}, findOptions as any, diff --git a/packages/compass-crud/src/utils/cancellable-queries.spec.ts b/packages/compass-crud/src/utils/cancellable-queries.spec.ts index 5db15055ff7..0f49c566677 100644 --- a/packages/compass-crud/src/utils/cancellable-queries.spec.ts +++ b/packages/compass-crud/src/utils/cancellable-queries.spec.ts @@ -99,7 +99,7 @@ describe('cancellable-queries', function () { dataService, preferences, 'cancel.numbers', - null, + undefined, { signal, } @@ -138,6 +138,7 @@ describe('cancellable-queries', function () { dataService, preferences, 'cancel.numbers', + // @ts-expect-error this is deliberately wrong 'this is not a filter', { signal, diff --git a/packages/compass-crud/src/utils/cancellable-queries.ts b/packages/compass-crud/src/utils/cancellable-queries.ts index 8e477c80983..e1ec9ed8058 100644 --- a/packages/compass-crud/src/utils/cancellable-queries.ts +++ b/packages/compass-crud/src/utils/cancellable-queries.ts @@ -7,7 +7,7 @@ export async function countDocuments( dataService: DataService, preferences: PreferencesAccess, ns: string, - filter: BSONObject, + filter: BSONObject | undefined, { signal, skip, diff --git a/packages/compass-indexes/test/setup-store.ts b/packages/compass-indexes/test/setup-store.ts index 2b8a7676ece..b1e3be5f02b 100644 --- a/packages/compass-indexes/test/setup-store.ts +++ b/packages/compass-indexes/test/setup-store.ts @@ -73,6 +73,7 @@ const NOOP_DATA_PROVIDER: IndexesDataService = { free_storage_size: 0, index_count: 0, index_size: 0, + index_details: {}, name: collectionName, ns: `${databaseName}.${collectionName}`, storage_size: 0, diff --git a/packages/data-service/src/data-service.ts b/packages/data-service/src/data-service.ts index 2761fd7ba59..4d22fb9c366 100644 --- a/packages/data-service/src/data-service.ts +++ b/packages/data-service/src/data-service.ts @@ -1108,6 +1108,7 @@ class DataServiceImpl extends WithLogContext implements DataService { ], }, }, + indexDetails: { $first: '$storageStats.indexDetails' }, nindexes: { $max: '$storageStats.nindexes' }, }, }, @@ -2581,6 +2582,7 @@ class DataServiceImpl extends WithLogContext implements DataService { storage_size: data.storageSize ?? 0, free_storage_size: data.freeStorageSize ?? 0, index_count: data.nindexes ?? 0, + index_details: data.indexDetails ?? {}, index_size: data.totalIndexSize ?? 0, }; } diff --git a/packages/data-service/src/types.ts b/packages/data-service/src/types.ts index 5c51f09c003..9393b36206c 100644 --- a/packages/data-service/src/types.ts +++ b/packages/data-service/src/types.ts @@ -9,6 +9,7 @@ export interface CollectionStats { storage_size: number; free_storage_size: number; index_count: number; + index_details: Record; index_size: number; }