Skip to content

MongoDB workload generation

bondi.mongodb.MongoGenerationConfig dataclass

All field, predicate and query-option settings in one place.

Attributes:

Name Type Description
min_predicates int

Minimum distinct filter fields (default 1).

max_predicates int

Maximum distinct filter fields (default 8).

include_fields list[str] | None

Optional exact normalized Mongo paths; takes precedence over exclusions.

exclude_fields list[str]

Exact paths excluded from filters, sorts and projections. Defaults to _id.

field_weights dict

Positive default weight plus ordered glob overrides; last match wins.

operator_config dict

Per-type operator/weight lists and conditional special-operator enabled/chance settings. See the generation guide for the full structure.

sort_probability float

Fraction of pattern slots requesting sort.

sort_on_filter_field_ratio float

Preference for selecting sort fields from the filter.

sort_two_field_ratio float

Probability of requesting two sort fields instead of one.

sort_ascending_ratio float

Probability of ascending sort direction.

skip_probability float

Fraction of pattern slots requesting skip.

limit_probability float

Fraction of pattern slots requesting limit.

skip_min int

Minimum generated skip (inclusive).

skip_max int

Maximum generated skip (inclusive).

limit_min int

Minimum generated limit (inclusive).

limit_max int

Maximum generated limit (inclusive).

skip_requires_limit bool

Force a limit for slots requesting skip.

projection_probability float

Fraction of pattern slots requesting inclusion projection.

projection_filter_overlap_ratio float

Desired fraction of projection fields from the filter.

projection_min_fields int

Minimum requested projection fields.

projection_max_fields int

Maximum requested projection fields.

projection_exclude_id_ratio float

Probability of adding _id: 0, unless _id is projected.

Probabilities describe proposals, not accepted-workload frequencies. Rejection can change the distribution. Values are sampled uniformly from distinct values, even with a full-retention Schema. Configs are mutable; generators snapshot and validate them at the start of each call. Do not mutate during generation.

Source code in src/bondi/mongodb/generation_config.py
 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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
@dataclass
class MongoGenerationConfig:
    """All field, predicate and query-option settings in one place.

    Attributes:
        min_predicates: Minimum distinct filter fields (default 1).
        max_predicates: Maximum distinct filter fields (default 8).
        include_fields: Optional exact normalized Mongo paths; takes precedence over exclusions.
        exclude_fields: Exact paths excluded from filters, sorts and projections. Defaults to _id.
        field_weights: Positive default weight plus ordered glob overrides; last match wins.
        operator_config: Per-type operator/weight lists and conditional special-operator
            enabled/chance settings. See the generation guide for the full structure.
        sort_probability: Fraction of pattern slots requesting sort.
        sort_on_filter_field_ratio: Preference for selecting sort fields from the filter.
        sort_two_field_ratio: Probability of requesting two sort fields instead of one.
        sort_ascending_ratio: Probability of ascending sort direction.
        skip_probability: Fraction of pattern slots requesting skip.
        limit_probability: Fraction of pattern slots requesting limit.
        skip_min: Minimum generated skip (inclusive).
        skip_max: Maximum generated skip (inclusive).
        limit_min: Minimum generated limit (inclusive).
        limit_max: Maximum generated limit (inclusive).
        skip_requires_limit: Force a limit for slots requesting skip.
        projection_probability: Fraction of pattern slots requesting inclusion projection.
        projection_filter_overlap_ratio: Desired fraction of projection fields from the filter.
        projection_min_fields: Minimum requested projection fields.
        projection_max_fields: Maximum requested projection fields.
        projection_exclude_id_ratio: Probability of adding _id: 0, unless _id is projected.

    Probabilities describe proposals, not accepted-workload frequencies. Rejection
    can change the distribution. Values are sampled uniformly from distinct values,
    even with a full-retention Schema. Configs are mutable; generators snapshot
    and validate them at the start of each call. Do not mutate during generation.
    """

    min_predicates: int = 1
    max_predicates: int = 8
    include_fields: list[str] | None = None
    exclude_fields: list[str] = field(default_factory=lambda: ["_id"])
    field_weights: dict = field(default_factory=lambda: {"default": 1.0, "overrides": {}})
    operator_config: dict = field(default_factory=_operators)
    sort_probability: float = 0.3
    sort_on_filter_field_ratio: float = 0.7
    sort_two_field_ratio: float = 0.3
    sort_ascending_ratio: float = 0.8
    skip_probability: float = 0.0
    limit_probability: float = 0.2
    skip_min: int = 0
    skip_max: int = 1000
    limit_min: int = 1
    limit_max: int = 100
    skip_requires_limit: bool = True
    projection_probability: float = 0.0
    projection_filter_overlap_ratio: float = 0.5
    projection_min_fields: int = 1
    projection_max_fields: int = 5
    projection_exclude_id_ratio: float = 0.5

    def __post_init__(self):
        self.validate()

    def validate(self) -> None:
        """Validate all settings, including nested operator and weight mappings."""
        for low, high, minimum in (
            ("min_predicates", "max_predicates", 1),
            ("skip_min", "skip_max", 0),
            ("limit_min", "limit_max", 1),
            ("projection_min_fields", "projection_max_fields", 1),
        ):
            _positive(low, getattr(self, low), minimum)
            _positive(high, getattr(self, high), minimum)
            if getattr(self, low) > getattr(self, high):
                raise ValueError(f"{low} must not exceed {high}")
        for name in self.__dataclass_fields__:
            if name.endswith(("_probability", "_ratio")):
                _fraction(name, getattr(self, name))
        if type(self.skip_requires_limit) is not bool:
            raise TypeError("skip_requires_limit must be bool")
        for name in ("include_fields", "exclude_fields"):
            value = getattr(self, name)
            if name == "include_fields" and value is None:
                continue
            if not isinstance(value, list) or any(not isinstance(v, str) or not v for v in value):
                raise TypeError(f"{name} must be a list of nonempty paths")
        weights = self.field_weights
        if not isinstance(weights, dict) or set(weights) - {"default", "overrides"}:
            raise ValueError("field_weights accepts default and overrides only")
        overrides = weights.get("overrides", {})
        if not isinstance(overrides, dict) or any(not isinstance(k, str) for k in overrides):
            raise TypeError("field weight overrides must map glob strings to weights")
        for value in (weights.get("default", 1), *overrides.values()):
            _weight(value, positive=True)
        config = self.operator_config
        if not isinstance(config, dict) or set(config) - {"types", "operators"}:
            raise ValueError("operator_config accepts types and operators only")
        types = config.get("types")
        if not isinstance(types, dict) or not types:
            raise ValueError("operator_config.types must be a nonempty mapping")
        for name, settings in types.items():
            if name not in _operators()["types"]:
                raise ValueError(f"Unsupported generation type: {name}")
            if not isinstance(settings, dict) or set(settings) != {"operators", "weights"}:
                raise ValueError("Each type requires operators and weights")
            ops, ws = settings["operators"], settings["weights"]
            if (
                not isinstance(ops, list)
                or not isinstance(ws, list)
                or not ops
                or len(ops) != len(ws)
                or any(not isinstance(op, str) for op in ops)
                or len(set(ops)) != len(ops)
                or set(ops) - {"eq", "ne", "lt", "lte", "gt", "gte", "in", "nin"}
            ):
                raise ValueError("Invalid operator names or mismatched weights")
            for weight in ws:
                _weight(weight)
            if not any(ws):
                raise ValueError("At least one operator weight must be positive per type")
        specials = config.get("operators", {})
        if not isinstance(specials, dict) or set(specials) - {"$exists", "$type", "$size"}:
            raise ValueError("Unsupported special operator")
        for settings in specials.values():
            if not isinstance(settings, dict) or set(settings) - {"enabled", "chance"}:
                raise ValueError("Special operators accept enabled and chance only")
            if type(settings.get("enabled", False)) is not bool:
                raise TypeError("enabled must be bool")
            _fraction("chance", settings.get("chance", 0))

