summaryrefslogtreecommitdiff
path: root/jstests/replsets/read_at_cluster_time_outside_transactions.js
blob: e75bf2656e5e223af35db355ba9d21612cb39a46 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
/**
 * Tests that the "find" and "dbHash" commands support reading at a Timestamp by using the
 * $_internalReadAtClusterTime option.
 *
 * @tags: [requires_document_locking, uses_transactions]
 */
(function() {
"use strict";

const rst = new ReplSetTest({nodes: 1});
rst.startSet();
rst.initiate();

const primary = rst.getPrimary();
const db = primary.getDB("test");

const collName = "read_at_cluster_time_outside_transactions";
const collection = db[collName];

// We prevent the replica set from advancing oldest_timestamp. This ensures that the snapshot
// associated with 'clusterTime' is retained for the duration of this test.
rst.nodes.forEach(conn => {
    assert.commandWorked(conn.adminCommand({
        configureFailPoint: "WTPreserveSnapshotHistoryIndefinitely",
        mode: "alwaysOn",
    }));
});

// We insert 3 documents in order to have data to return for both the find and getMore commands
// when using a batch size of 2. We then save the md5sum associated with the opTime of the last
// insert.
assert.commandWorked(collection.insert({_id: 1, comment: "should be seen by find command"}));
assert.commandWorked(collection.insert({_id: 3, comment: "should be seen by find command"}));
assert.commandWorked(collection.insert({_id: 5, comment: "should be seen by getMore command"}));

const clusterTime = db.getSession().getOperationTime();

let res = assert.commandWorked(db.runCommand({dbHash: 1}));
const hashAfterOriginalInserts = {
    collections: res.collections,
    md5: res.md5
};

// The documents with _id=1 and _id=3 should be returned by the find command.
let cursor = collection.find().sort({_id: 1}).batchSize(2);
assert.eq({_id: 1, comment: "should be seen by find command"}, cursor.next());
assert.eq({_id: 3, comment: "should be seen by find command"}, cursor.next());

// We then insert documents with _id=2 and _id=4. The document with _id=2 is positioned behind
// the _id index cursor and won't be returned by the getMore command. However, the document with
// _id=4 is positioned ahead and should end up being returned.
assert.commandWorked(collection.insert({_id: 2, comment: "should not be seen by getMore command"}));
assert.commandWorked(
    collection.insert({_id: 4, comment: "should be seen by non-snapshot getMore command"}));
assert.eq({_id: 4, comment: "should be seen by non-snapshot getMore command"}, cursor.next());
assert.eq({_id: 5, comment: "should be seen by getMore command"}, cursor.next());
assert(!cursor.hasNext());

// When using the $_internalReadAtClusterTime option with a clusterTime from after the
// original 3 documents were inserted, the document with _id=2 shouldn't be visible to the find
// command because it was inserted afterwards. The same applies to the document with _id=4 and
// the getMore command.
res = collection.runCommand("find", {
    batchSize: 2,
    sort: {_id: 1},
    $_internalReadAtClusterTime: clusterTime,
});

const batchSize = 2;
cursor = new DBCommandCursor(db, res, batchSize);
assert.eq({_id: 1, comment: "should be seen by find command"}, cursor.next());
assert.eq({_id: 3, comment: "should be seen by find command"}, cursor.next());
assert.eq({_id: 5, comment: "should be seen by getMore command"}, cursor.next());
assert(!cursor.hasNext());

// Using the $_internalReadAtClusterTime option to read at the opTime of the last of the 3
// original inserts should return the same md5sum as it did originally.
res = assert.commandWorked(db.runCommand({
    dbHash: 1,
    $_internalReadAtClusterTime: clusterTime,
}));

const hashAtClusterTime = {
    collections: res.collections,
    md5: res.md5
};
assert.eq(hashAtClusterTime, hashAfterOriginalInserts);

// Attempting to read at a null timestamp should return an error.
assert.commandFailedWithCode(collection.runCommand("find", {
    batchSize: 2,
    sort: {_id: 1},
    $_internalReadAtClusterTime: new Timestamp(0, 0),
}),
                             ErrorCodes.InvalidOptions);

assert.commandFailedWithCode(db.runCommand({
    dbHash: 1,
    $_internalReadAtClusterTime: new Timestamp(0, 1),
}),
                             ErrorCodes.InvalidOptions);

// Attempting to read at a clusterTime in the future should return an error.
const futureClusterTime = new Timestamp(clusterTime.getTime() + 1000, 1);

assert.commandFailedWithCode(collection.runCommand("find", {
    batchSize: 2,
    sort: {_id: 1},
    $_internalReadAtClusterTime: futureClusterTime,
}),
                             ErrorCodes.InvalidOptions);

assert.commandFailedWithCode(db.runCommand({
    dbHash: 1,
    $_internalReadAtClusterTime: futureClusterTime,
}),
                             ErrorCodes.InvalidOptions);

// $_internalReadAtClusterTime is not supported in transactions.
const session = primary.startSession();
const sessionDB = session.getDatabase("test");
const sessionColl = sessionDB[collName];

session.startTransaction();
assert.commandFailedWithCode(sessionColl.runCommand("find", {
    batchSize: 2,
    sort: {_id: 1},
    $_internalReadAtClusterTime: clusterTime,
}),
                             ErrorCodes.OperationNotSupportedInTransaction);
assert.commandFailedWithCode(session.abortTransaction_forTesting(), ErrorCodes.NoSuchTransaction);

// dbHash is not supported in transactions at all.
session.startTransaction();
assert.commandFailedWithCode(
    sessionDB.runCommand({dbHash: 1, $_internalReadAtClusterTime: clusterTime}),
    ErrorCodes.OperationNotSupportedInTransaction);
assert.commandFailedWithCode(session.abortTransaction_forTesting(), ErrorCodes.NoSuchTransaction);

// Create a new collection to move the minimum visible snapshot to that operation time. Then
// read at a cluster time behind the minimum visible snapshot which should fail.
let newCollName = "newColl";
assert.commandWorked(db.createCollection(newCollName));
let createCollClusterTime = db.getSession().getOperationTime();
res = db[newCollName].runCommand("find", {
    $_internalReadAtClusterTime:
        Timestamp(createCollClusterTime.getTime() - 1, createCollClusterTime.getInc()),
});
assert.commandFailedWithCode(res, ErrorCodes.SnapshotUnavailable);

rst.stopSet();
})();