Files
mongo/jstests/core/query/index_key_expression.js
Finley Lau 406cb06ea4 SERVER-116179 Bump 2dsphereIndexVersion and check for v4 indexes persisted as v3 (#47621)
GitOrigin-RevId: ddc6b380801bc7cc06f81d52fbb40fef2f6fd573
2026-02-06 16:26:21 +00:00

1121 lines
40 KiB
JavaScript

/**
* Tests that '$_internalIndexKey' expression works as expected under various scenarios.
*
* @tags: [
* does_not_support_stepdowns,
* requires_fcv_63,
* # Not first stage in pipeline. The following test uses $planCacheStats, which is required to be the
* # first stage in a pipeline. This will be incomplatible with timeseries.
* exclude_from_timeseries_crud_passthrough,
* ]
*/
import {FeatureFlagUtil} from "jstests/libs/feature_flag_util.js";
import {isStableFCVSuite} from "jstests/libs/feature_compatibility_version.js";
// TODO (SERVER-117130): Remove the mongos pinning once the related issue is resolved.
// When a database is dropped, a stale router will report "database not found" error for
// deletes (instead of "ok") when pauseMigrationsDuringMultiUpdates is enabled.
if (TestData.pauseMigrationsDuringMultiUpdates) {
TestData.pinToSingleMongos = true;
}
const collection = db.index_key_expression;
/**
* Helper function to get the appropriate 2dsphere index version based on feature flag.
* Returns version 4 if the feature flag is enabled and we're in a stable FCV suite.
* Returns version 3 otherwise (to avoid having to drop v4 indexes during FCV transitions).
*/
function get2dsphereIndexVersion() {
const version = FeatureFlagUtil.isPresentAndEnabled(db, "2dsphereIndexVersion4") ? 4 : 3;
if (!isStableFCVSuite()) {
// If we are upgrading/downgrading the FCV, avoid having to drop any v4 indexes by pinning the version to 3.
// TODO SERVER-118561 Remove this when 9.0 is last LTS.
return 3;
}
return version;
}
/**
* Returns the hash of the provided BSON element that is compatible with 'hashed' indexes.
*/
function getHash(bsonElement) {
return assert.commandWorked(db.runCommand({_hashBSONElement: bsonElement, seed: 0})).out;
}
// Get the appropriate 2dsphere index version for this test run.
const twoDSphereIndexVersion = get2dsphereIndexVersion();
// A dictionary consisting of various test scenarios that must be run against the
// '$_internalIndexKey'.
//
// Below is the description about the fields:
// doc - The document that must be inserted to the collection. The '$_internalIndexKey' would
// observe this document using '$$ROOT' and generate the corresponding keys if possible.
// spec - The index specification document that is passed to the '$_internalIndexKey'.
// expectedIndexKeys - The keys document that the '$_internalIndexKey' must returned corresponding
// to the 'doc' and the 'spec'.
// expectedErrorCode - The error code that the '$_internalIndexKey' must throw in case of an
// exception.
const testScenarios = [
//
// Test '$_internalIndexKey' against invalid arguments.
//
{
doc: {a: 4, b: 5},
expectedErrorCode: 6868509, // $_internalIndexKey requires both 'doc' and 'spec' arguments.
},
{
spec: {key: {a: 1}, name: "btreeIndex"},
expectedErrorCode: 6868509, // $_internalIndexKey requires both 'doc' and 'spec' arguments.
},
{
doc: null,
spec: {key: {a: 1}, name: "btreeIndex"},
expectedErrorCode: 6868509, // $_internalIndexKey requires both 'doc' and 'spec' arguments.
},
{
doc: {a: 4, b: 5},
spec: null,
expectedErrorCode: 6868509, // $_internalIndexKey requires both 'doc' and 'spec' arguments.
},
{
doc: {a: 4, b: 5},
spec: {key: {b: 1, a: -1}, name: "btreeIndex"},
expectedErrorCode: 6868501, // Index key pattern field ordering must be ascending.
},
{
doc: {a: 4, b: 5},
spec: "spec",
expectedErrorCode: 6868507, // $_internalIndexKey requires 'spec' argument to be an object.
},
{
doc: {a: 4, b: 5},
spec: {a: {$const: 1}},
expectedErrorCode: ErrorCodes.InvalidIndexSpecificationOption, // The field 'a' is not valid
// for an index specification.
},
{
doc: {a: 4, b: 5},
spec: {a: {$literal: 1}},
expectedErrorCode: ErrorCodes.InvalidIndexSpecificationOption, // The field 'a' is not valid for an index
// specification.
},
{
doc: {a: 4, b: 5},
spec: {$literal: {key: {a: 1}, name: "btreeIndex"}},
expectedErrorCode: ErrorCodes.InvalidIndexSpecificationOption, // The field '$literal' is not valid for an
// index specification.
},
{
doc: {a: 4, b: 5},
spec: {$const: {key: {a: 1}, name: "btreeIndex"}},
expectedErrorCode: ErrorCodes.InvalidIndexSpecificationOption, // The field '$const' is not valid for an
// index specification.
},
//
// ++++ Btree index tests ++++
// Test '$_internalIndexKey' against various shapes of 'doc' and 'spec'. The 'doc' contains
// nested fields, arrays and combination of both. The 'spec' contains simple/composite fields,
// dotted field paths and combination of both.
//
{doc: {a: 4, b: 5}, spec: {key: {a: 1}, name: "btreeIndex"}, expectedIndexKeys: [{a: 4}]},
{
doc: {a: 4, b: 5},
spec: {key: {a: 1, b: 1}, name: "btreeIndex"},
expectedIndexKeys: [{a: 4, b: 5}],
},
{
doc: {a: 4, b: 5},
spec: {key: {b: 1, a: 1}, name: "btreeIndex"},
expectedIndexKeys: [{b: 5, a: 4}],
},
{
doc: {a: {b: 4}, c: "c"},
spec: {key: {"a.b": 1}, name: "btreeIndex"},
expectedIndexKeys: [{"a.b": 4}],
},
{
doc: {a: [1, 2, 3]},
spec: {key: {a: 1}, name: "btreeIndex"},
expectedIndexKeys: [{a: 1}, {a: 2}, {a: 3}],
},
{
doc: {a: [1, 2, 3], b: 2},
spec: {key: {a: 1, b: 1}, name: "btreeIndex"},
expectedIndexKeys: [
{a: 1, b: 2},
{a: 2, b: 2},
{a: 3, b: 2},
],
},
{
doc: {a: 5, b: [1, 2, 3]},
spec: {key: {b: 1, a: 1}, name: "btreeIndex"},
expectedIndexKeys: [
{b: 1, a: 5},
{b: 2, a: 5},
{b: 3, a: 5},
],
},
{doc: {a: [0, 0, 0]}, spec: {key: {a: 1}, name: "btreeIndex"}, expectedIndexKeys: [{a: 0}]},
{
doc: {a: {b: [1, 2, 3]}},
spec: {key: {"a.b": 1}, name: "btreeIndex"},
expectedIndexKeys: [{"a.b": 1}, {"a.b": 2}, {"a.b": 3}],
},
{
doc: {
a: [
{b: 1, c: 4},
{b: 2, c: 4},
{b: 3, c: 4},
],
},
spec: {key: {"a.b": 1}, name: "btreeIndex"},
expectedIndexKeys: [{"a.b": 1}, {"a.b": 2}, {"a.b": 3}],
},
{
doc: {a: {b: [{c: 1}, {c: 2}]}},
spec: {key: {"a.b.c": 1}, name: "btreeIndex"},
expectedIndexKeys: [{"a.b.c": 1}, {"a.b.c": 2}],
},
{
doc: {
a: [
{b: 1, c: 4, e: 6},
{e: 6, b: 2, c: 4},
{b: 3, e: 6, c: 4},
],
d: 5,
},
spec: {key: {"a.b": 1, d: 1}, name: "btreeIndex"},
expectedIndexKeys: [
{"a.b": 1, d: 5},
{"a.b": 2, d: 5},
{"a.b": 3, d: 5},
],
},
{
doc: {a: [{b: 1}, {b: [1, 2, 3]}]},
spec: {key: {"a.b": 1}, name: "btreeIndex"},
expectedIndexKeys: [{"a.b": 1}, {"a.b": 2}, {"a.b": 3}],
},
{
doc: {a: [{b: [1, 2]}, {b: [2, 3]}]},
spec: {key: {"a.b": 1}, name: "btreeIndex"},
expectedIndexKeys: [{"a.b": 1}, {"a.b": 2}, {"a.b": 3}],
},
{
doc: {a: [{b: [1, 2, 3]}, {b: [2]}, {b: [3, 1]}]},
spec: {key: {"a.b": 1}, name: "btreeIndex"},
expectedIndexKeys: [{"a.b": 1}, {"a.b": 2}, {"a.b": 3}],
},
{
doc: {a: [{b: 2}]},
spec: {key: {"a": 1, "a.b": 1}, name: "btreeIndex"},
expectedIndexKeys: [{a: {b: 2}, "a.b": 2}],
},
{doc: {a: [2]}, spec: {key: {"a.0": 1}, name: "btreeIndex"}, expectedIndexKeys: [{"a.0": 2}]},
{
doc: {a: [[2]]},
spec: {key: {"a.0": 1}, name: "btreeIndex"},
expectedIndexKeys: [{"a.0": [2]}],
},
{
doc: {a: {"0": 2}},
spec: {key: {"a.0": 1}, name: "btreeIndex"},
expectedIndexKeys: [{"a.0": 2}],
},
{
doc: {a: [[2]], c: 3},
spec: {key: {"a.0.0": 1}, name: "btreeIndex"},
expectedIndexKeys: [{"a.0.0": 2}],
},
{
doc: {a: {b: {c: [0, 2, 3, [4]]}}},
spec: {key: {"a.b.c.3": 1}, name: "btreeIndex"},
expectedIndexKeys: [{"a.b.c.3": [4]}],
},
{
doc: {a: [{b: [1, 2]}, {b: {0: 3}}]},
spec: {key: {a: 1, "a.b": 1, "a.0.b": 1, "a.b.0": 1, "a.0.b.0": 1}, name: "btreeIndex"},
expectedIndexKeys: [
{a: {b: {0: 3}}, "a.b": {0: 3}, "a.0.b": 1, "a.b.0": 3, "a.0.b.0": 1},
{a: {b: {0: 3}}, "a.b": {0: 3}, "a.0.b": 2, "a.b.0": 3, "a.0.b.0": 1},
{a: {b: [1, 2]}, "a.b": 1, "a.0.b": 1, "a.b.0": 1, "a.0.b.0": 1},
{a: {b: [1, 2]}, "a.b": 2, "a.0.b": 2, "a.b.0": 1, "a.0.b.0": 1},
],
},
//
// ++++ Btree index tests ++++
// These cases cause the '$_internalIndexKey' to return null and undefined keys for some or all
// fields.
//
{
doc: {a: [{c: 2}, {c: 2}, {c: 2}]},
spec: {key: {"a.b": 1}, name: "btreeIndex"},
expectedIndexKeys: [{"a.b": null}],
},
{doc: {b: 1}, spec: {key: {a: 1}, name: "btreeIndex"}, expectedIndexKeys: [{a: null}]},
{
doc: {a: [1, 2]},
spec: {key: {"a.b": 1}, name: "btreeIndex"},
expectedIndexKeys: [{"a.b": null}],
},
{
doc: {a: "a"},
spec: {key: {x: 1, y: 1}, name: "btreeIndex"},
expectedIndexKeys: [{x: null, y: null}],
},
{doc: {a: []}, spec: {key: {a: 1}, name: "btreeIndex"}, expectedIndexKeys: [{a: undefined}]},
{doc: {a: null}, spec: {key: {a: 1}, name: "btreeIndex"}, expectedIndexKeys: [{a: null}]},
{
doc: {a: [[]]},
spec: {key: {"a.0.0": 1}, name: "btreeIndex"},
expectedIndexKeys: [{"a.0.0": null}],
},
{
doc: {a: []},
spec: {key: {"a.0.0": 1}, name: "btreeIndex"},
expectedIndexKeys: [{"a.0.0": null}],
},
{
doc: {a: [[]]},
spec: {key: {"a.0": 1}, name: "btreeIndex"},
expectedIndexKeys: [{"a.0": undefined}],
},
{
doc: {a: [[1, [1, 2, [{b: [[], 2]}]]], 1]},
spec: {key: {"a.0.1.2.b.0": 1}, name: "btreeIndex"},
expectedIndexKeys: [{"a.0.1.2.b.0": undefined}],
},
{
doc: {a: 2, b: []},
spec: {key: {a: 1, b: 1}, name: "btreeIndex"},
expectedIndexKeys: [{a: 2, b: undefined}],
},
{
doc: {a: [1, {b: [2, {c: [3, {d: 1}], e: 4}, 5, {f: 6}], g: 7}]},
spec: {key: {"a.b.c.d": 1, "a.g": 1, "a.b.f": 1, "a.b.c": 1, "a.b.e": 1}, name: "btreeIndex"},
expectedIndexKeys: [
{"a.b.c.d": null, "a.g": null, "a.b.f": null, "a.b.c": null, "a.b.e": null},
{"a.b.c.d": null, "a.g": 7, "a.b.f": null, "a.b.c": null, "a.b.e": null},
{"a.b.c.d": null, "a.g": 7, "a.b.f": null, "a.b.c": 3, "a.b.e": 4},
{"a.b.c.d": null, "a.g": 7, "a.b.f": 6, "a.b.c": null, "a.b.e": null},
{"a.b.c.d": 1, "a.g": 7, "a.b.f": null, "a.b.c": {"d": 1}, "a.b.e": 4},
],
},
//
// ++++ Btree index tests ++++
// Test '$_internalIndexKey' by passing collation.
//
{
doc: {a: "2", b: 3},
spec: {key: {a: 1}, name: "btreeIndex", collation: {locale: "en"}},
expectedIndexKeys: [{a: "\u0016\u0001\u0005\u0001\u0005"}],
},
{
doc: {a: {b: "2"}, c: 3},
spec: {key: {"a.b": 1}, name: "btreeIndex", collation: {locale: "en"}},
expectedIndexKeys: [{"a.b": "\u0016\u0001\u0005\u0001\u0005"}],
},
{
doc: {a: ["2", "3", "4"]},
spec: {key: {a: 1}, name: "btreeIndex", collation: {locale: "en"}},
expectedIndexKeys: [
{"a": "\u0016\u0001\u0005\u0001\u0005"},
{"a": "\u0018\u0001\u0005\u0001\u0005"},
{"a": "\u001a\u0001\u0005\u0001\u0005"},
],
},
{
doc: {b: 2, a: {c: "3"}},
spec: {key: {a: 1}, name: "btreeIndex", collation: {locale: "en"}},
expectedIndexKeys: [{"a": {c: "\u0018\u0001\u0005\u0001\u0005"}}],
},
//
// ++++ Btree index tests ++++
// These tests causes '$_internalIndexKey' to throw exceptions for btree indexes.
//
{
doc: {a: [{"0": 2}]},
spec: {key: {"a.0": 1}, name: "btreeIndex"},
expectedErrorCode: 16746, // Ambiguous field name found in array.
},
{
doc: {a: [2, {"0": 3}]},
spec: {key: {"a.0": 1}, name: "btreeIndex"},
expectedErrorCode: 16746, // Ambiguous field name found in array.
},
{
doc: {a: [1, 2, 3], b: [1, 2, 3]},
spec: {key: {"a": 1, "b": 1}, name: "btreeIndex"},
expectedErrorCode: ErrorCodes.CannotIndexParallelArrays, // Cannot index parallel arrays [b] [a].
},
{
doc: {a: "2", b: 3},
spec: {key: {a: 1}, name: "btreeIndex", collation: {backwards: true}},
expectedErrorCode: 6868502, // Malformed 'collation' document provided.
},
{
doc: {a: 2},
spec: {key: {".a": 1}, name: "btreeIndex"},
expectedErrorCode: ErrorCodes.CannotCreateIndex, // Index keys cannot contain an empty field.
},
{
doc: {a: {"": [{c: 1}, {c: 2}]}},
spec: {key: {"a..c": 1}, name: "btreeIndex"},
expectedErrorCode: ErrorCodes.CannotCreateIndex, // Index keys cannot contain an empty field.
},
{
doc: {a: 2},
spec: {key: {"a.": 1}, name: "btreeIndex"},
expectedErrorCode: ErrorCodes.CannotCreateIndex, // Index keys cannot contain an empty field.
},
{
doc: {a: {"": 2}},
spec: {key: {"a.": 1}, name: "btreeIndex"},
expectedErrorCode: ErrorCodes.CannotCreateIndex, // Index keys cannot contain an empty field.
},
//
// ++++ Hashed index tests ++++
// Test '$_internalIndexKey' against various shapes of 'doc' and 'spec' with hashed index.
//
{
doc: {a: "a"},
spec: {key: {a: "hashed"}, name: "hashedIndex"},
expectedIndexKeys: [{a: getHash("a")}],
},
{
doc: {a: {b: 2}, c: 3},
spec: {key: {a: "hashed"}, name: "hashedIndex", collation: {locale: "simple"}},
expectedIndexKeys: [{a: getHash({b: 2})}],
},
{
doc: {a: 2, c: "c", b: {c: 100}},
spec: {key: {a: "hashed", b: 1}, name: "hashedIndex"},
expectedIndexKeys: [{a: getHash(2), b: {c: 100}}],
},
{
doc: {a: {b: "abc", c: "def"}},
spec: {key: {"a.c": 1, a: "hashed"}, name: "hashedIndex"},
expectedIndexKeys: [{"a.c": "def", a: getHash({b: "abc", c: "def"})}],
},
{
doc: {a: {b: {c: "abc", d: {e: "def"}}}, f: "ghi"},
spec: {key: {"a.b.d.e": "hashed", f: 1}, name: "hashedIndex"},
expectedIndexKeys: [{"a.b.d.e": getHash("def"), f: "ghi"}],
},
{
doc: {a: "a"},
spec: {key: {a: "hashed"}, name: "hashedIndex", collation: {locale: "simple"}},
expectedIndexKeys: [{a: getHash("a")}],
},
{
doc: {a: "a"},
spec: {key: {a: "hashed"}, name: "hashedIndex", collation: {locale: "en"}},
expectedIndexKeys: [{a: NumberLong("537359449531599826")}],
},
{
doc: {},
spec: {key: {a: "hashed", b: 1, c: 1}, name: "hashedIndex"},
expectedIndexKeys: [{a: getHash(null), b: null, c: null}],
},
{
doc: {a: 2},
spec: {key: {b: "hashed", c: 1}, name: "hashedIndex"},
expectedIndexKeys: [{b: getHash(null), c: null}],
},
{
doc: {a: 2},
spec: {key: {b: "hashed", c: 1}, name: "hashedIndex", collation: {locale: "en"}},
expectedIndexKeys: [{b: getHash(null), c: null}],
},
{
doc: {a: "a", b: 2},
spec: {key: {a: "hashed", b: "hashed"}, name: "hashedIndex", collation: {locale: "simple"}},
expectedErrorCode: 31303, // A maximum of one index field is allowed to be hashed but found 2.
},
{
doc: {a: [{b: 2}], c: 3},
spec: {key: {a: "hashed"}, name: "hashedIndex", collation: {locale: "simple"}},
expectedErrorCode: 16766, // hashed indexes do not currently support array values.
},
//
// ++++ 2d index tests ++++
// Test '$_internalIndexKey' against various shapes of 'doc' and 'spec' with 2d index.
//
{
doc: {a: [0, 0]},
spec: {key: {a: "2d"}, name: "2DIndex"},
expectedIndexKeys: [{a: BinData(128, "wAAAAAAAAAA=")}],
},
{
doc: {a: [0, 0], b: 5, e: 100, c: 200},
spec: {key: {a: "2d", c: 1, b: 1}, name: "2DIndex"},
expectedIndexKeys: [{a: BinData(128, "wAAAAAAAAAA="), c: 200, b: 5}],
},
{
doc: {c: 100, d: 400, a: {e: [0, 0]}, b: 5},
spec: {key: {"a.e": "2d", d: 1, b: 1}, name: "2DIndex"},
expectedIndexKeys: [{"a.e": BinData(128, "wAAAAAAAAAA="), d: 400, b: 5}],
},
{
doc: {a: [0, 0], b: [5, 6]},
spec: {key: {a: "2d", b: 1}, name: "2DIndex"},
expectedIndexKeys: [{a: BinData(128, "wAAAAAAAAAA="), b: [5, 6]}],
},
{
doc: {a: [0, 0], b: 5, e: 100, c: 200},
spec: {key: {f: "2d", g: 1, h: 1}, name: "2DIndex"},
expectedIndexKeys: [],
},
{
doc: {a: [0, 0], b: [5, 6]},
spec: {key: {a: "2d", b: "2d"}, name: "2DIndex"},
expectedErrorCode: 16800, // can't have 2 geo fields.
},
{
doc: {a: [0], b: 5},
spec: {key: {a: "2d", b: 1}, name: "2DIndex"},
expectedErrorCode: 13068, // geo field only has 1 element.
},
//
// ++++ 2dsphere index tests ++++
// Test '$_internalIndexKey' against various shapes of 'doc' and 'spec' with 2dsphere index.
//
{
doc: {
a: {
b: [
{nongeo: 1, geo: {type: "Point", coordinates: [0, 0]}},
{nongeo: 2, geo: {type: "Point", coordinates: [3, 3]}},
],
},
},
spec: {key: {"a.b.geo": "2dsphere"}, name: "2dsphereIndex"},
expectedIndexKeys: [{"a.b.geo": "0f20000000000000"}, {"a.b.geo": "0f20002002202203"}],
},
{
doc: {
a: {
b: [
{nongeo: 1, geo: {type: "Point", coordinates: [0, 0]}},
{nongeo: 2, geo: {type: "Point", coordinates: [3, 3]}},
],
},
},
spec: {key: {"a.b.none": "2dsphere"}, name: "2dsphereIndex"},
expectedIndexKeys: [{"a.b.none": null}],
},
{
doc: {
a: {
b: [
{nongeo: 1, geo: {type: "Point", coordinates: [0, 0]}},
{nongeo: 2, geo: {type: "Point", coordinates: [3, 3]}},
],
},
},
spec: {key: {"a.b.nongeo": 1, "a.b.geo": "2dsphere"}, name: "2dsphereIndex"},
expectedIndexKeys: [
{"a.b.nongeo": 1, "a.b.geo": "0f20000000000000"},
{"a.b.nongeo": 1, "a.b.geo": "0f20002002202203"},
{"a.b.nongeo": 2, "a.b.geo": "0f20000000000000"},
{"a.b.nongeo": 2, "a.b.geo": "0f20002002202203"},
],
},
{
doc: {
a: {
b: [
{
nongeo1: 1,
nongeo2: 3,
nongeo3: [5, 6],
geo: {type: "Point", coordinates: [0, 0]},
},
{nongeo1: 2, nongeo2: 4, nongeo4: 7, geo: {type: "Point", coordinates: [3, 3]}},
],
},
},
spec: {
key: {"a.b.geo": "2dsphere", "a.b.nongeo1": 1, "a.b.nongeo3": 1},
name: "2dsphereIndex",
},
expectedIndexKeys: [
{"a.b.geo": "0f20000000000000", "a.b.nongeo1": 1, "a.b.nongeo3": 5},
{"a.b.geo": "0f20000000000000", "a.b.nongeo1": 1, "a.b.nongeo3": 6},
{"a.b.geo": "0f20000000000000", "a.b.nongeo1": 2, "a.b.nongeo3": 5},
{"a.b.geo": "0f20000000000000", "a.b.nongeo1": 2, "a.b.nongeo3": 6},
{"a.b.geo": "0f20002002202203", "a.b.nongeo1": 1, "a.b.nongeo3": 5},
{"a.b.geo": "0f20002002202203", "a.b.nongeo1": 1, "a.b.nongeo3": 6},
{"a.b.geo": "0f20002002202203", "a.b.nongeo1": 2, "a.b.nongeo3": 5},
{"a.b.geo": "0f20002002202203", "a.b.nongeo1": 2, "a.b.nongeo3": 6},
],
},
{
doc: {
a: {
b: [
{nongeo: 1, geo: {type: "Point", coordinates: [0, 1]}},
{nongeo: 2, geo: {type: "Point", coordinates: [2, 3]}},
],
c: [
{nongeo: 1, geo: {type: "Point", coordinates: [4, 5]}},
{nongeo: 2, geo: {type: "Point", coordinates: [6, 7]}},
],
},
},
spec: {key: {"a.b.geo": "2dsphere", "a.c.geo": "2dsphere"}, name: "2dsphereIndex"},
expectedIndexKeys: [
{"a.b.geo": "0f20000033010011", "a.c.geo": "0f20002233220120"},
{"a.b.geo": "0f20000033010011", "a.c.geo": "0f20020313220130"},
{"a.b.geo": "0f20003113201132", "a.c.geo": "0f20002233220120"},
{"a.b.geo": "0f20003113201132", "a.c.geo": "0f20020313220130"},
],
},
{
doc: {
a: {
b: [
{nongeo: 1, geo: {coordinates: [0, 0]}},
{nongeo: 2, geo: {type: "Point", coordinates: [3, 3]}},
],
},
},
spec: {key: {"a.b.geo": "2dsphere"}, name: "2dsphereIndex"},
expectedErrorCode: 16755, // unknown GeoJSON type
},
//
// ++++ 2dsphere_bucket index tests ++++
// Test '$_internalIndexKey' against various shapes of 'doc' and 'spec' with 2dsphere_bucket
// index.
//
{
doc: {
control: {version: 1},
data: {
geo: {
0: {type: "Point", coordinates: [0, 0]},
1: {type: "Point", coordinates: [3, 3]},
},
},
},
spec: {
key: {"data.geo": "2dsphere_bucket"},
"2dsphereIndexVersion": twoDSphereIndexVersion,
name: "2dsphereBucketIndex",
},
expectedIndexKeys: [
{"data.geo": NumberLong("1152921504875282432")},
{"data.geo": NumberLong("1157514469887180800")},
],
},
{
doc: {
control: {version: 1},
data: {
geo1: {
0: {type: "Point", coordinates: [0, 0]},
1: {type: "Point", coordinates: [3, 3]},
},
geo2: {
0: {type: "Point", coordinates: [1, 1]},
1: {type: "Point", coordinates: [3, 3]},
},
},
},
spec: {
key: {"data.geo1": "2dsphere_bucket", "data.geo2": "2dsphere_bucket"},
"2dsphereIndexVersion": twoDSphereIndexVersion,
name: "2dsphereBucketIndex",
},
expectedIndexKeys: [
{
"data.geo1": NumberLong("1152921504875282432"),
"data.geo2": NumberLong("1153277837910736896"),
},
{
"data.geo1": NumberLong("1152921504875282432"),
"data.geo2": NumberLong("1157514469887180800"),
},
{
"data.geo1": NumberLong("1157514469887180800"),
"data.geo2": NumberLong("1153277837910736896"),
},
{
"data.geo1": NumberLong("1157514469887180800"),
"data.geo2": NumberLong("1157514469887180800"),
},
],
},
{
doc: {
control: {version: 1},
data: {
geo1: {
0: {type: "Point", coordinates: [0, 0]},
1: {type: "Point", coordinates: [3, 3]},
},
geo2: {
0: {type: "Point", coordinates: [1, 1]},
1: {type: "Point", coordinates: [3, 3]},
},
},
},
spec: {
key: {"data.geo2": "2dsphere_bucket", "data.geo1": "2dsphere_bucket"},
"2dsphereIndexVersion": twoDSphereIndexVersion,
name: "2dsphereBucketIndex",
},
expectedIndexKeys: [
{
"data.geo2": NumberLong("1153277837910736896"),
"data.geo1": NumberLong("1152921504875282432"),
},
{
"data.geo2": NumberLong("1153277837910736896"),
"data.geo1": NumberLong("1157514469887180800"),
},
{
"data.geo2": NumberLong("1157514469887180800"),
"data.geo1": NumberLong("1152921504875282432"),
},
{
"data.geo2": NumberLong("1157514469887180800"),
"data.geo1": NumberLong("1157514469887180800"),
},
],
},
{
doc: {
control: {version: 1},
data: {
geo1: {
0: {type: "Point", coordinates: [0, 0]},
1: {type: "Point", coordinates: [3, 3]},
},
geo2: {
0: {type: "Point", coordinates: [1, 1]},
1: {type: "Point", coordinates: [3, 3]},
},
},
},
spec: {
key: {"data.geo1": "2dsphere_bucket", "data.geo2": 1},
"2dsphereIndexVersion": twoDSphereIndexVersion,
name: "2dsphereBucketIndex",
},
expectedIndexKeys: [
{
"data.geo1": NumberLong("1152921504875282432"),
"data.geo2": {
"0": {"type": "Point", "coordinates": [1, 1]},
"1": {"type": "Point", "coordinates": [3, 3]},
},
},
{
"data.geo1": NumberLong("1157514469887180800"),
"data.geo2": {
"0": {"type": "Point", "coordinates": [1, 1]},
"1": {"type": "Point", "coordinates": [3, 3]},
},
},
],
},
{
doc: {
control: {version: 1},
data: {
geo: {
0: {type: "Point", coordinates: [0, 0]},
1: {type: "Point", coordinates: [3, 3]},
},
},
},
spec: {
key: {"data.none1": "2dsphere_bucket"},
"2dsphereIndexVersion": twoDSphereIndexVersion,
name: "2dsphereBucketIndex",
},
expectedIndexKeys: [],
},
{
doc: {
control: {version: 1},
data: {geo: {0: {coordinates: [0, 0]}, 1: {type: "Point", coordinates: [3, 3]}}},
},
spec: {
key: {"data.geo": "2dsphere_bucket"},
"2dsphereIndexVersion": twoDSphereIndexVersion,
name: "2dsphereBucketIndex",
},
expectedErrorCode: 183934, // Can't extract geo keys: unknown GeoJSON type.
},
{
doc: {
data: {
geo: {
0: {type: "Point", coordinates: [0, 0]},
1: {type: "Point", coordinates: [3, 3]},
},
},
},
spec: {
key: {"data.geo": "2dsphere_bucket"},
"2dsphereIndexVersion": twoDSphereIndexVersion,
name: "2dsphereBucketIndex",
},
expectedErrorCode: 6540600, // Time-series bucket documents must have 'control' object present.
},
//
// ++++ text index tests ++++
// Test '$_internalIndexKey' against various shapes of 'doc' and 'spec' with text index.
//
{
doc: {places: "Dublin London", food: "Salad, Soups"},
spec: {
key: {places: "text", food: "text"},
name: "textIndex",
weights: {places: 2, food: 1},
"default_language": "english",
textIndexVersion: 3,
},
expectedIndexKeys: [
{"places": "Dublin London", "food": "Salad, Soups", "term": "dublin", "weight": 1.5},
{"places": "Dublin London", "food": "Salad, Soups", "term": "london", "weight": 1.5},
{"places": "Dublin London", "food": "Salad, Soups", "term": "salad", "weight": 0.75},
{"places": "Dublin London", "food": "Salad, Soups", "term": "soup", "weight": 0.75},
],
},
{
doc: {places: "Dublin New-York Toronto", food: "Salad, Soups, Cakes"},
spec: {
key: {food: "text", places: "text"},
name: "textIndex",
weights: {places: 2, food: 1},
"default_language": "english",
textIndexVersion: 3,
},
expectedIndexKeys: [
{
"food": "Salad, Soups, Cakes",
"places": "Dublin New-York Toronto",
"term": "cake",
"weight": 0.6666666666666666,
},
{
"food": "Salad, Soups, Cakes",
"places": "Dublin New-York Toronto",
"term": "dublin",
"weight": 1.25,
},
{
"food": "Salad, Soups, Cakes",
"places": "Dublin New-York Toronto",
"term": "new",
"weight": 1.25,
},
{
"food": "Salad, Soups, Cakes",
"places": "Dublin New-York Toronto",
"term": "salad",
"weight": 0.6666666666666666,
},
{
"food": "Salad, Soups, Cakes",
"places": "Dublin New-York Toronto",
"term": "soup",
"weight": 0.6666666666666666,
},
{
"food": "Salad, Soups, Cakes",
"places": "Dublin New-York Toronto",
"term": "toronto",
"weight": 1.25,
},
{
"food": "Salad, Soups, Cakes",
"places": "Dublin New-York Toronto",
"term": "york",
"weight": 1.25,
},
],
},
{
doc: {places: "Dublin New-York Toronto", food: "Salad, Soups, Cakes"},
spec: {
key: {_fts: "text", food: "text", places: "text"},
name: "textIndex",
weights: {places: 2, food: 1},
"default_language": "english",
textIndexVersion: 3,
},
expectedIndexKeys: [
{
"term": "cake",
"weight": 0.6666666666666666,
"food": "Salad, Soups, Cakes",
"places": "Dublin New-York Toronto",
},
{
"term": "dublin",
"weight": 1.25,
"food": "Salad, Soups, Cakes",
"places": "Dublin New-York Toronto",
},
{
"term": "new",
"weight": 1.25,
"food": "Salad, Soups, Cakes",
"places": "Dublin New-York Toronto",
},
{
"term": "salad",
"weight": 0.6666666666666666,
"food": "Salad, Soups, Cakes",
"places": "Dublin New-York Toronto",
},
{
"term": "soup",
"weight": 0.6666666666666666,
"food": "Salad, Soups, Cakes",
"places": "Dublin New-York Toronto",
},
{
"term": "toronto",
"weight": 1.25,
"food": "Salad, Soups, Cakes",
"places": "Dublin New-York Toronto",
},
{
"term": "york",
"weight": 1.25,
"food": "Salad, Soups, Cakes",
"places": "Dublin New-York Toronto",
},
],
},
{
doc: {
places: "Dublin New-York Toronto",
food: "Salad, Soups, Cakes",
description: "This is a test with text index",
sports: "Cricket Football Tennis Baseball",
},
spec: {
key: {
food: "text",
sports: "text",
_ftsx: 1,
places: "text",
_fts: "text",
description: "text",
},
name: "textIndex",
weights: {places: 2, description: 1, food: 1, sports: 1},
"default_language": "english",
textIndexVersion: 3,
},
expectedIndexKeys: [
{
"food": "Salad, Soups, Cakes",
"sports": "Cricket Football Tennis Baseball",
"term": "basebal",
"weight": 0.625,
"places": "Dublin New-York Toronto",
"description": "This is a test with text index",
},
{
"food": "Salad, Soups, Cakes",
"sports": "Cricket Football Tennis Baseball",
"term": "cake",
"weight": 0.6666666666666666,
"places": "Dublin New-York Toronto",
"description": "This is a test with text index",
},
{
"food": "Salad, Soups, Cakes",
"sports": "Cricket Football Tennis Baseball",
"term": "cricket",
"weight": 0.625,
"places": "Dublin New-York Toronto",
"description": "This is a test with text index",
},
{
"food": "Salad, Soups, Cakes",
"sports": "Cricket Football Tennis Baseball",
"term": "dublin",
"weight": 1.25,
"places": "Dublin New-York Toronto",
"description": "This is a test with text index",
},
{
"food": "Salad, Soups, Cakes",
"sports": "Cricket Football Tennis Baseball",
"term": "footbal",
"weight": 0.625,
"places": "Dublin New-York Toronto",
"description": "This is a test with text index",
},
{
"food": "Salad, Soups, Cakes",
"sports": "Cricket Football Tennis Baseball",
"term": "index",
"weight": 0.6666666666666666,
"places": "Dublin New-York Toronto",
"description": "This is a test with text index",
},
{
"food": "Salad, Soups, Cakes",
"sports": "Cricket Football Tennis Baseball",
"term": "new",
"weight": 1.25,
"places": "Dublin New-York Toronto",
"description": "This is a test with text index",
},
{
"food": "Salad, Soups, Cakes",
"sports": "Cricket Football Tennis Baseball",
"term": "salad",
"weight": 0.6666666666666666,
"places": "Dublin New-York Toronto",
"description": "This is a test with text index",
},
{
"food": "Salad, Soups, Cakes",
"sports": "Cricket Football Tennis Baseball",
"term": "soup",
"weight": 0.6666666666666666,
"places": "Dublin New-York Toronto",
"description": "This is a test with text index",
},
{
"food": "Salad, Soups, Cakes",
"sports": "Cricket Football Tennis Baseball",
"term": "tenni",
"weight": 0.625,
"places": "Dublin New-York Toronto",
"description": "This is a test with text index",
},
{
"food": "Salad, Soups, Cakes",
"sports": "Cricket Football Tennis Baseball",
"term": "test",
"weight": 0.6666666666666666,
"places": "Dublin New-York Toronto",
"description": "This is a test with text index",
},
{
"food": "Salad, Soups, Cakes",
"sports": "Cricket Football Tennis Baseball",
"term": "text",
"weight": 0.6666666666666666,
"places": "Dublin New-York Toronto",
"description": "This is a test with text index",
},
{
"food": "Salad, Soups, Cakes",
"sports": "Cricket Football Tennis Baseball",
"term": "toronto",
"weight": 1.25,
"places": "Dublin New-York Toronto",
"description": "This is a test with text index",
},
{
"food": "Salad, Soups, Cakes",
"sports": "Cricket Football Tennis Baseball",
"term": "york",
"weight": 1.25,
"places": "Dublin New-York Toronto",
"description": "This is a test with text index",
},
],
},
//
// ++++ wildcard index tests ++++
// Test '$_internalIndexKey' against various shapes of 'doc' and 'spec' with wildcard index.
//
{
doc: {a: {b: "one", c: 2}},
spec: {key: {"$**": 1}, name: "wildcardIndex"},
expectedIndexKeys: [{"a.b": "one"}, {"a.c": 2}],
},
{
doc: {a: {b: "one", c: 2}},
spec: {key: {"d.$**": 1}, name: "wildcardIndex"},
expectedIndexKeys: [],
},
{
doc: {
product_name: "Spy Coat",
product_attributes: {material: ["Tweed", "Wool", "Leather"], size: {length: 72, units: "inches"}},
},
spec: {key: {"product_attributes.$**": 1}, name: "wildcardIndex"},
expectedIndexKeys: [
{"product_attributes.material": "Leather"},
{"product_attributes.material": "Tweed"},
{"product_attributes.material": "Wool"},
{"product_attributes.size.length": 72},
{"product_attributes.size.units": "inches"},
],
},
{
doc: {a: {b: "one", c: 2}},
spec: {key: {"$**": 1, "a.b": 1}, name: "wildcardIndex"},
expectedErrorCode: ErrorCodes.CannotCreateIndex, // Must have 'wildcardProjection'.
},
];
// Run each test scenario and verify that the '$_internalIndexKey' returns the
// expected response.
testScenarios.forEach((testScenario) => {
jsTestLog("Testing scenario: " + tojson(testScenario));
// Clear the collection so the '$$ROOT' does not pick documents from the last test
// scenario.
collection.deleteMany({});
// Insert the document to the collection if the field 'doc' exists in the
// test-scenario dictionary.
if (testScenario.doc) {
assert.commandWorked(collection.insert(testScenario.doc));
}
// Prepare the pipeline that consists of the '$_internalIndexKey' expression. The
// 'doc' field corresponds to the '$$ROOT', as such there must be only document in
// the collection, so that the aggregate command returns only one key document for
// each test scenario.
let internalIndexKeySpec = {};
if (testScenario.doc) {
internalIndexKeySpec["doc"] = "$$ROOT";
}
if (testScenario.spec) {
internalIndexKeySpec["spec"] = testScenario.spec;
}
const pipeline = [{$replaceRoot: {newRoot: {_id: {$_internalIndexKey: internalIndexKeySpec}}}}];
// If the 'expectedIndexKeys' field is present, then the aggregate command must
// succeed and the
// '$_internalIndexKey' must return expected keys document. Otherwise the
// 'expectedErrorCode' must be present and the aggregate command in such case must
// throw an exception.
if (testScenario.expectedIndexKeys) {
assert.eq(collection.aggregate(pipeline).toArray()[0], {_id: testScenario.expectedIndexKeys}, testScenario);
} else {
assert.throwsWithCode(() => collection.aggregate(pipeline).toArray(), testScenario.expectedErrorCode);
}
});