validate()

Validate all settings, including nested operator and weight mappings.

Source code in src/bondi/mongodb/generation_config.py
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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
def validate(self) -> None:
    """Validate all settings, including nested operator and weight mappings."""
    for low, high, minimum in (
        ("min_predicates", "max_predicates", 1),
        ("skip_min", "skip_max", 0),
        ("limit_min", "limit_max", 1),
        ("projection_min_fields", "projection_max_fields", 1),
    ):
        _positive(low, getattr(self, low), minimum)
        _positive(high, getattr(self, high), minimum)
        if getattr(self, low) > getattr(self, high):
            raise ValueError(f"{low} must not exceed {high}")
    for name in self.__dataclass_fields__:
        if name.endswith(("_probability", "_ratio")):
            _fraction(name, getattr(self, name))
    if type(self.skip_requires_limit) is not bool:
        raise TypeError("skip_requires_limit must be bool")
    for name in ("include_fields", "exclude_fields"):
        value = getattr(self, name)
        if name == "include_fields" and value is None:
            continue
        if not isinstance(value, list) or any(not isinstance(v, str) or not v for v in value):
            raise TypeError(f"{name} must be a list of nonempty paths")
    weights = self.field_weights
    if not isinstance(weights, dict) or set(weights) - {"default", "overrides"}:
        raise ValueError("field_weights accepts default and overrides only")
    overrides = weights.get("overrides", {})
    if not isinstance(overrides, dict) or any(not isinstance(k, str) for k in overrides):
        raise TypeError("field weight overrides must map glob strings to weights")
    for value in (weights.get("default", 1), *overrides.values()):
        _weight(value, positive=True)
    config = self.operator_config
    if not isinstance(config, dict) or set(config) - {"types", "operators"}:
        raise ValueError("operator_config accepts types and operators only")
    types = config.get("types")
    if not isinstance(types, dict) or not types:
        raise ValueError("operator_config.types must be a nonempty mapping")
    for name, settings in types.items():
        if name not in _operators()["types"]:
            raise ValueError(f"Unsupported generation type: {name}")
        if not isinstance(settings, dict) or set(settings) != {"operators", "weights"}:
            raise ValueError("Each type requires operators and weights")
        ops, ws = settings["operators"], settings["weights"]
        if (
            not isinstance(ops, list)
            or not isinstance(ws, list)
            or not ops
            or len(ops) != len(ws)
            or any(not isinstance(op, str) for op in ops)
            or len(set(ops)) != len(ops)
            or set(ops) - {"eq", "ne", "lt", "lte", "gt", "gte", "in", "nin"}
        ):
            raise ValueError("Invalid operator names or mismatched weights")
        for weight in ws:
            _weight(weight)
        if not any(ws):
            raise ValueError("At least one operator weight must be positive per type")
    specials = config.get("operators", {})
    if not isinstance(specials, dict) or set(specials) - {"$exists", "$type", "$size"}:
        raise ValueError("Unsupported special operator")
    for settings in specials.values():
        if not isinstance(settings, dict) or set(settings) - {"enabled", "chance"}:
            raise ValueError("Special operators accept enabled and chance only")
        if type(settings.get("enabled", False)) is not bool:
            raise TypeError("enabled must be bool")
        _fraction("chance", settings.get("chance", 0))

bondi.mongodb.MongoPredicateTemplate dataclass

One field/operator slot exposed through MongoQueryPattern.predicates.

Attributes:

Name Type Description
field str

Normalized MongoDB dot path.

operator str

Operator name without '$'.

value_type str | None

BSON-compatible Python type label, or None for fixed literals.

arity int

Number of operands; in uses 2–10, nin uses 1–10, others use one.

fixed_value bool | str | None

Boolean for exists or BSON type name for type; otherwise None.

