SERVER-122105 IDL compiler generic annotations block (#52568)

GitOrigin-RevId: 44194d7de6043776c2f1940520f3b296b9f98931
This commit is contained in:
Catalin Sumanaru 2026-04-24 15:20:43 +01:00 committed by MongoDB Bot
parent 20bbf04949
commit 18025e809a
13 changed files with 626 additions and 0 deletions

View File

@ -492,6 +492,8 @@ class ServerParameter(common.SourceLocation):
self.is_deprecated = False # type: bool
self.annotations = None # type: Dict[str, Any]
super(ServerParameter, self).__init__(file_name, line, column)

View File

@ -1567,6 +1567,7 @@ def _bind_server_parameter(ctxt, param):
ast_param.omit_in_ftdc = param.omit_in_ftdc
ast_param.is_deprecated = param.is_deprecated
ast_param.annotations = param.annotations
ast_param.set_at = _bind_server_parameter_set_at(ctxt, param)
if ast_param.set_at is None:

View File

@ -30,6 +30,7 @@
import hashlib
import io
import itertools
import math
import os
import re
import sys
@ -3171,6 +3172,86 @@ class _CppSourceFileWriter(_CppFileWriterBase):
f'{{"{entry.name}", {"true" if entry.generic_field_info.get_should_forward() else "false"} }},'
)
def _gen_annotations_elem(self, builder, key, value, depth=0):
# type: (str, Optional[str], Any, int) -> None
"""Dispatch an annotation value to the appropriate generator."""
if value is None:
self._gen_annotations_null(builder, key)
elif isinstance(value, dict):
self._gen_annotations_obj(builder, key, value, depth)
elif isinstance(value, list):
self._gen_annotations_arr(builder, key, value, depth)
else:
self._gen_annotations_scalar(builder, key, value)
def _gen_annotations_obj(self, builder, key, obj, depth):
# type: (str, Optional[str], Dict[str, Any], int) -> None
"""Emit a BSONObjBuilder sub-object for a dict."""
sub = "sub%d" % depth
start = "%s.subobjStart(%s)" % (builder, key) if key else "%s.subobjStart()" % builder
with self._block("{", "}"):
self._writer.write_line("BSONObjBuilder %s(%s);" % (sub, start))
for k, v in obj.items():
self._gen_annotations_elem(sub, _encaps(k), v, depth + 1)
def _gen_annotations_arr(self, builder, key, items, depth):
# type: (str, Optional[str], List[Any], int) -> None
"""Emit a BSONArrayBuilder sub-array for a list."""
arr = "arr%d" % depth
start = "%s.subarrayStart(%s)" % (builder, key) if key else "%s.subarrayStart()" % builder
with self._block("{", "}"):
self._writer.write_line("BSONArrayBuilder %s(%s);" % (arr, start))
for item in items:
self._gen_annotations_elem(arr, None, item, depth + 1)
def _gen_annotations_null(self, builder, key):
# type: (str, Optional[str]) -> None
"""Emit an appendNull call."""
if key:
self._writer.write_line("%s.appendNull(%s);" % (builder, key))
else:
self._writer.write_line("%s.appendNull();" % builder)
def _gen_annotations_scalar(self, builder, key, value):
# type: (str, Optional[str], Any) -> None
"""Emit an append call for a scalar value."""
if isinstance(value, bool):
literal = "true" if value else "false"
elif isinstance(value, int):
literal = str(value)
elif isinstance(value, float):
if not math.isfinite(value):
raise ValueError(
f"Unsupported annotation value {value!r} for key {key!r}: "
f"NaN and infinity are not valid C++ literals."
)
literal = str(value)
elif isinstance(value, str):
literal = _encaps(value)
else:
raise ValueError(
f"Unsupported annotation type {type(value).__name__} for {key}: "
f"expected bool, int, float, or str."
)
if key:
self._writer.write_line("%s.append(%s, %s);" % (builder, key, literal))
else:
self._writer.write_line("%s.append(%s);" % (builder, literal))
def _gen_server_parameter_annotations(self, var_name, param):
# type: (str, ast.ServerParameter) -> None
"""Generate setAnnotations call if the parameter has annotations."""
if param.annotations:
with self.get_initializer_lambda(
"static const auto kAnnotations", return_type="BSONObj"
):
self._writer.write_line("BSONObjBuilder builder;")
for k, v in param.annotations.items():
self._gen_annotations_elem("builder", _encaps(k), v)
self._writer.write_line("return builder.obj();")
self._writer.write_line("%s->setAnnotations(kAnnotations);" % var_name)
def _gen_server_parameter_specialized(self, param):
# type: (ast.ServerParameter) -> None
"""Generate a specialized ServerParameter."""
@ -3187,6 +3268,8 @@ class _CppSourceFileWriter(_CppFileWriterBase):
if param.is_deprecated:
self._writer.write_line("sp->setIsDeprecated(true);")
self._gen_server_parameter_annotations("sp", param)
self._writer.write_line("return std::move(sp);")
def _gen_server_parameter_class_definitions(self, param):
@ -3261,6 +3344,8 @@ class _CppSourceFileWriter(_CppFileWriterBase):
if param.is_deprecated:
self._writer.write_line("ret->setIsDeprecated(true);")
self._gen_server_parameter_annotations("ret", param)
if param.default and not (param.cpp_vartype and param.cpp_varname):
# Only need to call setDefault() if we haven't in-place initialized the declared var.
self._writer.write_line(

View File

@ -917,6 +917,13 @@ def _parse_server_parameter_class(ctxt, node):
return spc
def _parse_annotations(ctxt, node):
# type: (errors.ParserContext, yaml.nodes.MappingNode) -> Dict[str, Any]
"""Parse an annotations block as an opaque YAML mapping."""
constructor = yaml.constructor.SafeConstructor()
return constructor.construct_object(node, deep=True)
def _parse_server_parameter(ctxt, spec, name, node):
# type: (errors.ParserContext, syntax.IDLSpec, str, Union[yaml.nodes.MappingNode, yaml.nodes.ScalarNode, yaml.nodes.SequenceNode]) -> None
"""Parse a server_parameters section in the IDL file."""
@ -950,6 +957,7 @@ def _parse_server_parameter(ctxt, spec, name, node):
"omit_in_ftdc": _RuleDesc("bool_scalar"),
"cpp_class": _RuleDesc("scalar_or_mapping", mapping_parser_func=map_class),
"is_deprecated": _RuleDesc("bool_scalar"),
"annotations": _RuleDesc("mapping", mapping_parser_func=_parse_annotations),
},
)

