Skip to content

Commit 2283a8a

Browse files
authored
feat(shell-api): account for unsharded collections in sharding catalog COMPASS-7602 (#1808)
1 parent 8a8cf45 commit 2283a8a

File tree

3 files changed

+67
-38
lines changed

3 files changed

+67
-38
lines changed

packages/shell-api/src/collection.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import {
4040
shouldRunAggregationImmediately,
4141
coerceToJSNumber,
4242
buildConfigChunksCollectionMatch,
43+
onlyShardedCollectionsInConfigFilter,
4344
} from './helpers';
4445
import type {
4546
AnyBulkWriteOperation,
@@ -1762,9 +1763,7 @@ export default class Collection extends ShellApiWithMongoClass {
17621763
try {
17631764
result.sharded = !!(await config.getCollection('collections').findOne({
17641765
_id: timeseriesBucketsNs ?? ns,
1765-
// Dropped is gone on newer server versions, so check for !== true
1766-
// rather than for === false (SERVER-51880 and related).
1767-
dropped: { $ne: true },
1766+
...onlyShardedCollectionsInConfigFilter,
17681767
}));
17691768
} catch (e) {
17701769
// A user might not have permissions to check the config. In which
@@ -2068,9 +2067,7 @@ export default class Collection extends ShellApiWithMongoClass {
20682067
.getCollection('collections')
20692068
.findOne({
20702069
_id: ns,
2071-
// dropped is gone on newer server versions, so check for !== true
2072-
// rather than for === false (SERVER-51880 and related)
2073-
dropped: { $ne: true },
2070+
...onlyShardedCollectionsInConfigFilter,
20742071
});
20752072
if (!configCollectionsInfo) {
20762073
throw new MongoshInvalidInputError(

packages/shell-api/src/helpers.ts

Lines changed: 50 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import type {
2727
bson,
2828
} from '@mongosh/service-provider-core';
2929
import type { ClientSideFieldLevelEncryptionOptions } from './field-level-encryption';
30-
import type { AutoEncryptionOptions } from 'mongodb';
30+
import { type AutoEncryptionOptions } from 'mongodb';
3131
import { shellApiType } from './enums';
3232
import type { AbstractCursor } from './abstract-cursor';
3333
import type ChangeStreamCursor from './change-stream-cursor';
@@ -92,6 +92,17 @@ function getAssertCaller(caller?: string): string {
9292
return caller ? ` (${caller})` : '';
9393
}
9494

95+
// Fields to add to a $match/filter on config.collections to only get
96+
// collections that are actually sharded
97+
export const onlyShardedCollectionsInConfigFilter = {
98+
// dropped is gone on newer server versions, so check for !== true
99+
// rather than for === false (SERVER-51880 and related)
100+
dropped: { $ne: true },
101+
// unsplittable introduced in PM-3364 to mark unsharded collections
102+
// that are still being tracked in the catalog
103+
unsplittable: { $ne: true },
104+
} as const;
105+
95106
export function assertArgsDefinedType(
96107
args: any[],
97108
expectedTypes: Array<true | string | Array<string | undefined>>,
@@ -487,33 +498,45 @@ export async function getPrintableShardStatus(
487498
]);
488499
result.balancer = balancerRes;
489500

490-
const databases = await (await configDB.getCollection('databases').find())
491-
.sort({ name: 1 })
492-
.toArray();
493-
501+
// All databases in config.databases + those implicitly referenced
502+
// by a sharded collection in config.collections
503+
// (could become a single pipeline using $unionWith when we drop 4.2 server support)
504+
const [databases, collections] = await Promise.all([
505+
(async () =>
506+
await (await configDB.getCollection('databases').find())
507+
.sort({ _id: 1 })
508+
.toArray())(),
509+
(async () =>
510+
await (
511+
await configDB.getCollection('collections').find({
512+
...onlyShardedCollectionsInConfigFilter,
513+
})
514+
)
515+
.sort({ _id: 1 })
516+
.toArray())(),
517+
]);
494518
// Special case the config db, since it doesn't have a record in config.databases.
495519
databases.push({ _id: 'config', primary: 'config', partitioned: true });
520+
521+
for (const coll of collections) {
522+
if (!databases.find((db) => coll._id.startsWith(db._id + '.'))) {
523+
databases.push({ _id: coll._id.split('.')[0] });
524+
}
525+
}
526+
496527
databases.sort((a: any, b: any): number => {
497528
return a._id.localeCompare(b._id);
498529
});
499530

500-
result.databases = await Promise.all(
501-
databases.map(async (db) => {
502-
const escapeRegex = (string: string): string => {
503-
return string.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
504-
};
505-
const colls = await (
506-
await configDB
507-
.getCollection('collections')
508-
.find({ _id: new RegExp('^' + escapeRegex(db._id) + '\\.') })
509-
)
510-
.sort({ _id: 1 })
511-
.toArray();
531+
result.databases = (
532+
await Promise.all(
533+
databases.map(async (db) => {
534+
const colls = collections.filter((coll) =>
535+
coll._id.startsWith(db._id + '.')
536+
);
512537

513-
const collList: any = await Promise.all(
514-
colls
515-
.filter((coll) => !coll.dropped)
516-
.map(async (coll) => {
538+
const collList = await Promise.all(
539+
colls.map(async (coll) => {
517540
const collRes = {} as any;
518541
collRes.shardKey = coll.key;
519542
collRes.unique = !!coll.unique;
@@ -605,12 +628,13 @@ export async function getPrintableShardStatus(
605628
}
606629
collRes.chunks = chunksRes;
607630
collRes.tags = tagsRes;
608-
return [coll._id, collRes];
631+
return [coll._id, collRes] as const;
609632
})
610-
);
611-
return { database: db, collections: Object.fromEntries(collList) };
612-
})
613-
);
633+
);
634+
return { database: db, collections: Object.fromEntries(collList) };
635+
})
636+
)
637+
).filter((dbEntry) => !!dbEntry);
614638

615639
delete result.shardingVersion.currentVersion;
616640
return result;

packages/shell-api/src/shard.spec.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1835,6 +1835,7 @@ describe('Shard', function () {
18351835
.toArray();
18361836
expect(members.length).to.equal(2);
18371837
await sh._database.getSiblingDB(dbName).dropDatabase();
1838+
await sh._database.getSiblingDB(dbName).createCollection('unsharded');
18381839
});
18391840

18401841
after(function () {
@@ -1901,19 +1902,25 @@ describe('Shard', function () {
19011902
});
19021903
describe('turn on sharding', function () {
19031904
it('enableSharding for a db', async function () {
1904-
expect((await sh.status()).value.databases.length).to.equal(1);
1905-
expect((await sh.enableSharding(dbName)).ok).to.equal(1);
1906-
expect((await sh.status()).value.databases.length).to.equal(2);
1905+
expect((await sh.status()).value.databases.length).to.oneOf([1, 2]);
1906+
expect((await sh.enableSharding(dbName)).ok).to.equal(1); // This may not have any effect on newer server versions
1907+
expect((await sh.status()).value.databases.length).to.be.oneOf([1, 2]);
19071908
});
19081909
it('enableSharding for a collection and modify documents in it', async function () {
19091910
expect(
1910-
Object.keys((await sh.status()).value.databases[1].collections).length
1911-
).to.equal(0);
1911+
Object.keys(
1912+
(await sh.status()).value.databases.find(
1913+
(d) => d.database._id === 'test'
1914+
)?.collections ?? []
1915+
)
1916+
).to.deep.equal([]);
19121917
expect(
19131918
(await sh.shardCollection(ns, { key: 1 })).collectionsharded
19141919
).to.equal(ns);
19151920
expect(
1916-
(await sh.status()).value.databases[1].collections[ns].shardKey
1921+
(await sh.status()).value.databases.find(
1922+
(d) => d.database._id === 'test'
1923+
).collections[ns].shardKey
19171924
).to.deep.equal({ key: 1 });
19181925

19191926
const db = instanceState.currentDb.getSiblingDB(dbName);
@@ -2122,6 +2129,7 @@ describe('Shard', function () {
21222129
});
21232130
it('fails when running against an unsharded collection', async function () {
21242131
try {
2132+
await db.createCollection('test');
21252133
await db.getCollection('test').getShardDistribution();
21262134
} catch (err: any) {
21272135
expect(err.name).to.equal('MongoshInvalidInputError');

0 commit comments

Comments
 (0)