Source code in src/bondi/mongodb/pattern.py
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
@dataclass(frozen=True)
class MongoPredicateTemplate:
    """One field/operator slot exposed through MongoQueryPattern.predicates.

    Attributes:
        field: Normalized MongoDB dot path.
        operator: Operator name without '$'.
        value_type: BSON-compatible Python type label, or None for fixed literals.
        arity: Number of operands; in uses 2–10, nin uses 1–10, others use one.
        fixed_value: Boolean for exists or BSON type name for type; otherwise None.
    """

    field: str
    operator: str
    value_type: str | None = None
    arity: int = 1
    fixed_value: bool | str | None = None

    def __post_init__(self):
        _field_path(self.field)
        if self.operator not in {
            "eq",
            "ne",
            "lt",
            "lte",
            "gt",
            "gte",
            "in",
            "nin",
            "exists",
            "type",
            "size",
        }:
            raise ValueError("Unsupported pattern operator")
        if type(self.arity) is not int or self.arity < 1:
            raise ValueError("arity must be a positive integer")
        if self.operator in {"in", "nin"}:
            if not (2 if self.operator == "in" else 1) <= self.arity <= 10:
                raise ValueError("Unsupported membership arity")
        elif self.arity != 1:
            raise ValueError("Scalar operators have arity=1")
        if self.operator in {"exists", "type"}:
            if self.value_type is not None:
                raise ValueError("Fixed predicates have no sampled type")
            if self.operator == "exists" and type(self.fixed_value) is not bool:
                raise ValueError("exists requires a fixed bool")
            if self.operator == "type" and self.fixed_value not in _BSON_TYPES:
                raise ValueError("type requires a supported BSON type name")
        elif self.value_type not in _TYPES or self.fixed_value is not None:
            raise ValueError("Sampled predicates require a supported type and no fixed value")
        if self.operator == "size" and self.value_type != "int":
            raise ValueError("size requires int operands")

bondi.mongodb.MongoQueryPattern dataclass

Complete immutable flat-find shape, independent of operand values.

Field/operator/type/membership length and all query options identify a pattern. Filter and projection order are canonicalized; sort order is significant. Patterns may be inspected or extracted from saved queries with from_query(). Logical expressions, object/array equality and multiple operators on one field are outside this initial subset. They remain representable as MongoFindQuery.

Source code in src/bondi/mongodb/pattern.py
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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
@dataclass(frozen=True)
class MongoQueryPattern:
    """Complete immutable flat-find shape, independent of operand values.

    Field/operator/type/membership length and all query options identify a pattern.
    Filter and projection order are canonicalized; sort order is significant.
    Patterns may be inspected or extracted from saved queries with from_query().
    Logical expressions, object/array equality and multiple operators on one field
    are outside this initial subset. They remain representable as MongoFindQuery.
    """

    target: MongoCollectionRef
    predicates: tuple[MongoPredicateTemplate, ...]
    sort: tuple[tuple[str, int], ...] = ()
    projection: tuple[tuple[str, int], ...] | None = None
    skip: int = 0
    limit: int | None = None

    def __post_init__(self):
        if (
            not isinstance(self.predicates, tuple)
            or not self.predicates
            or not all(isinstance(p, MongoPredicateTemplate) for p in self.predicates)
        ):
            raise ValueError("Patterns require a nonempty tuple of MongoPredicateTemplate")
        fields = [p.field for p in self.predicates]
        if len(fields) != len(set(fields)):
            raise ValueError("One predicate per field is supported")
        object.__setattr__(
            self, "predicates", tuple(sorted(self.predicates, key=lambda p: p.field))
        )
        MongoFindQuery(self.target, {}, sort=self.sort, skip=self.skip, limit=self.limit)
        if len({p[0] for p in self.sort}) != len(self.sort):
            raise ValueError("Duplicate sort field")
        for name, _ in self.sort:
            _field_path(name)
        if self.projection is not None:
            if not isinstance(self.projection, tuple) or any(
                not isinstance(p, tuple)
                or len(p) != 2
                or type(p[1]) is not int
                or p[1] not in (0, 1)
                for p in self.projection
            ):
                raise ValueError("Projection patterns require tuple pairs with 0/1 values")
            names = [p[0] for p in self.projection]
            for name in names:
                _field_path(name)
            if len(names) != len(set(names)):
                raise ValueError("Duplicate projection field")
            non_id = {v for k, v in self.projection if k != "_id"}
            if len(non_id) > 1:
                raise ValueError("Cannot mix inclusion and exclusion projection")
            object.__setattr__(self, "projection", tuple(sorted(self.projection)))

    @classmethod
    def from_query(cls, query: MongoFindQuery) -> Self:
        """Extract a shape from a supported native find query, including after JSONL loading."""
        if not isinstance(query, MongoFindQuery):
            raise TypeError("Expected MongoFindQuery")
        query.validate()
        predicates = []
        for name, expression in query.filter.items():
            op, operand = "eq", expression
            if isinstance(expression, dict):
                if len(expression) != 1:
                    raise ValueError("Only one operator per field is supported")
                key, operand = next(iter(expression.items()))
                if not isinstance(key, str) or not key.startswith("$"):
                    raise ValueError("Literal object equality is not a pattern operand")
                op = key[1:]
            if op in ("exists", "type"):
                predicate = MongoPredicateTemplate(name, op, fixed_value=operand)
            else:
                values = operand if op in ("in", "nin") else [operand]
                if not isinstance(values, list) or not values:
                    raise ValueError("Membership operands require a nonempty list")
                kind = _value_type(values[0])
                if any(_value_type(v) != kind for v in values) or len(set(values)) != len(values):
                    raise ValueError("Membership operands must be distinct and of one type")
                if op == "size" and (type(operand) is not int or operand < 0):
                    raise ValueError("size requires a nonnegative integer")
                predicate = MongoPredicateTemplate(name, op, kind, len(values))
            predicates.append(predicate)
        return cls(
            query.target,
            tuple(predicates),
            query.sort,
            None if query.projection is None else tuple(query.projection.items()),
            query.skip,
            query.limit,
        )

    def matches(self, query: MongoFindQuery) -> bool:
        """Whether the query has this shape; unsupported shapes return False."""
        try:
            return self == self.from_query(query)
        except (ValueError, TypeError):
            return False