View File

@ -858,6 +858,8 @@ class ServerParameter(common.SourceLocation):
self.is_deprecated = False # type: bool
self.annotations = None # type: Dict[str, Any]
super(ServerParameter, self).__init__(file_name, line, column)

View File

@ -690,6 +690,11 @@ properties:
description: >
Marks the server parameter as deprecated. Warns users if the server parameter
is ever used. Defaults to false.
annotations:
type: object
description: >
Opaque metadata block attached to the server parameter. Parsed as-is and
exposed at runtime via ServerParameter::annotations() as a BSONObj.
on_update:
type: string
description: >

View File

@ -39,3 +39,4 @@ import idl.errors # noqa: F401
import idl.generator # noqa: F401
import idl.parser # noqa: F401
import idl.syntax # noqa: F401
import idl.writer # noqa: F401

View File

@ -2364,6 +2364,76 @@ class TestBinder(testcase.IDLTestcase):
""")
)
def test_server_parameter_annotations_positive(self):
# type: () -> None
"""Positive server parameter annotations test cases."""
# server parameter with cpp_varname and simple annotations.
self.assert_bind(
textwrap.dedent("""
server_parameters:
foo:
set_at: startup
description: bar
redact: false
cpp_varname: baz
annotations:
query_knob:
wire_name: fooWire
applicability: queryShape
""")
)
# server parameter with cpp_class and annotations.
self.assert_bind(
textwrap.dedent("""
server_parameters:
foo:
set_at: startup
description: bar
redact: false
cpp_class: baz
annotations:
query_knob:
wire_name: fooWire
""")
)
# annotations with nested mapping (fcv sub-block).
self.assert_bind(
textwrap.dedent("""
server_parameters:
foo:
set_at: startup
description: bar
redact: false
cpp_varname: baz
annotations:
query_knob:
wire_name: fooWire
applicability: queryShape
fcv:
min: "9.0"
""")
)
# multiple annotation keys.
self.assert_bind(
textwrap.dedent("""
server_parameters:
foo:
set_at: startup
description: bar
redact: false
cpp_varname: baz
annotations:
query_knob:
wire_name: fooWire
security:
audit: true
""")
)
def test_server_parameter_negative(self):
# type: () -> None
"""Negative server parameter test cases."""

View File

@ -36,6 +36,7 @@ idl base directory:
$ coverage run run_tests.py && coverage html
"""
import datetime
import io
import os
import unittest
@ -475,6 +476,157 @@ class TestGenerator(testcase.IDLTestcase):
)
self.assertIn(expected, header)
def test_server_parameter_annotations_generates_set_call(self) -> None:
"""Test generation of setAnnotations call for annotated server parameter."""
_, source = self.assert_generate(
self.view_test_common_types
+ dedent("""
server_parameters:
testAnnotatedParam:
description: "Test annotated parameter"
set_at: ["startup", "runtime"]
redact: false
cpp_varname: testParameter
annotations:
query_knob:
wire_name: testWire
applicability: queryShape
""")
)
# Verify the BSON builder lambda is generated.
self.assertIn("kAnnotations", source)
self.assertIn("BSONObjBuilder builder", source)
self.assertIn('"query_knob"', source)
self.assertIn('"wire_name"', source)
self.assertIn('"testWire"', source)
self.assertIn('"applicability"', source)
self.assertIn('"queryShape"', source)
# Verify setAnnotations call.
self.assertIn("setAnnotations(kAnnotations)", source)
def test_server_parameter_no_annotations_no_set_call(self) -> None:
"""Test that server parameter without annotations does not generate setAnnotations."""
_, source = self.assert_generate(
self.view_test_common_types
+ dedent("""
server_parameters:
testUnannotatedParam:
description: "Test unannotated parameter"
set_at: ["startup", "runtime"]
redact: false
cpp_varname: testParameter
""")
)
self.assertNotIn("setAnnotations", source)
self.assertNotIn("kAnnotations", source)
def test_server_parameter_nested_annotations_bson(self) -> None:
"""Test nested annotation values produce correct nested BSON output."""
_, source = self.assert_generate(
self.view_test_common_types
+ dedent("""
server_parameters:
testNestedParam:
description: "Test nested annotations"
set_at: ["startup", "runtime"]
redact: false
cpp_varname: testParameter
annotations:
query_knob:
wire_name: testNestedWire
applicability: queryShape
fcv:
min: "9.0"
""")
)
# Verify nested BSON structure.
self.assertIn("kAnnotations", source)
self.assertIn("subobjStart", source)
self.assertIn('"fcv"', source)
self.assertIn('"min"', source)
self.assertIn('"9.0"', source)
# Verify setAnnotations call.
self.assertIn("setAnnotations(kAnnotations)", source)
def test_server_parameter_annotations_mixed_types_bson(self) -> None:
"""Test annotation values of different YAML types produce correct BSON output."""
_, source = self.assert_generate(
self.view_test_common_types
+ dedent("""
server_parameters:
testMixedParam:
description: "Test mixed-type annotations"
set_at: ["startup", "runtime"]
redact: false
cpp_varname: testParameter
annotations:
query_knob:
wire_name: testMixedWire
pqs_settable: true
max_retries: 3
applicability:
- queryShape
- opCtx
""")
)
# Verify string value.
self.assertIn('"testMixedWire"', source)
# Verify boolean value appended.
self.assertIn('append("pqs_settable", true)', source)
# Verify integer value appended.
self.assertIn('append("max_retries", 3)', source)
# Verify array value.
self.assertIn("subarrayStart", source)
self.assertIn('"queryShape"', source)
self.assertIn('"opCtx"', source)
# Verify setAnnotations call.
self.assertIn("setAnnotations(kAnnotations)", source)
def test_server_parameter_annotations_rejects_unquoted_date(self) -> None:
"""YAML auto-coerces unquoted dates to datetime.date; the generator must reject them."""
with self.assertRaises(ValueError):
self.assert_generate(
self.view_test_common_types
+ dedent("""
server_parameters:
testDateParam:
description: "Test param with unquoted date annotation"
set_at: ["startup", "runtime"]
redact: false
cpp_varname: testParameter
annotations:
wire_name: 2026-04-14
""")
)
def test_server_parameter_annotations_accepts_quoted_date(self) -> None:
"""A quoted date stays a string and flows through as a C++ string literal."""
_, source = self.assert_generate(
self.view_test_common_types
+ dedent("""
server_parameters:
testQuotedDateParam:
description: "Test param with quoted date annotation"
set_at: ["startup", "runtime"]
redact: false
cpp_varname: testParameter
annotations:
wire_name: "2026-04-14"
""")
)
self.assertIn('"2026-04-14"', source)
self.assertIn("setAnnotations(kAnnotations)", source)
def test_command_view_type_generates_anchor(self) -> None:
"""Test anchor generation on command with view parameter."""
header, _ = self.assert_generate(
@ -1700,5 +1852,136 @@ class TestGenerator(testcase.IDLTestcase):
)
class TestAnnotationsCodegen(testcase.IDLTestcase):
"""Direct unit tests for the _gen_annotations_* helpers on _CppSourceFileWriter."""
def _emit(self, key, value, depth=0):
# type: (object, object, int) -> str
"""Run _gen_annotations_elem on a fresh writer and return the generated text."""
stream = io.StringIO()
source_writer = idl.generator._CppSourceFileWriter(
idl.writer.IndentedTextWriter(stream), target_arch=""
)
source_writer._gen_annotations_elem("builder", key, value, depth=depth)
return stream.getvalue()
def test_scalar_string_keyed(self):
self.assertEqual(self._emit('"k"', "hello"), 'builder.append("k", "hello");\n')
def test_scalar_string_unkeyed(self):
self.assertEqual(self._emit(None, "hello"), 'builder.append("hello");\n')
def test_scalar_bool_true(self):
self.assertEqual(self._emit('"k"', True), 'builder.append("k", true);\n')
def test_scalar_bool_false(self):
self.assertEqual(self._emit('"k"', False), 'builder.append("k", false);\n')
def test_scalar_int(self):
self.assertEqual(self._emit('"k"', 3), 'builder.append("k", 3);\n')
def test_scalar_float(self):
self.assertEqual(self._emit('"k"', 3.25), 'builder.append("k", 3.25);\n')
def test_null_keyed(self):
self.assertEqual(self._emit('"k"', None), 'builder.appendNull("k");\n')
def test_null_unkeyed(self):
self.assertEqual(self._emit(None, None), "builder.appendNull();\n")
def test_empty_dict(self):
expected = dedent("""\
{
BSONObjBuilder sub0(builder.subobjStart("k"));
}
""")
self.assertEqual(self._emit('"k"', {}), expected)
def test_flat_dict(self):
expected = dedent("""\
{
BSONObjBuilder sub0(builder.subobjStart("k"));
sub0.append("a", 1);
}
""")
self.assertEqual(self._emit('"k"', {"a": 1}), expected)
def test_nested_dict_depth_counter(self):
# Depth increments so nested sub-builder names don't collide.
expected = dedent("""\
{
BSONObjBuilder sub0(builder.subobjStart("root"));
{
BSONObjBuilder sub1(sub0.subobjStart("outer"));
sub1.append("inner", 1);
}
}
""")
self.assertEqual(self._emit('"root"', {"outer": {"inner": 1}}), expected)
def test_empty_list(self):
expected = dedent("""\
{
BSONArrayBuilder arr0(builder.subarrayStart("k"));
}
""")
self.assertEqual(self._emit('"k"', []), expected)
def test_flat_list(self):
expected = dedent("""\
{
BSONArrayBuilder arr0(builder.subarrayStart("k"));
arr0.append(1);
arr0.append(2);
}
""")
self.assertEqual(self._emit('"k"', [1, 2]), expected)
def test_list_of_dicts(self):
# Items inside a list use the unkeyed subobjStart form and advance depth.
expected = dedent("""\
{
BSONArrayBuilder arr0(builder.subarrayStart("k"));
{
BSONObjBuilder sub1(arr0.subobjStart());
sub1.append("a", 1);
}
{
BSONObjBuilder sub1(arr0.subobjStart());
sub1.append("b", 2);
}
}
""")
self.assertEqual(self._emit('"k"', [{"a": 1}, {"b": 2}]), expected)
def test_scalar_rejects_date(self):
with self.assertRaises(ValueError):
self._emit('"k"', datetime.date(2026, 4, 14))
def test_scalar_rejects_datetime(self):
with self.assertRaises(ValueError):
self._emit('"k"', datetime.datetime(2026, 4, 14, 10, 30))
def test_scalar_rejects_nan(self):
with self.assertRaises(ValueError):
self._emit('"k"', float("nan"))
def test_scalar_rejects_positive_inf(self):
with self.assertRaises(ValueError):
self._emit('"k"', float("inf"))
def test_scalar_rejects_negative_inf(self):
with self.assertRaises(ValueError):
self._emit('"k"', float("-inf"))
def test_scalar_rejects_bytes(self):
with self.assertRaises(ValueError):
self._emit('"k"', b"hello")
def test_scalar_rejects_set(self):
with self.assertRaises(ValueError):
self._emit('"k"', {"a", "b"})
if __name__ == "__main__":
unittest.main()

