Chapter 19 · Firestore with MongoDB Compatibility: Drivers, MQL/BSON, Tools, and Serverless Differences

Supported BSON Types, _id Rules, Documents / Collections, MQL Query / CRUD Compatibility, and Limits

Validate AtlasMart BSON round-trips, document and collection naming, _id constraints, CRUD/query/aggregation support, document/query limits, and behavior that differs from MongoDB even when the syntax looks familiar.

Advanced · 180–240 minutesMongoDB compatibility · MQL/BSON · tools · serverless differencesNode 22.23.2 · mongodb 6.21.0 · Mongoose 8.24.4 · mongosh 2.10.0gcloud 585.0.0 · managed MongoDB-compatible database optional · local contract harness mandatoryLast reviewed: 17 September 2026

1. AtlasMart problem: BSON can serialize successfully and still violate the destination contract

Execution and safety note

Use the Emulator Suite, a Firebase demo project, or an isolated test project for destructive, security-sensitive, billing-sensitive, migration, backup/restore, or write-heavy exercises unless the lesson explicitly marks managed verification as required. Treat shown output as expected evidence unless it is explicitly identified as captured output, and re-check current Firebase/Google Cloud edition, mode, quota, pricing, and security documentation before production execution.

After the connection works, the next migration failure usually comes from data shape and query semantics rather than networking. AtlasMart has IDs, dates, arrays, nested objects, numeric values, regex filters, and update operators. Many are supported, some differ, and a few familiar BSON/MQL features are explicitly unsupported. The safe technique is a round-trip contract: generate a controlled document, write it in the optional managed test, read it back, compare type/value semantics, and separately test every query/update command the application requires.

Chapter 19 reproducibility baseline · reviewed 17 September 2026

AtlasMart keeps the course-wide project identity demo-atlasmart-firestore. The mandatory compatibility harness is local/no-cost and targets Node.js 22.23.2 LTS with mongodb@6.21.0, a current Firestore-supported 6.x Node driver. Tool examples pin mongosh 2.10.0, MongoDB Compass 1.50.0, Mongoose 8.24.4, Google Cloud CLI 585.0.0, and retain Firebase CLI 15.30.0 for course continuity. The optional managed database is atlasmart-mongo-lab, Enterprise edition with MongoDB-compatible data access in an explicitly chosen supported location such as us-central1. Re-check location availability before creation.

Managed-service boundary

Unlike the Native-mode labs, current official documentation does not provide a Local Emulator Suite endpoint that emulates the MongoDB wire/API compatibility surface. Therefore the mandatory lab uses deterministic BSON/query/index/compatibility fixtures and executable contract tests without credentials. A real Firestore with MongoDB compatibility database is an optional verification layer: database creation requires an actual Google Cloud/Firebase project and billing enabled, and connection/authentication uses SCRAM or Google Cloud identity. No lesson fabricates a successful handshake, Query Explain result, billed unit count, p95/p99 latency, or tool connection that was not actually executed.

Learning outcomes

01

Validate supported BSON types and explicitly reject unsupported values before migration.

02

Apply current _id type, size, naming, document-size, and nesting constraints.

03

Distinguish MQL syntax support from identical MongoDB behavior and natural ordering.

04

Build CRUD/query/update/aggregation compatibility tests with deterministic expected state.

05

Use Query Explain and indexes as production evidence rather than assuming successful scans are efficient.

2. BSON support: compatibility is a typed contract

BSON family Current status AtlasMart consequence
32/64-bit integer, Double, Decimal128 Supported Preserve type-aware fixtures; Decimal128 arithmetic has compatibility limits.
String, Boolean, Null Supported Basic fields round-trip normally subject to limits.
Date, Timestamp Supported Date range constraints apply; test serialization from each driver.
Array, Object Supported Nesting depth limit is 20; index fan-out can matter.
ObjectId, Binary, Regex Supported Regex options have constraints; Binary useful for specific IDs/payloads.
JavaScript / JavaScript-with-scope Unsupported Refactor code-as-data patterns.
Symbol, DBPointer, Undefined Unsupported Normalize before migration; do not let the pipeline discover this late.

Do not treat “BSON compatible” as “all BSON.” Build a source inventory before migration and fail fast on unsupported types. Chapter 20 will turn that inventory into a migration gate.

3. _id: unique does not mean automatically indexed the MongoDB way

The current supported-data-types page permits top-level _id values in a constrained set including ObjectId, String, integer, Double, Binary, Object, and Boolean, with a 1,500-byte maximum. Firestore guarantees _id uniqueness within the collection, but it does not automatically create a MongoDB-style ordered _id index. If AtlasMart sorts/ranges on _id, it must create the ordered index deliberately—and avoid monotonically increasing IDs at high write rates because they can create hotspots.

_id contract fixture
import { ObjectId, Long, Binary } from "mongodb";const ids = [  new ObjectId("66f000000000000000000001"),  "order-2026-0001",  Long.fromNumber(42),  42,  42.5,  new Binary(Buffer.from("abc")),  { tenant: "tenant-a", local: 7 },  true];// Managed lab: insert each into an isolated collection and round-trip type/value.
Boundary case