from_query(query) classmethod

Extract a shape from a supported native find query, including after JSONL loading.

Source code in src/bondi/mongodb/pattern.py
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
@classmethod
def from_query(cls, query: MongoFindQuery) -> Self:
    """Extract a shape from a supported native find query, including after JSONL loading."""
    if not isinstance(query, MongoFindQuery):
        raise TypeError("Expected MongoFindQuery")
    query.validate()
    predicates = []
    for name, expression in query.filter.items():
        op, operand = "eq", expression
        if isinstance(expression, dict):
            if len(expression) != 1:
                raise ValueError("Only one operator per field is supported")
            key, operand = next(iter(expression.items()))
            if not isinstance(key, str) or not key.startswith("$"):
                raise ValueError("Literal object equality is not a pattern operand")
            op = key[1:]
        if op in ("exists", "type"):
            predicate = MongoPredicateTemplate(name, op, fixed_value=operand)
        else:
            values = operand if op in ("in", "nin") else [operand]
            if not isinstance(values, list) or not values:
                raise ValueError("Membership operands require a nonempty list")
            kind = _value_type(values[0])
            if any(_value_type(v) != kind for v in values) or len(set(values)) != len(values):
                raise ValueError("Membership operands must be distinct and of one type")
            if op == "size" and (type(operand) is not int or operand < 0):
                raise ValueError("size requires a nonnegative integer")
            predicate = MongoPredicateTemplate(name, op, kind, len(values))
        predicates.append(predicate)
    return cls(
        query.target,
        tuple(predicates),
        query.sort,
        None if query.projection is None else tuple(query.projection.items()),
        query.skip,
        query.limit,
    )

matches(query)

Whether the query has this shape; unsupported shapes return False.

Source code in src/bondi/mongodb/pattern.py
194
195
196
197
198
199
def matches(self, query: MongoFindQuery) -> bool:
    """Whether the query has this shape; unsupported shapes return False."""
    try:
        return self == self.from_query(query)
    except (ValueError, TypeError):
        return False

bondi.mongodb.MongoWorkloadGenerator

Generate flat-find patterns for one collection, with optional selectivity checks.

Parameters:

Name Type Description Default
target MongoCollectionRef

Collection targeted by every generated query.

required
schema Schema

Observed Schema retaining distinct or full values. Frequencies are ignored.

required
estimator CardinalityEstimator[MongoFindQuery] | None

Optional prepared synchronous CardinalityEstimator[MongoFindQuery]. Required only when selectivity bounds are active. Never opened or closed here.

None
config MongoGenerationConfig | None

One MongoGenerationConfig; omitted settings use documented defaults.

None

No database connection or event loop is created. Schema and config are retained by reference and read at each generate() call; do not mutate inputs during a call. Calls on one generator must not overlap. Patterns support flat filters only; logical expressions, elemMatch and aggregation generation are deferred.