View File

@ -1566,6 +1566,74 @@ class TestParser(testcase.IDLTestcase):
idl.errors.ERROR_ID_IS_NODE_TYPE_SCALAR_OR_MAPPING,
)
def test_server_parameter_annotations_positive(self):
# type: () -> None
"""Positive server parameter annotations tests."""
# server parameter with annotations mapping.
self.assert_parse(
textwrap.dedent("""
server_parameters:
foo:
set_at: startup
description: bar
redact: false
cpp_varname: baz
annotations:
query_knob:
wire_name: fooWire
applicability: queryShape
""")
)
def test_server_parameter_annotations_negative(self):
# type: () -> None
"""Negative server parameter annotations tests."""
# annotations as a scalar should fail. Must be a mapping.
self.assert_parse_fail(
textwrap.dedent("""
server_parameters:
foo:
set_at: startup
description: bar
redact: false
cpp_varname: baz
annotations: some_scalar_value
"""),
idl.errors.ERROR_ID_IS_NODE_TYPE,
)
# annotations as a sequence should fail. Must be a mapping.
self.assert_parse_fail(
textwrap.dedent("""
server_parameters:
foo:
set_at: startup
description: bar
redact: false
cpp_varname: baz
annotations:
- item1
- item2
"""),
idl.errors.ERROR_ID_IS_NODE_TYPE,
)
# annotations as null should fail. Must be a mapping.
self.assert_parse_fail(
textwrap.dedent("""
server_parameters:
foo:
set_at: startup
description: bar
redact: false
cpp_varname: baz
annotations: null
"""),
idl.errors.ERROR_ID_IS_NODE_TYPE,
)
def test_feature_flag(self):
# type: () -> None
"""Test feature flag."""

