Skip to content
This repository was archived by the owner on Oct 31, 2023. It is now read-only.

Commit 9363cfe

Browse files
alyacbEvergreen Agent
authored andcommitted
SERVER-51540 Support mod expression in SBE
1 parent a4c5970 commit 9363cfe

File tree

10 files changed

+366
-11
lines changed

10 files changed

+366
-11
lines changed

buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_jscore_passthrough.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ selector:
5454
- jstests/core/or2.js
5555
- jstests/core/or3.js
5656
- jstests/core/orj.js
57+
- jstests/core/projection_expr_mod.js
5758
- jstests/core/ref.js
5859
- jstests/core/ref4.js
5960
- jstests/core/regex_limit.js

jstests/aggregation/sources/merge/mode_replace_insert.js

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
11
// Tests for the $merge stage with whenMatched: "replace" and whenNotMatched: "insert".
2-
// @tags: [
3-
// sbe_incompatible,
4-
// ]
52
(function() {
63
"use strict";
74

jstests/core/mod_overflow.js

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
/**
22
* Tests that modding the smallest representable integer values by -1 does not result in integer
33
* overflow. Exercises the fix for SERVER-43699.
4-
* @tags: [
5-
* sbe_incompatible,
6-
* ]
74
*/
85
(function() {
96
"use strict";

jstests/core/not2.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
// @tags: [
22
// requires_non_retryable_writes,
3-
// sbe_incompatible,
43
// ]
54

65
(function() {
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
// Confirm correctness of $mod evaluation in find projection.
2+
3+
(function() {
4+
"use strict";
5+
6+
load("jstests/aggregation/extras/utils.js"); // For assertArrayEq.
7+
8+
// Note that the "getParameter" command is expected to fail in versions of mongod that do not yet
9+
// include the slot-based execution engine. When that happens, however, 'isSBEEnabled' still
10+
// correctly evaluates to false.
11+
const isSBEEnabled = db.adminCommand({
12+
getParameter: 1,
13+
internalQueryEnableSlotBasedExecutionEngine: 1
14+
}).internalQueryEnableSlotBasedExecutionEngine;
15+
if (isSBEEnabled) {
16+
// Override error-code-checking APIs. We only load this when SBE is explicitly enabled, because
17+
// it causes failures in the parallel suites.
18+
load("jstests/libs/sbe_assert_error_override.js");
19+
}
20+
21+
const coll = db.projection_expr_mod;
22+
coll.drop();
23+
24+
assert.commandWorked(coll.insertMany([
25+
{n: "double % double", v: 138.5, m: 3.0},
26+
{n: "double % long", v: 138.5, m: NumberLong(3)},
27+
{n: "double % int", v: 138.5, m: NumberInt(3)},
28+
{n: "int % double", v: NumberInt(8), m: 3.25},
29+
{n: "int % double int", v: NumberInt(8), m: 3.0},
30+
{n: "int % int", v: NumberInt(8), m: NumberInt(3)},
31+
{n: "int % long", v: NumberInt(8), m: NumberLong(3)},
32+
{n: "long % double", v: NumberLong(8), m: 3.25},
33+
{n: "long % double int", v: NumberLong(8), m: 3.0},
34+
{n: "long % double long", v: NumberLong(500000000000), m: 450000000000.0},
35+
{n: "long % int", v: NumberLong(8), m: NumberInt(3)},
36+
{n: "long % long", v: NumberLong(8), m: NumberLong(3)},
37+
{n: "very long % very long", v: NumberLong(800000000000), m: NumberLong(300000000000)}
38+
]));
39+
40+
const result = coll.find({}, {f: {$mod: ["$v", "$m"]}, _id: 0, n: 1}).toArray();
41+
const expectedResult = [
42+
{n: "double % double", f: 0.5},
43+
{n: "double % long", f: 0.5},
44+
{n: "double % int", f: 0.5},
45+
{n: "int % double", f: 1.5},
46+
{n: "int % double int", f: 2},
47+
{n: "int % int", f: 2},
48+
{n: "int % long", f: NumberLong(2)},
49+
{n: "long % double", f: 1.5},
50+
{n: "long % double int", f: NumberLong(2)},
51+
{n: "long % double long", f: 50000000000},
52+
{n: "long % int", f: NumberLong(2)},
53+
{n: "long % long", f: NumberLong(2)},
54+
{n: "very long % very long", f: NumberLong(200000000000)}
55+
];
56+
assertArrayEq({actual: result, expected: expectedResult});
57+
58+
//
59+
// Confirm error cases.
60+
//
61+
62+
// Confirm mod by 0 fails in an expected manner.
63+
assert(coll.drop());
64+
assert.commandWorked(coll.insert({a: 10}));
65+
let error = assert.throws(() => coll.find({}, {f: {$mod: ["$a", 0]}, _id: 0, n: 1}).toArray());
66+
assert.commandFailedWithCode(error, 16610);
67+
68+
assert(coll.drop());
69+
assert.commandWorked(coll.insert({a: NumberInt(10)}));
70+
error =
71+
assert.throws(() => coll.find({}, {f: {$mod: ["$a", NumberInt(0)]}, _id: 0, n: 1}).toArray());
72+
assert.commandFailedWithCode(error, 16610);
73+
74+
assert(coll.drop());
75+
assert.commandWorked(coll.insert({a: NumberLong(10)}));
76+
error =
77+
assert.throws(() => coll.find({}, {f: {$mod: ["$a", NumberLong(0)]}, _id: 0, n: 1}).toArray());
78+
assert.commandFailedWithCode(error, 16610);
79+
80+
// Clear collection again and reset.
81+
assert(coll.drop());
82+
assert.commandWorked(coll.insert({a: 10}));
83+
84+
// Confirm expected behavior for NaN and Infinity values.
85+
assert.eq(coll.findOne({}, {f: {$mod: ["$a", NaN]}, _id: 0}), {f: NaN});
86+
assert.eq(coll.findOne({}, {f: {$mod: ["$a", Infinity]}, _id: 0}), {f: 10});
87+
assert.eq(coll.findOne({}, {f: {$mod: ["$a", -Infinity]}, _id: 0}), {f: 10});
88+
assert.eq(coll.findOne({}, {f: {$mod: [Infinity, "$a"]}, _id: 0}), {f: NaN});
89+
assert.eq(coll.findOne({}, {f: {$mod: [-Infinity, "$a"]}, _id: 0}), {f: NaN});
90+
assert.eq(coll.findOne({}, {f: {$mod: [NaN, "$a"]}, _id: 0}), {f: NaN});
91+
})();

jstests/libs/sbe_assert_error_override.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ const equivalentErrorCodesList = [
3030
[16007, 5066300],
3131
[16608, 4848401],
3232
[16609, 5073101],
33+
[16610, 4848403],
3334
[16555, 5073102],
3435
[28680, 4903701],
3536
[28689, 5126701],

src/mongo/db/exec/sbe/SConscript

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ env.CppUnitTest(
9696
'expressions/sbe_index_of_test.cpp',
9797
'expressions/sbe_is_member_builtin_test.cpp',
9898
'expressions/sbe_iso_date_to_parts_test.cpp',
99+
'expressions/sbe_mod_expression_test.cpp',
99100
'expressions/sbe_set_expressions_test.cpp',
100101
'expressions/sbe_to_upper_to_lower_test.cpp',
101102
'expressions/sbe_trigonometric_expressions_test.cpp',
Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
/**
2+
* Copyright (C) 2020-present MongoDB, Inc.
3+
*
4+
* This program is free software: you can redistribute it and/or modify
5+
* it under the terms of the Server Side Public License, version 1,
6+
* as published by MongoDB, Inc.
7+
*
8+
* This program is distributed in the hope that it will be useful,
9+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
* Server Side Public License for more details.
12+
*
13+
* You should have received a copy of the Server Side Public License
14+
* along with this program. If not, see
15+
* <http://www.mongodb.com/licensing/server-side-public-license>.
16+
*
17+
* As a special exception, the copyright holders give permission to link the
18+
* code of portions of this program with the OpenSSL library under certain
19+
* conditions as described in each individual source file and distribute
20+
* linked combinations including the program with the OpenSSL library. You
21+
* must comply with the Server Side Public License in all respects for
22+
* all of the code used other than as permitted herein. If you modify file(s)
23+
* with this exception, you may extend this exception to your version of the
24+
* file(s), but you are not obligated to do so. If you do not wish to do so,
25+
* delete this exception statement from your version. If you delete this
26+
* exception statement from all source files in the program, then also delete
27+
* it in the license file.
28+
*/
29+
30+
#include "mongo/db/exec/sbe/expression_test_base.h"
31+
32+
namespace mongo::sbe {
33+
34+
class SBEModExprTest : public EExpressionTestFixture {
35+
protected:
36+
void runAndAssertExpression(const vm::CodeFragment* compiledExpr, double expectedVal) {
37+
auto [tag, val] = runCompiledExpression(compiledExpr);
38+
value::ValueGuard guard(tag, val);
39+
ASSERT_EQUALS(value::TypeTags::NumberDouble, tag);
40+
ASSERT_APPROX_EQUAL(value::bitcastTo<double>(val), expectedVal, 0.000001);
41+
}
42+
43+
void runAndAssertExpression(const vm::CodeFragment* compiledExpr, Decimal128 expectedVal) {
44+
auto [tag, val] = runCompiledExpression(compiledExpr);
45+
value::ValueGuard guard(tag, val);
46+
47+
ASSERT_EQUALS(value::TypeTags::NumberDecimal, tag);
48+
ASSERT(value::bitcastTo<Decimal128>(val)
49+
.subtract(expectedVal)
50+
.toAbs()
51+
.isLessEqual(Decimal128(".000001")));
52+
}
53+
54+
void runAndAssertExpression(const vm::CodeFragment* compiledExpr, int32_t expectedVal) {
55+
auto [tag, val] = runCompiledExpression(compiledExpr);
56+
value::ValueGuard guard(tag, val);
57+
ASSERT_EQUALS(value::TypeTags::NumberInt32, tag);
58+
ASSERT_EQUALS(value::bitcastTo<int32_t>(val), expectedVal);
59+
}
60+
61+
void runAndAssertExpression(const vm::CodeFragment* compiledExpr, int64_t expectedVal) {
62+
auto [tag, val] = runCompiledExpression(compiledExpr);
63+
value::ValueGuard guard(tag, val);
64+
ASSERT_EQUALS(value::TypeTags::NumberInt64, tag);
65+
ASSERT_EQUALS(value::bitcastTo<int64_t>(val), expectedVal);
66+
}
67+
68+
void runAndAssertNothing(const vm::CodeFragment* compiledExpr) {
69+
auto [tag, val] = runCompiledExpression(compiledExpr);
70+
value::ValueGuard guard(tag, val);
71+
ASSERT_EQUALS(value::TypeTags::Nothing, tag);
72+
}
73+
74+
void runAndAssertThrows(const vm::CodeFragment* compiledExpr) {
75+
ASSERT_THROWS_CODE(runCompiledExpression(compiledExpr), AssertionException, 4848403);
76+
}
77+
};
78+
79+
TEST_F(SBEModExprTest, ComputesMod) {
80+
value::OwnedValueAccessor slotAccessor1, slotAccessor2;
81+
auto argSlot1 = bindAccessor(&slotAccessor1);
82+
auto argSlot2 = bindAccessor(&slotAccessor2);
83+
auto modExpr = sbe::makeE<sbe::EFunction>(
84+
"mod", sbe::makeEs(makeE<EVariable>(argSlot1), makeE<EVariable>(argSlot2)));
85+
auto compiledExpr = compileExpression(*modExpr);
86+
87+
const int32_t i32Val = 16;
88+
const int32_t i32Mod = 4;
89+
const int64_t i64Val = 2147483648;
90+
const int64_t i64Mod = 2147483649;
91+
const Decimal128 decVal(123.4);
92+
const Decimal128 decMod(43.2);
93+
const double dblVal(9.9);
94+
const double dblMod(2.3);
95+
96+
slotAccessor1.reset(value::TypeTags::NumberInt32, value::bitcastFrom<int32_t>(i32Val));
97+
slotAccessor2.reset(value::TypeTags::NumberInt32, value::bitcastFrom<int32_t>(i32Mod));
98+
runAndAssertExpression(compiledExpr.get(), i32Val % i32Mod);
99+
100+
slotAccessor1.reset(value::TypeTags::NumberInt64, value::bitcastFrom<int64_t>(i64Val));
101+
slotAccessor2.reset(value::TypeTags::NumberInt64, value::bitcastFrom<int64_t>(i64Mod));
102+
runAndAssertExpression(compiledExpr.get(), i64Val % i64Mod);
103+
104+
auto [tagArgDecVal, valArgDecVal] = value::makeCopyDecimal(decVal);
105+
slotAccessor1.reset(tagArgDecVal, valArgDecVal);
106+
auto [tagArgDecMod, valArgDecMod] = value::makeCopyDecimal(decMod);
107+
slotAccessor2.reset(tagArgDecMod, valArgDecMod);
108+
runAndAssertExpression(compiledExpr.get(), decVal.modulo(decMod));
109+
110+
slotAccessor1.reset(value::TypeTags::NumberDouble, value::bitcastFrom<double>(dblVal));
111+
slotAccessor2.reset(value::TypeTags::NumberDouble, value::bitcastFrom<double>(dblMod));
112+
runAndAssertExpression(compiledExpr.get(), std::fmod(dblVal, dblMod));
113+
}
114+
115+
TEST_F(SBEModExprTest, ComputesModDifferentWidths) {
116+
value::OwnedValueAccessor slotAccessor1, slotAccessor2;
117+
auto argSlot1 = bindAccessor(&slotAccessor1);
118+
auto argSlot2 = bindAccessor(&slotAccessor2);
119+
auto modExpr = sbe::makeE<sbe::EFunction>(
120+
"mod", sbe::makeEs(makeE<EVariable>(argSlot1), makeE<EVariable>(argSlot2)));
121+
auto compiledExpr = compileExpression(*modExpr);
122+
123+
const int32_t i32Val = 16;
124+
const int32_t i32Mod = 4;
125+
const int64_t i64Val = 2147483648;
126+
const int64_t i64Mod = 2147483649;
127+
128+
slotAccessor1.reset(value::TypeTags::NumberInt64, value::bitcastFrom<int64_t>(i64Val));
129+
slotAccessor2.reset(value::TypeTags::NumberInt32, value::bitcastFrom<int32_t>(i32Mod));
130+
runAndAssertExpression(compiledExpr.get(), i64Val % i32Mod);
131+
132+
slotAccessor1.reset(value::TypeTags::NumberInt32, value::bitcastFrom<int32_t>(i32Val));
133+
slotAccessor2.reset(value::TypeTags::NumberInt64, value::bitcastFrom<int64_t>(i64Mod));
134+
runAndAssertExpression(compiledExpr.get(), i32Val % i64Mod);
135+
}
136+
137+
TEST_F(SBEModExprTest, ComputesNothingIfNotNumeric) {
138+
value::OwnedValueAccessor slotAccessor1, slotAccessor2;
139+
auto argSlot1 = bindAccessor(&slotAccessor1);
140+
auto argSlot2 = bindAccessor(&slotAccessor2);
141+
auto modExpr = sbe::makeE<sbe::EFunction>(
142+
"mod", sbe::makeEs(makeE<EVariable>(argSlot1), makeE<EVariable>(argSlot2)));
143+
auto compiledExpr = compileExpression(*modExpr);
144+
145+
const int32_t i32Val = 16;
146+
const int32_t i32Mod = 4;
147+
148+
auto [tagStrArg1, valStrArg1] = value::makeNewString("abc");
149+
slotAccessor1.reset(tagStrArg1, valStrArg1);
150+
auto [tagStrArg2, valStrArg2] = value::makeNewString("xyz");
151+
slotAccessor2.reset(tagStrArg2, valStrArg2);
152+
runAndAssertNothing(compiledExpr.get());
153+
154+
slotAccessor1.reset(value::TypeTags::NumberInt32, value::bitcastFrom<int32_t>(i32Val));
155+
slotAccessor2.reset(tagStrArg2, valStrArg2);
156+
runAndAssertNothing(compiledExpr.get());
157+
158+
slotAccessor1.reset(tagStrArg1, valStrArg1);
159+
slotAccessor2.reset(value::TypeTags::NumberInt32, value::bitcastFrom<int32_t>(i32Mod));
160+
runAndAssertNothing(compiledExpr.get());
161+
}
162+
163+
TEST_F(SBEModExprTest, ComputesNothingIfNullOrMissing) {
164+
value::OwnedValueAccessor slotAccessor1, slotAccessor2;
165+
auto argSlot1 = bindAccessor(&slotAccessor1);
166+
auto argSlot2 = bindAccessor(&slotAccessor2);
167+
auto modExpr = sbe::makeE<sbe::EFunction>(
168+
"mod", sbe::makeEs(makeE<EVariable>(argSlot1), makeE<EVariable>(argSlot2)));
169+
auto compiledExpr = compileExpression(*modExpr);
170+
171+
const int32_t i32Val = 16;
172+
const int32_t i32Mod = 4;
173+
174+
slotAccessor1.reset(value::TypeTags::NumberInt32, value::bitcastFrom<int32_t>(i32Val));
175+
slotAccessor2.reset(value::TypeTags::Nothing, 0);
176+
runAndAssertNothing(compiledExpr.get());
177+
178+
slotAccessor1.reset(value::TypeTags::Nothing, 0);
179+
slotAccessor2.reset(value::TypeTags::NumberInt32, value::bitcastFrom<int32_t>(i32Mod));
180+
runAndAssertNothing(compiledExpr.get());
181+
182+
slotAccessor1.reset(value::TypeTags::Nothing, 0);
183+
slotAccessor2.reset(value::TypeTags::Nothing, 0);
184+
runAndAssertNothing(compiledExpr.get());
185+
186+
slotAccessor1.reset(value::TypeTags::NumberInt32, value::bitcastFrom<int32_t>(i32Val));
187+
slotAccessor2.reset(value::TypeTags::Null, 0);
188+
runAndAssertNothing(compiledExpr.get());
189+
190+
slotAccessor1.reset(value::TypeTags::Null, 0);
191+
slotAccessor2.reset(value::TypeTags::NumberInt32, value::bitcastFrom<int32_t>(i32Mod));
192+
runAndAssertNothing(compiledExpr.get());
193+
194+
slotAccessor1.reset(value::TypeTags::Null, 0);
195+
slotAccessor2.reset(value::TypeTags::Null, 0);
196+
runAndAssertNothing(compiledExpr.get());
197+
198+
slotAccessor1.reset(value::TypeTags::Null, 0);
199+
slotAccessor2.reset(value::TypeTags::Nothing, 0);
200+
runAndAssertNothing(compiledExpr.get());
201+
202+
slotAccessor1.reset(value::TypeTags::Nothing, 0);
203+
slotAccessor2.reset(value::TypeTags::Null, 0);
204+
runAndAssertNothing(compiledExpr.get());
205+
}
206+
207+
TEST_F(SBEModExprTest, ErrorIfModRHSIsZero) {
208+
value::OwnedValueAccessor slotAccessor1, slotAccessor2;
209+
auto argSlot1 = bindAccessor(&slotAccessor1);
210+
auto argSlot2 = bindAccessor(&slotAccessor2);
211+
auto modExpr = sbe::makeE<sbe::EFunction>(
212+
"mod", sbe::makeEs(makeE<EVariable>(argSlot1), makeE<EVariable>(argSlot2)));
213+
auto compiledExpr = compileExpression(*modExpr);
214+
215+
const int32_t i32Val = 16;
216+
const int32_t i32Mod = 0;
217+
const Decimal128 decVal(123.4);
218+
const Decimal128 decMod(0);
219+
220+
slotAccessor1.reset(value::TypeTags::NumberInt32, value::bitcastFrom<int32_t>(i32Val));
221+
slotAccessor2.reset(value::TypeTags::NumberInt32, value::bitcastFrom<int32_t>(i32Mod));
222+
runAndAssertThrows(compiledExpr.get());
223+
224+
auto [tagArgDecVal, valArgDecVal] = value::makeCopyDecimal(decVal);
225+
slotAccessor1.reset(tagArgDecVal, valArgDecVal);
226+
auto [tagArgDecMod, valArgDecMod] = value::makeCopyDecimal(decMod);
227+
slotAccessor2.reset(tagArgDecMod, valArgDecMod);
228+
runAndAssertThrows(compiledExpr.get());
229+
}
230+
231+
} // namespace mongo::sbe

src/mongo/db/exec/sbe/vm/arith.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -497,19 +497,19 @@ std::tuple<bool, value::TypeTags, value::Value> ByteCode::genericMod(value::Type
497497
return {false, value::TypeTags::NumberInt32, value::bitcastFrom<int32_t>(result)};
498498
}
499499
case value::TypeTags::NumberInt64: {
500-
assertNonZero(numericCast<int32_t>(rhsTag, rhsValue) != 0);
500+
assertNonZero(numericCast<int64_t>(rhsTag, rhsValue) != 0);
501501
auto result = overflow::safeMod(numericCast<int64_t>(lhsTag, lhsValue),
502502
numericCast<int64_t>(rhsTag, rhsValue));
503503
return {false, value::TypeTags::NumberInt64, value::bitcastFrom<int64_t>(result)};
504504
}
505505
case value::TypeTags::NumberDouble: {
506-
assertNonZero(numericCast<int32_t>(rhsTag, rhsValue) != 0);
506+
assertNonZero(numericCast<double>(rhsTag, rhsValue) != 0);
507507
auto result = fmod(numericCast<double>(lhsTag, lhsValue),
508508
numericCast<double>(rhsTag, rhsValue));
509509
return {false, value::TypeTags::NumberDouble, value::bitcastFrom<double>(result)};
510510
}
511511
case value::TypeTags::NumberDecimal: {
512-
assertNonZero(numericCast<Decimal128>(rhsTag, rhsValue).isZero());
512+
assertNonZero(!numericCast<Decimal128>(rhsTag, rhsValue).isZero());
513513
auto result = numericCast<Decimal128>(lhsTag, lhsValue)
514514
.modulo(numericCast<Decimal128>(rhsTag, rhsValue));
515515
auto [tag, val] = value::makeCopyDecimal(result);

0 commit comments

Comments
 (0)