Source code in src/bondi/mongodb/generation.py
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
class MongoWorkloadGenerator:
    """Generate flat-find patterns for one collection, with optional selectivity checks.

    Args:
        target: Collection targeted by every generated query.
        schema: Observed Schema retaining distinct or full values. Frequencies are ignored.
        estimator: Optional prepared synchronous CardinalityEstimator[MongoFindQuery].
            Required only when selectivity bounds are active. Never opened or closed here.
        config: One MongoGenerationConfig; omitted settings use documented defaults.

    No database connection or event loop is created. Schema and config are retained
    by reference and read at each generate() call; do not mutate inputs during a
    call. Calls on one generator must not overlap. Patterns support flat filters
    only; logical expressions, elemMatch and aggregation generation are deferred.
    """

    def __init__(
        self,
        *,
        target: MongoCollectionRef,
        schema: Schema,
        estimator: CardinalityEstimator[MongoFindQuery] | None = None,
        config: MongoGenerationConfig | None = None,
    ):
        if not isinstance(target, MongoCollectionRef) or not isinstance(schema, Schema):
            raise TypeError("Expected MongoCollectionRef target and Schema schema")
        if config is not None and not isinstance(config, MongoGenerationConfig):
            raise TypeError("config must be MongoGenerationConfig")
        if estimator is not None and (
            not callable(getattr(estimator, "estimate", None))
            or inspect.iscoroutinefunction(estimator.estimate)
        ):
            raise TypeError("estimator must expose a synchronous estimate(query) method")
        if schema.value_mode == "none":
            raise ValueError("Generation requires Schema values='distinct' or values='full'")
        self.target, self.schema, self.estimator = target, schema, estimator
        self.config = config if config is not None else MongoGenerationConfig()
        self.config.validate()
        self._last_stats = {}

    @property
    def last_stats(self) -> Mapping[str, Any]:
        """Read-only snapshot of the latest generation call, including interrupted/failed calls."""
        return _snapshot(self._last_stats)

    @overload
    def generate(
        self,
        *,
        num_patterns: int,
        instances_per_pattern: int = 1,
        return_patterns: Literal[False] = False,
        seed: int | None = None,
        min_selectivity: float = 0.0,
        max_selectivity: float = 1.0,
        validate_pattern: Callable | None = None,
        max_candidate_attempts: int = 10_000,
        max_instance_attempts: int = 1000,
        timeout_seconds: float = 60,
    ) -> Workload: ...

    @overload
    def generate(
        self,
        *,
        num_patterns: int,
        instances_per_pattern: int = 1,
        return_patterns: Literal[True],
        seed: int | None = None,
        min_selectivity: float = 0.0,
        max_selectivity: float = 1.0,
        validate_pattern: Callable | None = None,
        max_candidate_attempts: int = 10_000,
        max_instance_attempts: int = 1000,
        timeout_seconds: float = 60,
    ) -> tuple[Workload, list[MongoQueryPattern]]: ...

    def generate(
        self,
        *,
        num_patterns: int,
        instances_per_pattern: int = 1,
        return_patterns: bool = False,
        seed: int | None = None,
        min_selectivity: float = 0.0,
        max_selectivity: float = 1.0,
        validate_pattern: Callable | None = None,
        max_candidate_attempts: int = 10_000,
        max_instance_attempts: int = 1000,
        timeout_seconds: float = 60,
    ) -> Workload | tuple[Workload, list[MongoQueryPattern]]:
        """Return exactly N patterns of K instances, or raise WorkloadGenerationError.

        Args:
            num_patterns: Positive number of distinct complete patterns.
            instances_per_pattern: Positive group size; one gives unique-pattern queries.
            return_patterns: Also return a list indexed by each entry's pattern_id.
            seed: Local RNG seed. Reproducibility requires fixed inputs and estimator responses.
            min_selectivity: Inclusive lower estimated matching fraction, before skip/limit.
            max_selectivity: Inclusive upper bound; [0, 1] disables estimation.
            validate_pattern: Callback(candidate, tuple_of_completed_patterns) -> bool,
                before estimation. Exceptions propagate unchanged.
            max_candidate_attempts: Global budget for discovering patterns.
            max_instance_attempts: Attempts per additional instance, not per workload.
            timeout_seconds: Monotonic deadline, checked between operations including
                estimator/callback calls. Does not interrupt a running external call.

        Each group fixes fields/operators/types/membership arity and exact options;
        exists/type literals are fixed. Duplicate full instances are allowed.
        Output is shuffled. No partial workload is returned; errors expose stats.
        Estimator errors propagate. The estimator owns its operation timeout.
        """
        self._last_stats = {}
        for name, value in [
            ("num_patterns", num_patterns),
            ("instances_per_pattern", instances_per_pattern),
            ("max_candidate_attempts", max_candidate_attempts),
            ("max_instance_attempts", max_instance_attempts),
        ]:
            _positive(name, value)
        if type(return_patterns) is not bool:
            raise TypeError("return_patterns must be bool")
        if seed is not None and type(seed) is not int:
            raise TypeError("seed must be int or None")
        _fraction("min_selectivity", min_selectivity)
        _fraction("max_selectivity", max_selectivity)
        if min_selectivity > max_selectivity:
            raise ValueError("min_selectivity must not exceed max_selectivity")
        if (
            type(timeout_seconds) not in (int, float)
            or not math.isfinite(timeout_seconds)
            or timeout_seconds <= 0
        ):
            raise ValueError("timeout_seconds must be positive and finite")
        if validate_pattern is not None and (
            not callable(validate_pattern) or inspect.iscoroutinefunction(validate_pattern)
        ):
            raise TypeError("validate_pattern must be a synchronous callable")
        constrained = min_selectivity > 0 or max_selectivity < 1
        if constrained and self.estimator is None:
            raise ValueError("Selectivity constraints require a prepared estimator")
        config = deepcopy(self.config)
        config.validate()
        rng, started = random.Random(seed), time.monotonic()
        stats = dict(
            candidate_attempts=0,
            value_attempts=0,
            duplicate_candidates=0,
            structural_rejections=0,
            validation_rejections=0,
            selectivity_rejections=0,
            discarded_groups=0,
            estimator_calls=0,
            completed_patterns=0,
            elapsed_seconds=0.0,
        )

        def check():
            stats["elapsed_seconds"] = time.monotonic() - started
            if stats["elapsed_seconds"] >= timeout_seconds:
                raise WorkloadGenerationError("Generation deadline exceeded", stats)

        def accepted(query):
            check()
            if not constrained:
                return True
            stats["estimator_calls"] += 1
            # Isolate user-supplied estimators from the candidate retained for output.
            result = self.estimator.estimate(deepcopy(query))
            if inspect.iscoroutine(result):
                result.close()
                raise TypeError("Use a synchronous estimator")
            if not isinstance(result, CardinalityEstimate):
                raise TypeError("estimate() must return CardinalityEstimate")
            if (
                type(result.population_size) is not int
                or result.population_size < 0
                or type(result.cardinality) not in (int, float)
                or not math.isfinite(result.cardinality)
                or not 0 <= result.cardinality <= result.population_size
            ):
                raise ValueError("Estimator returned invalid cardinality/population")
            check()
            ok = min_selectivity <= result.selectivity <= max_selectivity
            if not ok:
                stats["selectivity_rejections"] += 1
            return ok

        try:
            fields = _fields(self.schema, config, check)
            if len(fields) < config.min_predicates:
                raise WorkloadGenerationError("Insufficient eligible fields", stats)
            patterns, entries, seen = [], [], set()
            slot = None
            while (
                len(patterns) < num_patterns
                and stats["candidate_attempts"] < max_candidate_attempts
            ):
                check()
                if slot is None:
                    count = rng.randint(
                        config.min_predicates, min(config.max_predicates, len(fields))
                    )
                    presence = {
                        name: rng.random() < getattr(config, name + "_probability")
                        for name in ("sort", "skip", "limit", "projection")
                    }
                    if presence["skip"] and config.skip_requires_limit:
                        presence["limit"] = True
                    slot = count, presence
                    stats["pending_pattern_target"] = dict(
                        pattern_id=len(patterns), num_predicates=count, **presence
                    )
                count, presence = slot
                stats["candidate_attempts"] += 1
                names = _sample_fields(rng, fields, count, config)
                predicates, pools = {}, {}
                for name in names:
                    predicate = _predicate(fields[name], config, rng)
                    if predicate is not None:
                        predicates[name], pools[name] = predicate
                if len(predicates) != count:
                    stats["structural_rejections"] += 1
                    continue
                query = MongoFindQuery(
                    self.target, predicates, **_options(names, list(fields), config, presence, rng)
                )
                pattern = MongoQueryPattern.from_query(query)
                if pattern in seen:
                    stats["duplicate_candidates"] += 1
                    continue
                if validate_pattern is not None:
                    valid = validate_pattern(pattern, tuple(patterns))
                    if inspect.iscoroutine(valid):
                        valid.close()
                    if type(valid) is not bool:
                        raise TypeError("validate_pattern must return bool")
                    check()
                    if not valid:
                        stats["validation_rejections"] += 1
                        continue
                if not accepted(query):
                    continue
                group = [query]
                while len(group) < instances_per_pattern:
                    for _ in range(max_instance_attempts):
                        check()
                        stats["value_attempts"] += 1
                        query = _instantiate(pattern, pools, rng)
                        if accepted(query):
                            group.append(query)
                            break
                    else:
                        break
                if len(group) != instances_per_pattern:
                    stats["discarded_groups"] += 1
                    continue
                entries.extend(WorkloadEntry(q, len(patterns)) for q in group)
                patterns.append(pattern)
                seen.add(pattern)
                stats["completed_patterns"] = len(patterns)
                stats.pop("pending_pattern_target", None)
                slot = None
            check()
            if len(patterns) != num_patterns:
                raise WorkloadGenerationError("Candidate attempt budget exhausted", stats)
            rng.shuffle(entries)
            workload = Workload(entries)
            check()
            return (workload, patterns) if return_patterns else workload
        finally:
            stats["elapsed_seconds"] = time.monotonic() - started
            self._last_stats = deepcopy(stats)