View File

@ -387,6 +387,14 @@ public:
return _isDeprecated;
}
void setAnnotations(BSONObj annotations) {
_annotations = std::move(annotations);
}
BSONObj annotations() const {
return _annotations;
}
protected:
// Helper for translating setParameter values from BSON to string.
StatusWith<std::string> _coerceToString(const BSONElement&);
@ -410,6 +418,7 @@ private:
bool _testOnly = false;
bool _redact = false;
bool _isOmittedInFTDC = false;
BSONObj _annotations;
ParameterGatingFeatureFlag* _featureFlag = nullptr;
boost::optional<multiversion::FeatureCompatibilityVersion> _minFCV;

View File

@ -298,6 +298,43 @@ TEST(IDLServerParameterWithStorage, exportedDefaults) {
ASSERT_EQ(test::kUgly_complicated_name_spDefault, true);
}
TEST(IDLServerParameterWithStorage, annotationsAccessible) {
auto* sp = getNodeServerParameter("storageIntAnnotated");
ASSERT_BSONOBJ_EQ(
sp->annotations(),
BSON("query_knob" << BSON("wire_name" << "intAnnotatedWire"
<< "applicability" << BSON_ARRAY("queryShape")
<< "fcv" << BSON("min" << "9.0"))));
}
TEST(IDLServerParameterWithStorage, noAnnotationsReturnsEmpty) {
auto* sp = getNodeServerParameter("stdIntDeclared");
ASSERT_TRUE(sp->annotations().isEmpty());
}
TEST(IDLServerParameterWithStorage, annotationsArrayOfObjects) {
auto* sp = getNodeServerParameter("storageIntArrayOfObjects");
ASSERT_BSONOBJ_EQ(sp->annotations(),
BSON("items" << BSON_ARRAY(BSON("name" << "first" << "value" << 1)
<< BSON("name" << "second" << "value" << 2))));
}
TEST(IDLServerParameterWithStorage, annotationsNestedArrays) {
auto* sp = getNodeServerParameter("storageIntNestedArrays");
ASSERT_BSONOBJ_EQ(sp->annotations(),
BSON("matrix" << BSON_ARRAY(BSON_ARRAY(1 << 2) << BSON_ARRAY(3 << 4))));
}
TEST(IDLServerParameterWithStorage, annotationsEmptyArray) {
auto* sp = getNodeServerParameter("storageIntEmptyArray");
ASSERT_BSONOBJ_EQ(sp->annotations(), BSON("tags" << BSONArray()));
}
TEST(IDLServerParameterWithStorage, annotationsOnClusterParameter) {
auto* sp = getClusterServerParameter("testClusterServerParameter");
ASSERT_BSONOBJ_EQ(sp->annotations(), BSON("cluster_meta" << BSON("scope" << "global")));
}
// Test that the RAIIServerParameterControllerForTest works correctly on IDL-generated types.
TEST(IDLServerParameterWithStorage, RAIIServerParameterController) {
// Test int

View File

@ -108,6 +108,9 @@ server_parameters:
on_update: "onUpdateTestClusterServerParameter"
redact: false
omit_in_ftdc: false
annotations:
cluster_meta:
scope: global
storageIntDeprecated:
set_at: [startup, runtime]
@ -120,3 +123,55 @@ server_parameters:
lt: 1000
redact: false
is_deprecated: true
storageIntAnnotated:
set_at: [startup, runtime]
description: "Server parameter with annotations."
cpp_vartype: AtomicWord<int>
cpp_varname: gIntAnnotated
default: 7
redact: false
annotations:
query_knob:
wire_name: intAnnotatedWire
applicability: [queryShape]
fcv:
min: "9.0"
storageIntArrayOfObjects:
set_at: [startup, runtime]
description: "Parameter with array-of-objects annotations."
cpp_vartype: AtomicWord<int>
cpp_varname: gIntArrayOfObjects
default: 1
redact: false
annotations:
items:
- name: first
value: 1
- name: second
value: 2
storageIntNestedArrays:
set_at: [startup, runtime]
description: "Parameter with nested array annotations."
cpp_vartype: AtomicWord<int>
cpp_varname: gIntNestedArrays
default: 1
redact: false
annotations:
matrix:
- - 1
- 2
- - 3
- 4
storageIntEmptyArray:
set_at: [startup, runtime]
description: "Parameter with empty array annotation."
cpp_vartype: AtomicWord<int>
cpp_varname: gIntEmptyArray
default: 1
redact: false
annotations:
tags: []