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.
1. AtlasMart problem: BSON can serialize successfully and still violate the destination contract
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.
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.
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
Validate supported BSON types and explicitly reject unsupported values before migration.
Apply current _id type, size, naming, document-size, and nesting constraints.
Distinguish MQL syntax support from identical MongoDB behavior and natural ordering.
Build CRUD/query/update/aggregation compatibility tests with deterministic expected state.
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.
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.
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
[ {"_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}]
{ "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" }}
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"]}));
node compatibility-local.mjs
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.
// 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.
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.
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.
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).
{ "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.
-
_idtype/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
-
Does Firestore automatically create an ordered
_idindex? - What is the current maximum document size?
- Why is natural order unsafe for pagination?
- Does supported aggregation syntax make every pipeline efficient?
- 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
- Google Cloud · Firestore with MongoDB compatibility overview
- Google Cloud · Authenticate and connect to a database
- Google Cloud · Supported BSON types, drivers, and third-party tools
- Google Cloud · Behavior differences from MongoDB
- Google Cloud · Supported MongoDB 8.0 feature matrix
- Google Cloud · MongoDB-compatible indexing overview
- Google Cloud · Query Explain for MongoDB-compatible operations
- Google Cloud · Quotas and limits
- Google Cloud · MongoDB compatibility release notes
- Google Cloud · Text search (Preview)
- Google Cloud · Geospatial search (Preview)
- MongoDB · mongosh release notes