A timestamp-shaped string may be a valid _id, yet sequential IDs at scale can concentrate traffic. An ID can be syntactically legal and operationally poor.

4. Document and naming limits shape the schema

Constraint Current MongoDB-compatible Firestore limit / difference Test
Document size 16 MiB Generate a bounded near-limit test document only in isolated tooling; never production.
Field value 16 MiB − 89 bytes Reject oversized blobs; use object storage where appropriate.
Nested arrays/objects Maximum depth 20 Static schema walk before migration.
Field/collection naming No empty field name; names matching __.*__ rejected; collection restrictions also apply Run a name inventory.
Connection database scope One Firestore database per connection Do not port multi-database connection assumptions blindly.

5. CRUD compatibility starts with result-state assertions

atlasmart-products.json · deterministic fixture
[  {"_id":"p-1001","sellerId":"seller-a","tenantId":"tenant-a","name":"Trail Camera","category":"cameras","price":99.0,"stock":8,"rating":4.6,"tags":["outdoor","camera"],"published":true},  {"_id":"p-1002","sellerId":"seller-a","tenantId":"tenant-a","name":"USB-C Hub","category":"accessories","price":49.0,"stock":3,"rating":4.4,"tags":["usb-c","desk"],"published":true},  {"_id":"p-1003","sellerId":"seller-b","tenantId":"tenant-a","name":"Temp Sensor","category":"sensors","price":39.0,"stock":12,"rating":4.7,"tags":["iot","sensor"],"published":true},  {"_id":"p-1004","sellerId":"seller-b","tenantId":"tenant-a","name":"Edge Gateway","category":"gateways","price":149.0,"stock":1,"rating":4.2,"tags":["edge","iot"],"published":true},  {"_id":"p-1005","sellerId":"seller-c","tenantId":"tenant-b","name":"PoE Camera","category":"cameras","price":199.0,"stock":5,"rating":4.8,"tags":["poe","camera"],"published":true},  {"_id":"p-1006","sellerId":"seller-a","tenantId":"tenant-a","name":"Bench PSU","category":"power","price":89.0,"stock":9,"rating":4.5,"tags":["bench","power"],"published":true}]
compatibility-matrix.json · versioned contract
{  "target": "firestore-enterprise-mongodb-compat-2026-09-17",  "driver": "mongodb@6.21.0",  "checks": {    "find": "supported",    "insert": "supported",    "update": "supported",    "delete": "supported",    "aggregate": "supported",    "transactions": "supported-with-different-defaults",    "retryableWrites": "unsupported-disable-in-uri",    "mapReduce": "unsupported",    "gridFS": "unsupported",    "wildcardIndexes": "unsupported",    "vectorIndexMongoAPI": "unsupported",    "textSearch": "preview",    "geoNear": "preview",    "changeStreams": "preview"  }}
compatibility-local.mjs · mandatory no-credential harness
import assert from "node:assert/strict";import fs from "node:fs";const products = JSON.parse(fs.readFileSync("atlasmart-products.json", "utf8"));const matrix = JSON.parse(fs.readFileSync("compatibility-matrix.json", "utf8"));assert.equal(products.length, 6);assert.equal(new Set(products.map(p => p._id)).size, 6);assert.deepEqual(  products.filter(p => p.tenantId === "tenant-a" && p.stock < 5).map(p => p._id).sort(),  ["p-1002", "p-1004"]);assert.equal(products.filter(p => p.category === "cameras").length, 2);assert.equal(matrix.checks.retryableWrites, "unsupported-disable-in-uri");assert.equal(matrix.checks.changeStreams, "preview");console.log(JSON.stringify({fixture:"PASS", products:6, lowStock:["p-1002","p-1004"]}));
run the mandatory harness
node compatibility-local.mjs
crud-contract.mjs · optional managed database
import { MongoClient } from "mongodb";const client = new MongoClient(process.env.ATLASMART_MONGO_URI);await client.connect();const db = client.db("atlasmart-mongo-lab");const c = db.collection("compat_products");await c.deleteMany({ testRun: "ch19-l2" });await c.insertOne({ _id:"p-contract", testRun:"ch19-l2", stock:3, tags:["a"], price:49 });await c.updateOne({ _id:"p-contract" }, { $inc:{stock:2}, $addToSet:{tags:"b"} });const got = await c.findOne({ _id:"p-contract" });if (got.stock !== 5 || got.tags.length !== 2) throw new Error("state mismatch");await c.deleteOne({ _id:"p-contract" });if (await c.findOne({ _id:"p-contract" })) throw new Error("delete mismatch");console.log("CRUD contract PASS");await client.close();

The optional script validates final state, not merely acknowledged commands. That distinction catches serialization and update-operator behavior that a “no exception thrown” test misses.

6. Query compatibility: supported operators are not infinite

