Chapter 03 · CRUD Fundamentals: Insert, Find, Projection, Sort, Limit, and Delete
Build a CRUD Lab and Verify Every Operation with Deterministic Queries
Combine the chapter into one auditable CRUD runbook that records exact inputs, results, batches, partial failures, and postconditions.
Learning outcomes
This capstone turns the chapter's isolated operations into a reproducible AtlasMart data task: reset a fixture, insert known records, prove partial-success handling, query with a precise contract, consume a cursor in bounded batches, delete only staged records after a preflight, and verify every postcondition. The goal is not command memorization; it is an evidence-driven CRUD runbook.
Build one deterministic fixture that exercises insert, read, projection, sort, limit, cursor, and delete semantics.
Record before state, operation inputs/options, result metadata, and after state for every mutation.
Use stable _id values and total sort orders so repeated verification is comparable.
Handle expected duplicate-key failure without confusing partial success with rollback.
Finish with a reusable checklist for safe CRUD work in scripts and services.
Mandatory labs use a disposable loopback-only standalone
mongodb/mongodb-community-server:8.3.8-ubuntu2204-slim
with a dedicated AtlasMart collection and explicit reset
commands. Driver examples pin pymongo==4.17.0.
The standalone is intentionally unauthenticated only for these
short-lived local exercises; do not publish it beyond
127.0.0.1. Docker commands use syntax accepted
directly by ordinary shells; if the first cleanup reports that
the container does not exist, that is harmless.
Retryable-write behavior that requires a replica set or
sharded cluster is explained but not claimed for this
standalone.
Docker, mongod, mongosh, and PyMongo are not available in this generation environment. Commands were checked against current official MongoDB Server and PyMongo documentation, but product commands were not executed here. Expected-output blocks describe stable fields and relationships to verify; they are not fabricated captured transcripts.
1. Define the CRUD contract before executing commands
| Step | Exact contract | Evidence |
|---|---|---|
| Reset | Dedicated atlasmart.crud_lab only |
Collection absent/empty before seed |
| Insert | Known IDs for fixture plus one generated-ID demonstration | Acknowledged result, inserted IDs, sorted stored rows |
| Read |
Explicit filter + projection +
{priceCents:1,_id:1}
|
Returned documents and count |
| Cursor | Batch size 2, deterministic order | First/next batches and exhaustion |
| Delete | Tenant + source + status predicate | Preflight count/sample, deletedCount, zero-match postcondition |
This table is the operational specification. It makes reviews possible because every destructive step has a corresponding read-only proof and every read has an explicit ordering contract.
2. One mongosh runbook with deliberately observable state
use atlasmartdb.crud_lab.drop();printjson({phase:"reset", count:db.crud_lab.countDocuments({})});const generated = db.crud_lab.insertOne({sku:"generated",tenantId:"tenant-demo",kind:"catalog",active:true,priceCents:7000});printjson({phase:"insertOne", acknowledged:generated.acknowledged, insertedId:generated.insertedId});try { db.crud_lab.insertMany([ {_id:"p1",sku:"cam-a",tenantId:"tenant-demo",kind:"catalog",active:true,category:"camera",priceCents:9000}, {_id:"p2",sku:"cam-b",tenantId:"tenant-demo",kind:"catalog",active:true,category:"camera",priceCents:9000}, {_id:"p2",sku:"duplicate",tenantId:"tenant-demo",kind:"catalog",active:true,category:"camera",priceCents:1}, {_id:"p3",sku:"cam-c",tenantId:"tenant-demo",kind:"catalog",active:true,category:"camera",priceCents:12000}, {_id:"stage-1",sku:"staged-a",tenantId:"tenant-demo",kind:"staging",source:"import-03",status:"staging",priceCents:5000}, {_id:"stage-2",sku:"staged-b",tenantId:"tenant-demo",kind:"staging",source:"import-03",status:"staging",priceCents:6000} ], {ordered:false});} catch(e) { printjson({phase:"insertManyError",code:e.code,result:e.result});}printjson({phase:"afterInsert", docs:db.crud_lab.find({}, {_id:1,sku:1,kind:1}).sort({_id:1}).toArray()});const filter={kind:"catalog",active:true,category:"camera",priceCents:{$gte:8000,$lte:12000}};const projection={_id:1,sku:1,priceCents:1};const result=db.crud_lab.find(filter,projection).sort({priceCents:1,_id:1}).limit(10).toArray();printjson({phase:"read",filter,projection,returned:result.length,result});const first=db.runCommand({find:"crud_lab",filter:{kind:"catalog"},sort:{_id:1},batchSize:2});printjson({phase:"cursorFirst",id:first.cursor.id,batch:first.cursor.firstBatch.map(x=>x._id)});let id=first.cursor.id;while(id.toString() !== "0"){ const more=db.runCommand({getMore:id,collection:"crud_lab",batchSize:2}); printjson({phase:"cursorMore",id:more.cursor.id,batch:more.cursor.nextBatch.map(x=>x._id)}); id=more.cursor.id;}const deleteFilter={tenantId:"tenant-demo",kind:"staging",source:"import-03",status:"staging"};const deleteBefore=db.crud_lab.countDocuments(deleteFilter);printjson({phase:"deletePreflight",filter:deleteFilter,count:deleteBefore,sample:db.crud_lab.find(deleteFilter,{_id:1,sku:1}).sort({_id:1}).toArray()});const del=db.crud_lab.deleteMany(deleteFilter);printjson({phase:"deleteResult",acknowledged:del.acknowledged,deletedCount:del.deletedCount});printjson({phase:"final",sameFilterRemaining:db.crud_lab.countDocuments(deleteFilter),all:db.crud_lab.find({}, {_id:1,sku:1,kind:1}).sort({_id:1}).toArray()});
Every phase emits a compact evidence record. A production implementation would normally send equivalent fields to structured logs/traces rather than relying on terminal output. Be careful not to log sensitive document contents, credentials, or tenant data indiscriminately.
3. The driver version should preserve the same invariants
The API surface changes from camelCase mongosh methods to PyMongo methods and result classes, but the correctness model is the same. Filters remain BSON documents represented as Python dictionaries, sorted reads need a unique tie-breaker, cursors should be iterated/closed, and write result objects/errors must be inspected.
from pymongo import MongoClient, ASCENDINGfrom pymongo.errors import BulkWriteErrorURI = "mongodb://127.0.0.1:27031/?directConnection=true"client = MongoClient(URI, serverSelectionTimeoutMS=3000)coll = client.atlasmart.crud_labcoll.drop()one = coll.insert_one({"sku":"generated", "kind":"catalog", "active":True, "priceCents":7000})print("insert_one", one.acknowledged, one.inserted_id)try: coll.insert_many([ {"_id":"p1","sku":"cam-a","kind":"catalog","active":True,"category":"camera","priceCents":9000}, {"_id":"p2","sku":"cam-b","kind":"catalog","active":True,"category":"camera","priceCents":9000}, {"_id":"p2","sku":"duplicate","kind":"catalog","active":True,"category":"camera","priceCents":1}, {"_id":"p3","sku":"cam-c","kind":"catalog","active":True,"category":"camera","priceCents":12000}, ], ordered=False)except BulkWriteError as exc: print("partial", exc.details.get("nInserted"), [(e.get("index"),e.get("code")) for e in exc.details.get("writeErrors",[])])flt = {"kind":"catalog","active":True,"category":"camera","priceCents":{"$gte":8000,"$lte":12000}}with coll.find(flt,{"_id":1,"sku":1,"priceCents":1}).sort([("priceCents",ASCENDING),("_id",ASCENDING)]).batch_size(2) as cur: for doc in cur: print("read", doc)client.close()
The test should assert state, not just “no exception.” For example, assert the persisted IDs after the unordered error, the exact ordered IDs returned by the query, and the zero remaining count after a scoped delete. These assertions transform examples into regression tests.
4. Failure interpretation matrix
| Signal | What it proves | What to do next |
|---|---|---|
acknowledged:true |
The server acknowledged under configured write concern | Verify business postcondition; do not infer unrelated side effects |
| Bulk write error + inserted count > 0 | Partial success occurred | Reconcile successful IDs and failed indexes; do not replay blindly |
deletedCount:0 |
No document was deleted by that call | Re-check predicate and concurrent state |
| Cursor id nonzero | More server-side results may remain | Iterate/getMore or close when finished |
| Same delete filter count = 0 afterward | Observed postcondition in current state | Record evidence; consider concurrency/topology requirements |
5. Run the complete disposable lab
docker rm -f atlasmart-mongo-ch03-l5docker run --name atlasmart-mongo-ch03-l5 -p 127.0.0.1:27031:27017 -d mongodb/mongodb-community-server:8.3.8-ubuntu2204-slimdocker logs atlasmart-mongo-ch03-l5 --tail 25
# Save the mongosh runbook from the lesson as chapter03_crud_lab.js, then run:mongosh "mongodb://127.0.0.1:27031/atlasmart?directConnection=true" --quiet chapter03_crud_lab.js# Optional driver path after creating/activating a virtual environment:python -m pip install "pymongo==4.17.0"python chapter03_crud_lab.py
Verification checklist
-
Exactly one generated
_idis captured and used to verify the inserted document. - The duplicate-key batch reports partial success and the persisted IDs are inspected afterward.
- The read contract prints filter, projection, deterministic sort result, and returned count.
- The cursor produces multiple bounded batches and reaches exhaustion.
-
The delete preflight and
deletedCountagree in this quiescent fixture, the same filter returns zero afterward, and catalog rows survive. -
All mutation scope is confined to
atlasmart.crud_labin the disposable container.
Check your understanding
- What is the chapter-wide rule for every mutation?
- Why does the fixture use stable string _id values for most rows?
- Why is a thrown BulkWriteError not enough information?
- What makes a paginated read deterministic?
- What is still missing before calling this production-ready?
Review the answers
Record the before state/scope, exact operation inputs and options, result metadata, and an explicit after-state verification.
They make expected ordering and reconciliation deterministic while a separate insert still demonstrates generated ObjectId behavior.
It does not by itself tell you whether earlier or independent operations succeeded; inspect counts/error indexes and persisted state.
An explicit total sort order, typically ending with a unique tie-breaker such as _id, plus a precise filter/projection.
Authentication/TLS, topology-specific concerns and retries, indexes/query plans, monitoring, backups, tenant authorization, load testing, and later-course operational controls must be designed and verified.
docker rm -f atlasmart-mongo-ch03-l5
Chapter 04 now deepens the query language: arrays, nested fields, null/missing semantics, and expression-based predicates.
Authoritative references
- MongoDB release notes — Official current stable server series and patch notes.
- MongoDB 8.3 release notes — Official 8.3 patch history; 8.3.8 is the latest released patch at review time.
- MongoDB CRUD operations — Official CRUD overview and server semantics.
- PyMongo CRUD guides — Official Python driver CRUD behavior and result objects.
- insertMany() — Batch insert result/error behavior.
- find database command — Filter/window/cursor mechanics.
- PyMongo cursor access — Cursor iteration and resource management.
- deleteMany() — Multi-document delete result and topology caveats.
- Retryable writes — Replica-set/sharded retry semantics and error labels.