SERVER-124174 Replace FLE2 prefix/suffix preview types with GA types in jstests and unit tests (#52170)

GitOrigin-RevId: 1f47137fd5a87dc5545c3904cdc5b8d7b32abb7e
This commit is contained in:
Erwin Pe 2026-04-21 17:32:49 -04:00 committed by MongoDB Bot
parent ae4b3b02be
commit d304172308
5 changed files with 68 additions and 43 deletions

View File

@ -1,10 +1,12 @@
import {FeatureFlagUtil} from "jstests/libs/feature_flag_util.js";
class TextFieldBase {
constructor(lb, ub, caseSensitive, diacriticSensitive, maxContention) {
constructor(lb, ub, caseSensitive, diacriticSensitive, maxContention, forcePreview = false) {
this._lb = NumberInt(lb);
this._ub = NumberInt(ub);
this._caseSensitive = caseSensitive;
this._diacriticSensitive = diacriticSensitive;
this._cm = NumberLong(maxContention);
this._forcePreview = forcePreview;
}
createQueryTypeDescriptor() {
return {
@ -18,10 +20,12 @@ class TextFieldBase {
}
export class SuffixField extends TextFieldBase {
createQueryTypeDescriptor() {
// Use the deprecated "suffixPreview" name for mongocrypt compatibility.
// TODO SERVER-123416 Change to "suffix" once libmongocrypt supports the new name.
return Object.assign({"queryType": "suffixPreview"}, super.createQueryTypeDescriptor());
createQueryTypeDescriptor(db) {
let queryType = "suffix";
if (this._forcePreview || (db && !FeatureFlagUtil.isPresentAndEnabled(db.getMongo(), "QEPrefixSuffixSearch"))) {
queryType = "suffixPreview";
}
return Object.assign({"queryType": queryType}, super.createQueryTypeDescriptor());
}
calculateExpectedTagCount(byte_len) {
@ -61,11 +65,12 @@ export class SuffixField extends TextFieldBase {
}
export class PrefixField extends SuffixField {
createQueryTypeDescriptor() {
let spec = super.createQueryTypeDescriptor();
// Use the deprecated "prefixPreview" name for mongocrypt compatibility.
// TODO SERVER-123416 Change to "prefix" once libmongocrypt supports the new name.
spec.queryType = "prefixPreview";
createQueryTypeDescriptor(db) {
let spec = super.createQueryTypeDescriptor(db);
spec.queryType = "prefix";
if (this._forcePreview || (db && !FeatureFlagUtil.isPresentAndEnabled(db.getMongo(), "QEPrefixSuffixSearch"))) {
spec.queryType = "prefixPreview";
}
return spec;
}
@ -91,12 +96,12 @@ export class PrefixField extends SuffixField {
}
export class SubstringField extends TextFieldBase {
constructor(mlen, lb, ub, caseSensitive, diacriticSensitive, maxContention) {
super(lb, ub, caseSensitive, diacriticSensitive, maxContention);
constructor(mlen, lb, ub, caseSensitive, diacriticSensitive, maxContention, forcePreview = false) {
super(lb, ub, caseSensitive, diacriticSensitive, maxContention, forcePreview);
this._mlen = NumberInt(mlen);
}
createQueryTypeDescriptor() {
createQueryTypeDescriptor(db) {
return Object.assign(
{"queryType": "substringPreview", "strMaxLength": this._mlen},
super.createQueryTypeDescriptor(),
@ -154,12 +159,26 @@ export class SubstringField extends TextFieldBase {
}
export class SuffixAndPrefixField {
constructor(sfxLb, sfxUb, pfxLb, pfxUb, caseSensitive, diacriticSensitive, maxContention) {
this._suffixField = new SuffixField(sfxLb, sfxUb, caseSensitive, diacriticSensitive, maxContention);
this._prefixField = new PrefixField(pfxLb, pfxUb, caseSensitive, diacriticSensitive, maxContention);
constructor(sfxLb, sfxUb, pfxLb, pfxUb, caseSensitive, diacriticSensitive, maxContention, forcePreview = false) {
this._suffixField = new SuffixField(
sfxLb,
sfxUb,
caseSensitive,
diacriticSensitive,
maxContention,
forcePreview,
);
this._prefixField = new PrefixField(
pfxLb,
pfxUb,
caseSensitive,
diacriticSensitive,
maxContention,
forcePreview,
);
}
createQueryTypeDescriptor() {
return [this._suffixField.createQueryTypeDescriptor(), this._prefixField.createQueryTypeDescriptor()];
createQueryTypeDescriptor(db) {
return [this._suffixField.createQueryTypeDescriptor(db), this._prefixField.createQueryTypeDescriptor(db)];
}
calculateExpectedTagCount(byte_len) {
// subtract 1 since the exact match string is doubly counted in the other call

View File

@ -1,6 +1,6 @@
/**
* Test downgrade to incompatible versions is blocked if substringPreview,
* suffix, or prefix query types are being used in a FLE2 collection.
* suffixPreview, or prefixPreview query types are being used in a FLE2 collection.
*/
import "jstests/multiVersion/libs/multi_rs.js";
@ -9,10 +9,12 @@ import {PrefixField, SubstringField, SuffixAndPrefixField, SuffixField} from "js
import {ReplSetTest} from "jstests/libs/replsettest.js";
const dbName = "qe_text_downgrade_test";
const substrField = new SubstringField(20, 2, 10, false, false, 1);
const suffixField = new SuffixField(2, 5, true, false, 1);
const prefixField = new PrefixField(2, 5, false, true, 1);
const comboField = new SuffixAndPrefixField(2, 5, 2, 5, false, false, 1);
// TODO: SERVER-123416 test downgrade also prevented by the existence of non-preview types.
const forcePreview = true;
const substrField = new SubstringField(20, 2, 10, false, false, 1, forcePreview);
const suffixField = new SuffixField(2, 5, true, false, 1, forcePreview);
const prefixField = new PrefixField(2, 5, false, true, 1, forcePreview);
const comboField = new SuffixAndPrefixField(2, 5, 2, 5, false, false, 1, forcePreview);
function testBinaryDowngrade(queryTypeConfig) {
jsTestLog("Testing downgrade from latest to last-lts");

View File

@ -58,6 +58,8 @@ static void accumulateStats(ECOCStats& left, const ECOCStats& right) {
static void accumulateStats(FLEIndexTypeStats& left, const FLEIndexTypeStats& right) {
left.setEquality(left.getEquality() + right.getEquality());
left.setRange(left.getRange() + right.getRange());
left.setSuffix(left.getSuffix() + right.getSuffix());
left.setPrefix(left.getPrefix() + right.getPrefix());
left.setRangePreview(left.getRangePreview() + right.getRangePreview());
left.setSubstringPreview(left.getSubstringPreview() + right.getSubstringPreview());
left.setSuffixPreview(left.getSuffixPreview() + right.getSuffixPreview());

View File

@ -175,6 +175,8 @@ TEST_F(FLEStatsTest, IndexTypeStats) {
int64_t equality = 0;
int64_t unindexed = 0;
int64_t range = 0;
int64_t suffix = 0;
int64_t prefix = 0;
int64_t rangePreview = 0;
int64_t substringPreview = 0;
int64_t suffixPreview = 0;
@ -187,6 +189,8 @@ TEST_F(FLEStatsTest, IndexTypeStats) {
ASSERT_EQ(actual.getEquality(), expected.equality);
ASSERT_EQ(actual.getUnindexed(), expected.unindexed);
ASSERT_EQ(actual.getRange(), expected.range);
ASSERT_EQ(actual.getSuffix(), expected.suffix);
ASSERT_EQ(actual.getPrefix(), expected.prefix);
ASSERT_EQ(actual.getRangePreview(), expected.rangePreview);
ASSERT_EQ(actual.getSubstringPreview(), expected.substringPreview);
ASSERT_EQ(actual.getSuffixPreview(), expected.suffixPreview);
@ -203,15 +207,18 @@ TEST_F(FLEStatsTest, IndexTypeStats) {
for (auto i = count; i > 0; i--) {
fields.emplace_back(keyId, indexType);
}
} else if (indexType == "multi") {
} else if (indexType == "multi" || indexType == "multiPreview") {
auto suffixType = (indexType == "multi") ? R"({"queryType": "suffix"})"
: R"({"queryType": "suffixPreview"})";
auto prefixType = (indexType == "multi") ? R"({"queryType": "prefix"})"
: R"({"queryType": "prefixPreview"})";
for (auto i = count; i > 0; i--) {
fields.emplace_back(keyId, indexType);
std::vector<QueryTypeConfig> queries;
queries.push_back(
QueryTypeConfig::parse(fromjson(R"({"queryType": "suffixPreview"})")));
queries.push_back(
QueryTypeConfig::parse(fromjson(R"({"queryType": "prefixPreview"})")));
queries.push_back(QueryTypeConfig::parse(fromjson(suffixType)));
queries.push_back(QueryTypeConfig::parse(fromjson(prefixType)));
fields.back().setQueries(
std::variant<std::vector<QueryTypeConfig>, QueryTypeConfig>(
@ -237,7 +244,7 @@ TEST_F(FLEStatsTest, IndexTypeStats) {
IndexTypeCounters expected;
assertCounters(expected);
auto efc1 = buildConfig({{"unindexed", 4}, {"equality", 2}, {"multi", 3}});
auto efc1 = buildConfig({{"unindexed", 4}, {"equality", 2}, {"multiPreview", 3}});
instance->updateIndexTypeStatsOnRegisterCollection(efc1);
expected.unindexed++;
expected.equality++;
@ -255,7 +262,8 @@ TEST_F(FLEStatsTest, IndexTypeStats) {
auto efc3 = buildConfig({{"suffixPreview", 1}, {"multi", 1}});
instance->updateIndexTypeStatsOnRegisterCollection(efc3);
expected.suffixPreview++;
expected.prefixPreview++;
expected.suffix++;
expected.prefix++;
assertCounters(expected);
instance->updateIndexTypeStatsOnDeregisterCollection(efc2);
@ -266,12 +274,14 @@ TEST_F(FLEStatsTest, IndexTypeStats) {
instance->updateIndexTypeStatsOnRegisterCollection(efc3);
expected.suffixPreview++;
expected.prefixPreview++;
expected.suffix++;
expected.prefix++;
assertCounters(expected);
instance->updateIndexTypeStatsOnDeregisterCollection(efc3);
expected.suffixPreview--;
expected.prefixPreview--;
expected.suffix--;
expected.prefix--;
assertCounters(expected);
instance->updateIndexTypeStatsOnDeregisterCollection(efc1);
@ -283,7 +293,8 @@ TEST_F(FLEStatsTest, IndexTypeStats) {
instance->updateIndexTypeStatsOnDeregisterCollection(efc3);
expected.suffixPreview--;
expected.prefixPreview--;
expected.suffix--;
expected.prefix--;
assertCounters(expected);
}

View File

@ -1811,16 +1811,7 @@ EncryptedFieldConfig QETextSearchCrudTest::getEFC() {
std::vector<QueryTypeConfig> qtcs;
for (const auto& schema : _schemas) {
QueryTypeConfig qtc;
// Use the deprecated "Preview" query type names when building the EFC that gets
// passed to libmongocrypt, since libmongocrypt does not yet recognize the new names.
// TODO SERVER-123416 Remove this mapping once libmongocrypt supports "suffix"/"prefix".
auto mongocryptType = schema.type;
if (mongocryptType == QueryTypeEnum::Suffix) {
mongocryptType = QueryTypeEnum::SuffixPreviewDeprecated;
} else if (mongocryptType == QueryTypeEnum::Prefix) {
mongocryptType = QueryTypeEnum::PrefixPreviewDeprecated;
}
qtc.setQueryType(mongocryptType);
qtc.setQueryType(schema.type);
if (schema.type == QueryTypeEnum::SubstringPreview) {
qtc.setStrMaxLength(schema.mlen);
}