last_stats property

Read-only snapshot of the latest generation call, including interrupted/failed calls.

generate(*, num_patterns, instances_per_pattern=1, return_patterns=False, seed=None, min_selectivity=0.0, max_selectivity=1.0, validate_pattern=None, max_candidate_attempts=10000, max_instance_attempts=1000, timeout_seconds=60)

generate(
    *,
    num_patterns: int,
    instances_per_pattern: int = 1,
    return_patterns: Literal[False] = False,
    seed: int | None = None,
    min_selectivity: float = 0.0,
    max_selectivity: float = 1.0,
    validate_pattern: Callable | None = None,
    max_candidate_attempts: int = 10000,
    max_instance_attempts: int = 1000,
    timeout_seconds: float = 60,
) -> Workload
generate(
    *,
    num_patterns: int,
    instances_per_pattern: int = 1,
    return_patterns: Literal[True],
    seed: int | None = None,
    min_selectivity: float = 0.0,
    max_selectivity: float = 1.0,
    validate_pattern: Callable | None = None,
    max_candidate_attempts: int = 10000,
    max_instance_attempts: int = 1000,
    timeout_seconds: float = 60,
) -> tuple[Workload, list[MongoQueryPattern]]

Return exactly N patterns of K instances, or raise WorkloadGenerationError.

Parameters:

Name Type Description Default
num_patterns int

Positive number of distinct complete patterns.

required
instances_per_pattern int

Positive group size; one gives unique-pattern queries.

1
return_patterns bool

Also return a list indexed by each entry's pattern_id.

False
seed int | None

Local RNG seed. Reproducibility requires fixed inputs and estimator responses.

None
min_selectivity float

Inclusive lower estimated matching fraction, before skip/limit.

0.0
max_selectivity float

Inclusive upper bound; [0, 1] disables estimation.

1.0
validate_pattern Callable | None

Callback(candidate, tuple_of_completed_patterns) -> bool, before estimation. Exceptions propagate unchanged.

None
max_candidate_attempts int

Global budget for discovering patterns.

10000
max_instance_attempts int

Attempts per additional instance, not per workload.

1000
timeout_seconds float

Monotonic deadline, checked between operations including estimator/callback calls. Does not interrupt a running external call.

60

Each group fixes fields/operators/types/membership arity and exact options; exists/type literals are fixed. Duplicate full instances are allowed. Output is shuffled. No partial workload is returned; errors expose stats. Estimator errors propagate. The estimator owns its operation timeout.