Current MongoDB 8.0 compatibility tables include common comparison ($eq, $gt, $in), logical ($and, $or), array ($all, $elemMatch, $size), expression, regex, text, CRUD, and aggregation features. They also explicitly mark unsupported features such as $where, $jsonSchema, mapReduce, GridFS, wildcard indexes, and many administrative/topology commands. AtlasMart converts the features it actually uses into executable tests rather than pasting the entire vendor matrix into production requirements.

query oracle from six-product fixture
// Expected IDs from the same fixture:// tenant-a + stock < 5       => [p-1002, p-1004]// cameras                    => [p-1001, p-1005]// rating >= 4.6              => [p-1001, p-1003, p-1005]// tags contains "camera"     => [p-1001, p-1005]// seller-a ordered by price  => [p-1002, p-1006, p-1001]

7. Natural order is not a contract

Firestore documentation explicitly warns that a query without an explicit sort does not guarantee MongoDB insertion order or _id ascending order. Any AtlasMart API whose pagination or user-visible ordering depends on natural order is already underspecified. Repair it by sorting on explicit fields with a stable tie-breaker and creating the needed index.

stable pagination shape
db.products.find({tenantId:"tenant-a", published:true})  .sort({price:1, _id:1})  .limit(3)

8. Aggregation compatibility has a surface and a resource budget

Aggregation pipelines are supported, but the current behavior-differences page caps them at 250 stages and excludes stages such as $merge and $out. Query memory is limited. A syntactically supported pipeline can still be a poor OLTP query if it scans too much data or materializes large intermediate sets.

bounded seller summary
db.products.aggregate([  {$match:{tenantId:"tenant-a", published:true}},  {$group:{_id:"$sellerId", products:{$sum:1}, stock:{$sum:"$stock"}, avgRating:{$avg:"$rating"}}},  {$sort:{_id:1}}])

9. Indexes are optional at query time, not optional for engineering

Firestore with MongoDB compatibility creates no indexes by default. A query can work by scanning data, which makes “it returned the right rows” a dangerous performance test. Create indexes for common/selective patterns, then use Query Explain to record rows scanned, bytes read, memory, execution tree, read units, and returned rows on a managed test dataset.

managed index / explain experiment
db.products.createIndex({tenantId:1, sellerId:1, price:1})db.products.find({tenantId:"tenant-a", sellerId:"seller-a"})  .sort({price:1})  .explain("executionStats")// Archive the plan + billing stats, then compare a bounded index-absent run.

10. Failure injection: use an unsupported feature intentionally

Choose one source dependency—such as mapReduce, $where, GridFS, wildcard index, or schema validation—and run the smallest isolated call. The expected outcome is a compatibility error, which the suite records as an expected difference. Do not catch-and-ignore it: the test should force a migration decision (refactor, replace, or block).

expected-unsupported.json
{  "feature": "mapReduce",  "sourceRequired": true,  "firestoreMongoCompat": "unsupported",  "decision": "replace with aggregation/materialized workflow before migration",  "owner": "atlasmart-data-platform"}

Verification checklist

  • BSON inventory has an explicit policy for unsupported types.
  • _id type/size constraints are tested from the real driver.
  • Final document state is asserted after CRUD/update operations.
  • Queries specify deterministic ordering where product behavior depends on order.
  • Every required MQL operator/stage has a test; unsupported dependencies are blockers, not footnotes.
  • Query Explain is reserved for managed evidence and no fabricated scan/billing numbers appear.

Bridge to Lesson 3

Lesson 2 proved the data and MQL contract. Lesson 3 moves outward to the ecosystem: shell, GUI, import/export, dump/restore, and Mongoose can all connect, but each tool carries assumptions about server commands, topology, schema enforcement, or features that must be tested.

Knowledge check

  1. Does Firestore automatically create an ordered _id index?
  2. What is the current maximum document size?
  3. Why is natural order unsafe for pagination?
  4. Does supported aggregation syntax make every pipeline efficient?
  5. What should happen when the source app depends on an unsupported command?
Review the answers

1. No. It enforces _id uniqueness, but ordered _id query behavior requires an explicit index when needed.

2. 16 MiB for Firestore with MongoDB compatibility.

3. It is not guaranteed to match insertion order or _id ascending, so explicit stable sorting is required.

4. No. Scan volume, memory, indexes, and billing still require evidence.

5. The compatibility suite should fail with a migration decision: refactor, replace, or block migration.

Summary and next step

This lesson established the working contract for Supported BSON Types, _id Rules, Documents/Collections, MQL Query/CRUD Compatibility, and Limits. Keep its edition/mode assumptions, trust boundary, verification evidence, and operational constraints explicit when reusing the pattern.

Next, continue to mongosh, Compass, mongoimport/export, mongodump/restore, Mongoose, and Tool Compatibility Boundaries.

Authoritative references

Keep knowledge open

Help the academy stay free and grow.

If these tutorials save you time, a small donation supports new lessons, technical review, diagrams, examples, and long-term maintenance.

ETHEthereum / ERC-20 only
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0

Send only Ethereum or ERC-20 compatible assets to this address.