Differences from MongoDB¶
MiniMongoDB is a mechanism model, not a compatibility implementation. This file records both the design's non-goals and smaller semantic differences introduced through M2.
Explicit non-goals¶
- sharding,
mongos, balancer behavior, and shard keys; - replica-set networking, elections, sync source selection, and rollback;
- multi-document ACID transactions, sessions, read/write concern, and MVCC;
$lookup, change streams, TTL, text, wildcard, hashed, and geospatial indexes;- MongoDB wire protocol, drivers, authentication, authorization, and server administration;
- binary BSON compatibility;
- WiredTiger compression, pages, cache eviction, concurrency, and checkpoints;
- JavaScript execution, schema validation, collation, regex, and full MQL.
Milestone boundaries¶
- M1 (implemented): value/path semantics, CRUD, query/update subset,
automatic
_id, idempotent oplog, journal, checkpoint, recovery, labs. - M2 (implemented): secondary/compound/unique/multikey indexes,
prefix-aware selectivity, IXSCAN/COLLSCAN planning,
explain, and the minimum aggregation pipeline. - M3 (not implemented): capped/ring oplog retention and replication mapping
to MiniDist. Only
oplog/capped.pyremains a documentation boundary.
BSON and identity differences¶
- Documents are Python
dictvalues and arrays arelistvalues. Supported scalar values areNone, bool, int/float, string, and MiniMongoDBObjectId. Dates, binary, decimal, regex, timestamp, MinKey/MaxKey, code, and others are rejected. - The local cross-type order is:
null < number < string < document < array < bool < objectId. It is deliberately not the full BSON comparison order. - Exact embedded-document equality treats field insertion order as significant. Persistence preserves that order using ordered key/value pairs.
- Real ObjectIds include non-deterministic/environment-derived material. MiniMongoDB's 24-hex-digit analogue is only an injected monotonic counter.
- The
_idindex recursively converts values to hashable canonical keys with BSON type tags. ThusTrueand1are distinct, while numerically equal int/float values share one identity, matching this project'sbson_equal.
Query differences¶
- Implemented operators are only
$eq(including implicit equality),$gt/$gte/$lt/$lte/$ne/$in/$exists/$and/$or/$not. - Field-level
$notfollows the supported MongoDB-shaped subset. MiniMongoDB additionally accepts$notas a top-level logical operator; real MongoDB does not support top-level$not. This is a project extension, not portable MQL behavior. - Scalar equality and comparison inspect stored array elements recursively. Literal array equality remains whole-array and order-sensitive.
- Literal embedded documents use exact whole-value equality. Use a dotted path to select one nested field.
- Real MongoDB range predicates generally use type bracketing. MiniMongoDB instead compares supported unlike types using its documented global order.
- MongoDB
{field: null}also matches missing fields. MiniMongoDB distinguishes null from missing; use$exists: falsefor missing. - There is no
$elemMatch; multiple predicates on an array path may therefore be satisfied by different elements. findhas no projection, sort, skip, limit, cursor, collation, or regex options. It returns an eager list in insertion order, even when IXSCAN narrows the candidate set. Projection/sort/limit exist only as aggregation stages.
Index and planner differences¶
- Secondary indexes are ascending-only ordered canonical maps, not B-trees. Dotted fields and compound leftmost prefixes are supported; descending, sparse, partial, wildcard, hashed, text, and geospatial indexes are absent.
- Index keys use the same recursive BSON-tagged canonical form as
_id. Arrays expand recursively into per-element multikey entries and duplicate keys owned by the same document are de-duplicated. - A compound index forms the Cartesian product when more than one indexed field contains arrays. Real MongoDB rejects a compound multikey index when more than one indexed field is an array in a document; this miniature keeps the product visible as a teaching simplification.
- Missing indexed fields use a null-like key. There is no sparse option, and a unique index therefore permits only one document with that missing/null key.
- Index creation validates current documents, appends a durable
create_indexentry, then publishes the definition. Definitions also enter checkpoints; document changes update every index only after journal success. - The planner has no statistics catalog, histograms, plan cache, intersection, covered queries, sort satisfaction, or trial execution. It counts candidate owners for prefix-compatible indexes and chooses IXSCAN only when that count is smaller than the collection size.
- Safe
_idequality uses the automatic_id_IXSCAN. Because this teaching model still permits a root array as_id, scalar predicates fall back to COLLSCAN when any such id exists so matcher element-expansion cannot be lost. explain(query)is an eager collection method rather than a cursor method. It reports one winning plan andkeysExamined/docsExamined/nReturned; it has no verbosity modes, rejected plans, timing, yields, or storage metrics.
Aggregation differences¶
- The implemented stages are only
$match/$project/$group/$sort/$limit.$matchreuses the normal matcher; there is no pipeline rewrite or index pushdown. $groupsupports_idplus$sum/$avg/$min/$max/$push. Expressions are constants, dotted$fieldreferences, or nested document/list shapes; the full MongoDB expression language is absent.$match,$project, and$limitstream.$groupand$sortmaterialize input in memory. There is no spilling, parallelism, distributed merge,$lookup,$unwind, window functions, or optimizer.
Update and CRUD differences¶
- Implemented update operators are only
$set/$unset/$inc/$push/$pull.$pushhas no$each/$slice/$sort/$position;$pullaccepts the local matcher subset. - Paths may create missing dictionary parents. They never invent or extend a missing array; numeric indexes must already exist.
$unseton an array index writesNoneto preserve positions, matching the useful MongoDB behavior, while unsetting a document key removes it.- There are no upserts, array filters, positional
$operators, bulk-write modes, find-and-modify variants, or write concern. insert_manyvalidates the batch before making any document visible. Generated counter values consumed by a rejected batch are not rolled back.insert_many,update_many, anddelete_manyare not all-or-nothing durability transactions.insert_manyvalidates all candidates first; each batch method then prepares and commits one document at a time. If journal append for item N fails, the earlier items remain durable and visible, item N and later items remain invisible, the failing sequence is not consumed, and the storage error is raised.- Single-document mutation is atomic only in the single Python process: the engine builds and validates a copy before swapping it. There is no concurrent reader/writer isolation.
Oplog differences¶
- MiniMongoDB emits one entry per affected document (plus index-definition entries), not a byte-compatible MongoDB oplog record.
- Insert/replace records carry a full document. Update records carry
$setand$unsetpost-images for every requested path;$inc,$push, and$pullnever survive into the entry. - Delete replay is a no-op when the key is already absent. Insert/replace replay
converges by
_id. - The in-memory oplog is unbounded. Capped retention is M3.
- There are no terms, timestamps, wall clocks, replica identities, majority commit points, rollback, or cross-node transport.
Storage and crash differences¶
- The local journal frames logical oplog entries as
4-byte length | payload | 4-byte CRC32. Real MongoDB uses WiredTiger's storage-engine journal separately from the replication oplog. - Appends flush and
fsyncbefore the document,_id/secondary indexes, sequence, or in-memory oplog entry becomes visible. A failed append is best-effort truncated back to its prior boundary and the storage error is raised. There is no group commit. - Only an invalid final frame is repaired by truncating to the last valid prefix. A CRC/decode failure before later bytes raises corruption instead of silently discarding history.
- Checkpoints serialize the whole database as tagged JSON, replace one file
atomically, and
fsyncthe parent directory after rename. They have no independent checksum, pages, compression, fuzzy checkpoint protocol, or concurrent-write coordination. - Startup loads the checkpoint and replays only journal sequences newer than the snapshot. Replay does not recursively append new records.
- The public
inject_journal_tail_truncationmethod exists only for deterministic teaching labs and tests; it is not a database administration feature.