mirror of https://github.com/mongodb/mongo
180 lines
7.6 KiB
JavaScript
180 lines
7.6 KiB
JavaScript
//
|
|
// Tests that documents in a sharded collection with missing shard key fields are treated as if they
|
|
// contain an explicit null value for any missing fields.
|
|
//
|
|
// @tags: [
|
|
// uses_multi_shard_transactions,
|
|
// uses_transactions,
|
|
// ]
|
|
|
|
import {ShardingTest} from "jstests/libs/shardingtest.js";
|
|
|
|
const st = new ShardingTest({shards: 2});
|
|
const mongos = st.s0;
|
|
const primaryShard = st.shard0.shardName;
|
|
const secondaryShard = st.shard1.shardName;
|
|
const kDbName = "db";
|
|
const kCollName = "foo";
|
|
const kNsName = kDbName + "." + kCollName;
|
|
const kOldKeyDoc = {
|
|
a: 1,
|
|
};
|
|
const kNewKeyDoc = {
|
|
a: 1,
|
|
b: 1,
|
|
};
|
|
|
|
function orphanDocCount() {
|
|
// Since count() includes orphaned documents while find({}).itcount() excludes them, their
|
|
// difference corresponds to the number of orphaned documents in 'db.foo'.
|
|
return mongos.getCollection(kNsName).count() - mongos.getCollection(kNsName).find({}).itcount();
|
|
}
|
|
|
|
function dropAndReshardColl() {
|
|
assert.commandWorked(mongos.getDB(kDbName).runCommand({drop: kCollName}));
|
|
assert.commandWorked(mongos.adminCommand({shardCollection: kNsName, key: kOldKeyDoc}));
|
|
assert.commandWorked(mongos.getCollection(kNsName).createIndex(kNewKeyDoc));
|
|
|
|
// Insert six documents such that three correspond to the old shard key and three correspond to
|
|
// the new shard key. Verify that there are no orphaned documents in 'db.foo'.
|
|
assert.commandWorked(mongos.getCollection(kNsName).insert({a: 1}));
|
|
assert.commandWorked(mongos.getCollection(kNsName).insert({a: 10}));
|
|
assert.commandWorked(mongos.getCollection(kNsName).insert({a: null}));
|
|
assert.commandWorked(mongos.getCollection(kNsName).insert({a: 1, b: 1}));
|
|
assert.commandWorked(mongos.getCollection(kNsName).insert({a: 10, b: 1}));
|
|
assert.commandWorked(mongos.getCollection(kNsName).insert({a: null, b: 1}));
|
|
assert.eq(0, orphanDocCount());
|
|
}
|
|
|
|
function isOwnedByShard(shardName, doc) {
|
|
let isOwned = false;
|
|
mongos
|
|
.getCollection(kNsName)
|
|
.find(doc)
|
|
.explain("executionStats")
|
|
.executionStats.executionStages.shards.forEach((shard) => {
|
|
if (shard.shardName.localeCompare(shardName) === 0) {
|
|
isOwned = 1 === shard.nReturned;
|
|
}
|
|
});
|
|
return isOwned;
|
|
}
|
|
|
|
function isOwnedByPrimaryShard(doc) {
|
|
return isOwnedByShard(primaryShard, doc);
|
|
}
|
|
|
|
function isOwnedBySecondaryShard(doc) {
|
|
return isOwnedByShard(secondaryShard, doc);
|
|
}
|
|
|
|
assert.commandWorked(mongos.adminCommand({enableSharding: kDbName, primaryShard: primaryShard}));
|
|
|
|
jsTestLog("********** ORPHAN FILTERING **********");
|
|
|
|
dropAndReshardColl();
|
|
|
|
// Verify that moving a chunk to the secondary shard produces two orphaned documents in 'db.foo' as
|
|
// a result of migrating documents {a: 10} and {a: 10, b: 1}.
|
|
assert.commandWorked(mongos.adminCommand({split: kNsName, middle: {a: 5}}));
|
|
assert.commandWorked(mongos.adminCommand({moveChunk: kNsName, find: {a: 5}, to: secondaryShard}));
|
|
assert.lte(orphanDocCount(), 2);
|
|
|
|
// Verify that refining the shard key produces no additional orphaned documents in 'db.foo'.
|
|
assert.commandWorked(mongos.adminCommand({refineCollectionShardKey: kNsName, key: kNewKeyDoc}));
|
|
assert.lte(orphanDocCount(), 2);
|
|
|
|
jsTestLog("********** REQUEST TARGETING **********");
|
|
|
|
dropAndReshardColl();
|
|
|
|
// Ensure that there exist two chunks belonging to 'db.foo' covering the entire key range.
|
|
//
|
|
// Chunk 1: {a: MinKey, b: MinKey} -->> {a: 5, b: MinKey} (belongs to the primary shard)
|
|
// Chunk 2: {a: 5, b: MinKey} -->> {a: MaxKey, b: MaxKey} (belongs to the secondary shard)
|
|
assert.commandWorked(mongos.adminCommand({split: kNsName, middle: {a: 5}}));
|
|
assert.commandWorked(mongos.adminCommand({moveChunk: kNsName, find: {a: 5}, to: secondaryShard}));
|
|
assert.commandWorked(mongos.adminCommand({refineCollectionShardKey: kNsName, key: kNewKeyDoc}));
|
|
|
|
// Verify that chunk 1 owns documents {a: 1}, {a: null}, {a: 1, b: 1}, {a: null, b: 1} while chunk 2
|
|
// owns documents {a: 10} and {a: 10, b: 1}.
|
|
assert(isOwnedByPrimaryShard({a: 1, b: {$exists: false}}));
|
|
assert(isOwnedBySecondaryShard({a: 10, b: {$exists: false}}));
|
|
assert(isOwnedByPrimaryShard({a: null, b: {$exists: false}}));
|
|
assert(isOwnedByPrimaryShard({a: 1, b: 1}));
|
|
assert(isOwnedBySecondaryShard({a: 10, b: 1}));
|
|
assert(isOwnedByPrimaryShard({a: null, b: 1}));
|
|
|
|
// Verify that find targets shards without treating missing shard key fields as null values.
|
|
let docsArr = mongos.getCollection(kNsName).find({b: 1}, {_id: 0}).sort({a: 1}).toArray();
|
|
assert.eq(3, docsArr.length);
|
|
assert.eq({a: null, b: 1}, docsArr[0]);
|
|
assert.eq({a: 1, b: 1}, docsArr[1]);
|
|
assert.eq({a: 10, b: 1}, docsArr[2]);
|
|
|
|
// Verify that count targets shards without treating missing shard key fields as null values.
|
|
assert.eq(3, mongos.getCollection(kNsName).count({b: 1}));
|
|
|
|
// Verify that distinct targets shards without treating missing shard key fields as null values.
|
|
const valuesArr = mongos.getCollection(kNsName).distinct("a").sort();
|
|
assert.eq(3, valuesArr.length);
|
|
assert.eq(1, valuesArr[0]);
|
|
assert.eq(10, valuesArr[1]);
|
|
assert.eq(null, valuesArr[2]);
|
|
|
|
// Verify that insert targets shards as if missing shard key fields were null values.
|
|
assert.commandWorked(mongos.getCollection(kNsName).insert({b: 10}));
|
|
docsArr = mongos.getCollection(kNsName).find({b: 10}).toArray();
|
|
assert(isOwnedByPrimaryShard({b: 10}));
|
|
assert(!isOwnedBySecondaryShard({b: 10}));
|
|
|
|
// Verify that delete targets shards without treating missing shard key fields as null values.
|
|
assert.commandWorked(mongos.getCollection(kNsName).insert({a: 10, b: 10}));
|
|
assert(!isOwnedByPrimaryShard({a: 10, b: 10}));
|
|
assert(isOwnedBySecondaryShard({a: 10, b: 10}));
|
|
|
|
assert.commandWorked(mongos.getCollection(kNsName).remove({b: 10}));
|
|
docsArr = mongos.getCollection(kNsName).find({b: 10}, {_id: 0}).toArray();
|
|
assert.eq(0, docsArr.length);
|
|
|
|
// Verify that a query-style update targets shards without treating missing shard key fields as null
|
|
// values.
|
|
assert.commandWorked(mongos.getCollection(kNsName).update({b: 1}, {$set: {c: 1}}, {multi: true}));
|
|
docsArr = mongos.getCollection(kNsName).find({c: 1}, {_id: 0}).sort({a: 1}).toArray();
|
|
assert.eq(3, docsArr.length);
|
|
assert.eq({a: null, b: 1, c: 1}, docsArr[0]);
|
|
assert.eq({a: 1, b: 1, c: 1}, docsArr[1]);
|
|
assert.eq({a: 10, b: 1, c: 1}, docsArr[2]);
|
|
|
|
// Verify that a replacement update targets shards while treating missing shard keys as null values.
|
|
|
|
// Insert documents all with {d: 1} so they're matched by the update query.
|
|
assert.commandWorked(mongos.getCollection(kNsName).insert({a: -100, b: 1, c: 1, d: 1}));
|
|
assert.commandWorked(mongos.getCollection(kNsName).insert({a: 0, b: 1, c: 2, d: 1}));
|
|
assert.commandWorked(mongos.getCollection(kNsName).insert({a: 100, b: 1, c: 3, d: 1}));
|
|
|
|
// Need to start a session to change the shard key.
|
|
const session = st.s.startSession({retryWrites: true});
|
|
const sessionDB = session.getDatabase(kDbName);
|
|
const sessionColl = sessionDB[kCollName];
|
|
|
|
assert.commandWorked(sessionColl.update({d: 1}, {b: 1, c: 4, d: 1}));
|
|
|
|
let res = assert.commandWorked(mongos.getCollection(kNsName).update({b: 2}, {$set: {c: 2}}, {upsert: true}));
|
|
assert.eq(0, res.nMatched);
|
|
assert.eq(1, res.nUpserted);
|
|
docsArr = mongos.getCollection(kNsName).find({b: 2}).toArray();
|
|
assert.eq(1, docsArr.length);
|
|
|
|
assert.commandWorked(sessionColl.insert({_id: "findAndModify", a: 1}));
|
|
res = assert.commandWorked(
|
|
sessionDB.runCommand({findAndModify: kCollName, query: {a: 2}, update: {$set: {updated: true}}, upsert: true}),
|
|
);
|
|
assert.eq(1, res.lastErrorObject.n);
|
|
assert.eq(0, res.lastErrorObject.updatedExisting);
|
|
assert(res.lastErrorObject.upserted);
|
|
docsArr = mongos.getCollection(kNsName).find({a: 2}).toArray();
|
|
assert.eq(1, docsArr.length);
|
|
|
|
st.stop();
|