Source code in src/bondi/mongodb/generation.py
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
def generate(
    self,
    *,
    num_patterns: int,
    instances_per_pattern: int = 1,
    return_patterns: bool = False,
    seed: int | None = None,
    min_selectivity: float = 0.0,
    max_selectivity: float = 1.0,
    validate_pattern: Callable | None = None,
    max_candidate_attempts: int = 10_000,
    max_instance_attempts: int = 1000,
    timeout_seconds: float = 60,
) -> Workload | tuple[Workload, list[MongoQueryPattern]]:
    """Return exactly N patterns of K instances, or raise WorkloadGenerationError.

    Args:
        num_patterns: Positive number of distinct complete patterns.
        instances_per_pattern: Positive group size; one gives unique-pattern queries.
        return_patterns: Also return a list indexed by each entry's pattern_id.
        seed: Local RNG seed. Reproducibility requires fixed inputs and estimator responses.
        min_selectivity: Inclusive lower estimated matching fraction, before skip/limit.
        max_selectivity: Inclusive upper bound; [0, 1] disables estimation.
        validate_pattern: Callback(candidate, tuple_of_completed_patterns) -> bool,
            before estimation. Exceptions propagate unchanged.
        max_candidate_attempts: Global budget for discovering patterns.
        max_instance_attempts: Attempts per additional instance, not per workload.
        timeout_seconds: Monotonic deadline, checked between operations including
            estimator/callback calls. Does not interrupt a running external call.

    Each group fixes fields/operators/types/membership arity and exact options;
    exists/type literals are fixed. Duplicate full instances are allowed.
    Output is shuffled. No partial workload is returned; errors expose stats.
    Estimator errors propagate. The estimator owns its operation timeout.
    """
    self._last_stats = {}
    for name, value in [
        ("num_patterns", num_patterns),
        ("instances_per_pattern", instances_per_pattern),
        ("max_candidate_attempts", max_candidate_attempts),
        ("max_instance_attempts", max_instance_attempts),
    ]:
        _positive(name, value)
    if type(return_patterns) is not bool:
        raise TypeError("return_patterns must be bool")
    if seed is not None and type(seed) is not int:
        raise TypeError("seed must be int or None")
    _fraction("min_selectivity", min_selectivity)
    _fraction("max_selectivity", max_selectivity)
    if min_selectivity > max_selectivity:
        raise ValueError("min_selectivity must not exceed max_selectivity")
    if (
        type(timeout_seconds) not in (int, float)
        or not math.isfinite(timeout_seconds)
        or timeout_seconds <= 0
    ):
        raise ValueError("timeout_seconds must be positive and finite")
    if validate_pattern is not None and (
        not callable(validate_pattern) or inspect.iscoroutinefunction(validate_pattern)
    ):
        raise TypeError("validate_pattern must be a synchronous callable")
    constrained = min_selectivity > 0 or max_selectivity < 1
    if constrained and self.estimator is None:
        raise ValueError("Selectivity constraints require a prepared estimator")
    config = deepcopy(self.config)
    config.validate()
    rng, started = random.Random(seed), time.monotonic()
    stats = dict(
        candidate_attempts=0,
        value_attempts=0,
        duplicate_candidates=0,
        structural_rejections=0,
        validation_rejections=0,
        selectivity_rejections=0,
        discarded_groups=0,
        estimator_calls=0,
        completed_patterns=0,
        elapsed_seconds=0.0,
    )

    def check():
        stats["elapsed_seconds"] = time.monotonic() - started
        if stats["elapsed_seconds"] >= timeout_seconds:
            raise WorkloadGenerationError("Generation deadline exceeded", stats)

    def accepted(query):
        check()
        if not constrained:
            return True
        stats["estimator_calls"] += 1
        # Isolate user-supplied estimators from the candidate retained for output.
        result = self.estimator.estimate(deepcopy(query))
        if inspect.iscoroutine(result):
            result.close()
            raise TypeError("Use a synchronous estimator")
        if not isinstance(result, CardinalityEstimate):
            raise TypeError("estimate() must return CardinalityEstimate")
        if (
            type(result.population_size) is not int
            or result.population_size < 0
            or type(result.cardinality) not in (int, float)
            or not math.isfinite(result.cardinality)
            or not 0 <= result.cardinality <= result.population_size
        ):
            raise ValueError("Estimator returned invalid cardinality/population")
        check()
        ok = min_selectivity <= result.selectivity <= max_selectivity
        if not ok:
            stats["selectivity_rejections"] += 1
        return ok

    try:
        fields = _fields(self.schema, config, check)
        if len(fields) < config.min_predicates:
            raise WorkloadGenerationError("Insufficient eligible fields", stats)
        patterns, entries, seen = [], [], set()
        slot = None
        while (
            len(patterns) < num_patterns
            and stats["candidate_attempts"] < max_candidate_attempts
        ):
            check()
            if slot is None:
                count = rng.randint(
                    config.min_predicates, min(config.max_predicates, len(fields))
                )
                presence = {
                    name: rng.random() < getattr(config, name + "_probability")
                    for name in ("sort", "skip", "limit", "projection")
                }
                if presence["skip"] and config.skip_requires_limit:
                    presence["limit"] = True
                slot = count, presence
                stats["pending_pattern_target"] = dict(
                    pattern_id=len(patterns), num_predicates=count, **presence
                )
            count, presence = slot
            stats["candidate_attempts"] += 1
            names = _sample_fields(rng, fields, count, config)
            predicates, pools = {}, {}
            for name in names:
                predicate = _predicate(fields[name], config, rng)
                if predicate is not None:
                    predicates[name], pools[name] = predicate
            if len(predicates) != count:
                stats["structural_rejections"] += 1
                continue
            query = MongoFindQuery(
                self.target, predicates, **_options(names, list(fields), config, presence, rng)
            )
            pattern = MongoQueryPattern.from_query(query)
            if pattern in seen:
                stats["duplicate_candidates"] += 1
                continue
            if validate_pattern is not None:
                valid = validate_pattern(pattern, tuple(patterns))
                if inspect.iscoroutine(valid):
                    valid.close()
                if type(valid) is not bool:
                    raise TypeError("validate_pattern must return bool")
                check()
                if not valid:
                    stats["validation_rejections"] += 1
                    continue
            if not accepted(query):
                continue
            group = [query]
            while len(group) < instances_per_pattern:
                for _ in range(max_instance_attempts):
                    check()
                    stats["value_attempts"] += 1
                    query = _instantiate(pattern, pools, rng)
                    if accepted(query):
                        group.append(query)
                        break
                else:
                    break
            if len(group) != instances_per_pattern:
                stats["discarded_groups"] += 1
                continue
            entries.extend(WorkloadEntry(q, len(patterns)) for q in group)
            patterns.append(pattern)
            seen.add(pattern)
            stats["completed_patterns"] = len(patterns)
            stats.pop("pending_pattern_target", None)
            slot = None
        check()
        if len(patterns) != num_patterns:
            raise WorkloadGenerationError("Candidate attempt budget exhausted", stats)
        rng.shuffle(entries)
        workload = Workload(entries)
        check()
        return (workload, patterns) if return_patterns else workload
    finally:
        stats["elapsed_seconds"] = time.monotonic() - started
        self._last_stats = deepcopy(stats)

bondi.mongodb.WorkloadGenerationError

Bases: RuntimeError

Generation exhausted a budget without a complete workload; stats describe progress.

Source code in src/bondi/mongodb/generation.py
44
45
46
47
48
49
class WorkloadGenerationError(RuntimeError):
    """Generation exhausted a budget without a complete workload; stats describe progress."""

    def __init__(self, message: str, stats: dict):
        self.stats = _snapshot(stats)
        super().__init__(message)

Paths and supported values

Generation supports strings, booleans, BSON-range integers, finite floats, datetimes, ObjectIds and null. Large Python integers normalize to Int64; dates normalize to BSON UTC millisecond precision. Retention of other Python types by Schema does not imply support for generating MongoDB predicates from them; exclude those fields or explicitly transform source data first.

Array traversal markers are removed when generating MongoDB dot paths. Values remain in their chosen typed source pool throughout a pattern. Mixed scalar and array sources can map to the same Mongo path, but only one predicate is selected per path. Missingness used for exists proposals comes from schema observations; MongoDB/your estimator evaluates the actual semantics.

Literal dotted keys, empty/dollar-prefixed path segments, positional numeric segments and literal [] segments are rejected rather than reinterpreted. Use schema display paths in exclusions for literal dotted keys, e.g. r"literal\.dot". Container-only paths without primitive value pools are not candidates. Generated queries still obey native MongoDB execution restrictions; for example, sorting on parallel arrays is not generally valid. Use field inclusion to restrict an experiment to suitable sortable fields.

Custom constraints, budgets and diagnostics

def accept_pattern(candidate, accepted_patterns):
    fields = {predicate.field for predicate in candidate.predicates}
    return not {"start_station", "end_station"} <= fields


workload = generator.generate(
    num_patterns=5,
    instances_per_pattern=20,
    validate_pattern=accept_pattern,
    max_candidate_attempts=10_000,
    max_instance_attempts=1_000,
    timeout_seconds=60,
    seed=42,
)
print(dict(generator.last_stats))

The callback runs before estimation, receives only fully completed prior patterns as a tuple, and must return bool. The candidate budget is global; the instance budget applies to each additional instance. Failed groups are discarded completely. Deadline checks occur between operations; they cannot interrupt a running estimator or callback. Configure the estimator's executor timeout separately.

On budget exhaustion, WorkloadGenerationError.stats reports progress. There is no partial workload result. generator.last_stats is a read-only snapshot with attempts, rejection counts, estimator calls, discarded groups, completed patterns, elapsed seconds and, when unfinished, the pending slot targets. Exceptions and interruptions also update diagnostics after generation starts. Input-validation failures clear the previous snapshot. Calls on one generator must not overlap.

Reproducibility and persistence

Fixed schema contents, config, seed and estimator responses give reproducible queries without changing Python's global random state. Value pools are canonically ordered, independent of hash seed and value frequencies. Generator seeds do not control database sampling or elapsed timings. Do not mutate inputs during a call.

from bondi.workload import Workload
from bondi.mongodb import MongoQueryPattern

workload.save("workload.jsonl")
loaded = Workload.load("workload.jsonl")
pattern = MongoQueryPattern.from_query(loaded[0].query)

Existing JSONL preserves queries and integer pattern IDs. Patterns can be extracted from saved queries; generation statistics are separate and are not added to the workload file. Queries remain mutable, so edits can invalidate their original shape or selectivity. The returned patterns are immutable snapshots.

See notebooks/workload_generation.ipynb for a toy example and optional live selectivity checks against the Docker MongoDB demo collection.

One configuration

MongoGenerationConfig contains all settings. Change only what your experiment needs:

config = MongoGenerationConfig(
    min_predicates=2,
    max_predicates=4,
    exclude_fields=["_id", "name", "address"],
    field_weights={"default": 1.0, "overrides": {"attributes.*": 0.05}},
    sort_probability=0.3,
    limit_probability=1.0,
    limit_min=10,
    limit_max=100,
    projection_probability=0.2,
)
Settings Defaults and meaning
min_predicates, max_predicates 1, 8; maximum capped by available fields, minimum never relaxed
include_fields, exclude_fields None, ["_id"]; exact normalized paths, include takes precedence
field_weights Default 1; positive weights with case-sensitive glob overrides, last match wins
sort_probability 0.3
sort_on_filter_field_ratio 0.7
sort_two_field_ratio, sort_ascending_ratio 0.3, 0.8
skip_probability, limit_probability 0.0, 0.2
skip_min, skip_max 0, 1000, inclusive
limit_min, limit_max 1, 100, inclusive
skip_requires_limit True
projection_probability 0.0; inclusion projections
projection_filter_overlap_ratio 0.5
projection_min_fields, projection_max_fields 1, 5 requested fields
projection_exclude_id_ratio 0.5; adds _id: 0 unless an _id path is projected

Field weights apply to filters, sorts and projections, with weighted selection without replacement. Exclusions apply to all three (except explicit _id: 0). Requested sort/projection counts may shrink when suitable fields are scarce. Projection paths that collide with parent/child paths are not selected together. Probabilities describe proposed pattern slots; rejection changes final frequencies. For each slot, predicate count and option-presence flags stay fixed through retries.

Operator configuration retains the existing explicit per-type weights:

config = MongoGenerationConfig(
    operator_config={
        "types": {
            "int": {"operators": ["eq", "gte", "in"], "weights": [1, 1, 0.5]},
            "str": {"operators": ["eq", "ne"], "weights": [1, 0.2]},
        },
        "operators": {
            "$exists": {"enabled": True, "chance": 0.3},
            "$type": {"enabled": False, "chance": 0},
            "$size": {"enabled": False, "chance": 0},
        },
    },
)

This replaces, rather than merges with, defaults. Omitted scalar types are not proposed; omitted special operators are disabled. Ordinary operators are eq, ne, lt, lte, gt, gte, in, nin. Zero operator weights disable individual operators; at least one weight per configured type must be positive.

Default integer operators are comparisons plus equality (weights 1,1,1,1,0.5); floats use four comparisons; strings use eq/ne/in/nin/gte/lte with weights 1/0.2/0.4/0.2/0.3/0.3. Booleans and ObjectIds use eq/ne (1/0.3), datetimes use comparisons/equality equally, and null uses eq/ne (1/0.3). Int64 follows integers.

$in uses 2–10 distinct values, $nin 1–10, and $size 0–5. Special operators conditionally replace an ordinary predicate, in exists/type/size order. Defaults are enabled with chances 0.3/0.2/0.1: exists requires observed missingness, type requires multiple observed types, and size requires an observed array. These probabilities are conditional, not independent final proportions.