-
-
Notifications
You must be signed in to change notification settings - Fork 101
feat(mongodb): [WRA-22] add MongoDB foreign data wrapper #606
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 21 commits
Commits
Show all changes
23 commits
Select commit
Hold shift + click to select a range
5492407
feat(mongodb_fdw): add cargo feature and dependencies
burmecia e6d34c4
ci(mongodb_fdw): add mongo:7 service to native docker-compose
burmecia 7ce95a0
feat(mongodb_fdw): scaffold module, error type, README
burmecia 1961baf
feat(mongodb_fdw): implement BSON to Cell type mapping
burmecia fd8ab31
feat(mongodb_fdw): implement Cell to BSON write-path mapping
burmecia 25a5e29
feat(mongodb_fdw): qual/sort/limit/projection deparse helpers
burmecia 683cc91
feat(mongodb_fdw): implement begin_scan, iter_scan, re_scan
burmecia a2d15e3
test(mongodb_fdw): smoketest covering count, lookup, missing field
burmecia 3639340
test(mongodb_fdw): pushdown operators, sort, limit
burmecia 5d7408b
test(mongodb_fdw): _doc jsonb column reads full document
burmecia 7c80844
feat(mongodb_fdw): implement begin_modify, insert
burmecia 88f9ccc
feat(mongodb_fdw): implement update and delete
burmecia dfe9a4d
test(mongodb_fdw): ObjectId hex round-trip on read and qual
burmecia b838ec3
docs(mongodb_fdw): add catalog page
burmecia 0b2d0cd
chore(mongodb_fdw): cargo fmt
burmecia cc87387
fix(mongodb_fdw): use per-test collection names to avoid parallel-tes…
burmecia fe5be1f
fix(docker-compose): rename mongo service to mongodb for consistency
burmecia 6fba4e4
test(mongodb_fdw): add comprehensive type conversion tests
burmecia 2e848c9
fix(mongodb_fdw): enable dns-resolver for mongodb+srv URIs and surfac…
burmecia a8dd9b7
fix(mongodb_fdw): sanitize error messages to prevent credential leakage
burmecia 0c47100
fix(tests): update float values in BSON tests for accuracy
burmecia e964cf0
feat(mongodb): add MongoDB FDW entry to the README
burmecia 8ddae74
fix(mongodb_fdw): address PR review feedback
burmecia File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,329 @@ | ||
| --- | ||
| source: | ||
| documentation: | ||
| author: Supabase | ||
| tags: | ||
| - native | ||
| --- | ||
|
|
||
| # MongoDB | ||
|
|
||
| [MongoDB](https://www.mongodb.com/) is a popular open-source document database that stores data as flexible BSON documents. | ||
|
|
||
| The MongoDB Wrapper allows you to read and write data from MongoDB within your Postgres database. Top-level BSON fields are mapped to declared columns by exact name; missing fields surface as SQL `NULL`. A special `_doc jsonb` column receives the full document for nested and array access. | ||
|
|
||
| ## Preparation | ||
|
|
||
| Before you can query MongoDB, you need to enable the Wrappers extension and store your credentials in Postgres. | ||
|
|
||
| ### Enable Wrappers | ||
|
|
||
| Make sure the `wrappers` extension is installed on your database: | ||
|
|
||
| ```sql | ||
| create extension if not exists wrappers with schema extensions; | ||
| ``` | ||
|
|
||
| ### Enable the MongoDB Wrapper | ||
|
|
||
| Enable the `mongodb_wrapper` FDW: | ||
|
|
||
| ```sql | ||
| create foreign data wrapper mongodb_wrapper | ||
| handler mongodb_fdw_handler | ||
| validator mongodb_fdw_validator; | ||
| ``` | ||
|
|
||
| ### Store your credentials (optional) | ||
|
|
||
| By default, Postgres stores FDW credentials inside `pg_catalog.pg_foreign_server` in plain text. Anyone with access to this table will be able to view these credentials. Wrappers is designed to work with [Vault](https://supabase.com/docs/guides/database/vault), which provides an additional level of security for storing credentials. We recommend using Vault to store your credentials. | ||
|
|
||
| ```sql | ||
| -- Save your MongoDB connection string in Vault and retrieve the created `key_id` | ||
| select vault.create_secret( | ||
| 'mongodb://user:password@host:27017/?replicaSet=rs0', | ||
| 'mongodb', | ||
| 'MongoDB connection string for Wrappers' | ||
| ); | ||
| ``` | ||
|
|
||
| ### Connecting to MongoDB | ||
|
|
||
| We need to provide Postgres with the credentials to connect to MongoDB, and any additional options. We can do this using the `create server` command: | ||
|
|
||
| === "With Vault" | ||
|
|
||
| ```sql | ||
| create server mongo_server | ||
| foreign data wrapper mongodb_wrapper | ||
| options ( | ||
| conn_string_id '<key_ID>' -- The Key ID from above. | ||
| ); | ||
| ``` | ||
|
|
||
| === "Without Vault" | ||
|
|
||
| ```sql | ||
| create server mongo_server | ||
| foreign data wrapper mongodb_wrapper | ||
| options ( | ||
| conn_string 'mongodb://user:password@host:27017/?replicaSet=rs0' | ||
| ); | ||
| ``` | ||
|
|
||
| The connection string follows the standard MongoDB URI format and supports `mongodb://` and `mongodb+srv://` schemes. Credentials, TLS options, replica set names, and other driver options can all be encoded in the URI. | ||
|
|
||
| Some connection string examples: | ||
|
|
||
| - `mongodb://root:secret@localhost:27017/` | ||
| - `mongodb://app_user:password@db.example.com:27017/?tls=true` | ||
| - `mongodb+srv://user:password@cluster.example.mongodb.net/?retryWrites=true` | ||
| - `mongodb://user:pass@host1:27017,host2:27017/?replicaSet=rs0` | ||
|
|
||
| ### Create a schema | ||
|
|
||
| We recommend creating a schema to hold all the foreign tables: | ||
|
|
||
| ```sql | ||
| create schema if not exists mongo; | ||
| ``` | ||
|
|
||
| ## Options | ||
|
|
||
| The following options are available when creating MongoDB foreign tables: | ||
|
|
||
| - `database` - MongoDB database name, required | ||
| - `collection` - MongoDB collection name, required | ||
| - `rowid_column` - Column to use as the row identifier, optional for data scan, required for data modify. The conventional value is `_id`. | ||
|
|
||
| ## Entities | ||
|
|
||
| ### Collections | ||
|
|
||
| The MongoDB Wrapper supports data reads and writes from MongoDB collections. | ||
|
|
||
| #### Operations | ||
|
|
||
| | Object | Select | Insert | Update | Delete | Truncate | | ||
| | ----------- | :----: | :----: | :----: | :----: | :------: | | ||
| | Collections | ✅ | ✅ | ✅ | ✅ | ❌ | | ||
|
|
||
| #### Usage | ||
|
|
||
| ```sql | ||
| create foreign table mongo.users ( | ||
| _id text, | ||
| name text, | ||
| age int, | ||
| created_at timestamp, | ||
| _doc jsonb | ||
| ) | ||
| server mongo_server | ||
| options ( | ||
| database 'app', | ||
| collection 'users', | ||
| rowid_column '_id' | ||
| ); | ||
| ``` | ||
|
|
||
| #### Notes | ||
|
|
||
| - Supports `where`, `order by`, and `limit` clause pushdown | ||
| - Documents are streamed one at a time from MongoDB; no full buffering in memory | ||
| - A column named `_doc` of type `jsonb` receives the complete document and can be used with Postgres JSON operators (`->`, `->>`) to access nested fields and arrays | ||
| - When `rowid_column` is set, INSERT, UPDATE, and DELETE are supported | ||
|
|
||
| ## Schema Mapping | ||
|
|
||
| Each column declared on the foreign table maps to a top-level BSON field of the same name (exact match): | ||
|
|
||
| - If a document does not contain a field, the corresponding column is set to `NULL`. | ||
| - Dots in column names are treated as literal characters — they do not traverse embedded documents. Use the `_doc` column for nested field access. | ||
| - If a column named `_doc` of type `jsonb` is declared, it receives the full BSON document serialized as JSON. When `_doc` is declared, projection is disabled so that the complete document is returned from MongoDB. | ||
|
|
||
| Example: access a nested `address.city` field via `_doc`: | ||
|
|
||
| ```sql | ||
| select _doc->>'name', _doc->'address'->>'city' | ||
| from mongo.users; | ||
| ``` | ||
|
|
||
| ## Query Pushdown Support | ||
|
|
||
| This FDW supports `where`, `order by`, and `limit` clause pushdown. | ||
|
|
||
| ### Supported Operators | ||
|
|
||
| The following SQL predicates are translated to MongoDB filter operators: | ||
|
|
||
| | SQL predicate | MongoDB filter | | ||
| | ----------------- | ----------------------- | | ||
| | `=` | `{field: {$eq: v}}` | | ||
| | `!=` | `{field: {$ne: v}}` | | ||
| | `<` | `{field: {$lt: v}}` | | ||
| | `<=` | `{field: {$lte: v}}` | | ||
| | `>` | `{field: {$gt: v}}` | | ||
| | `>=` | `{field: {$gte: v}}` | | ||
| | `IN (...)` | `{field: {$in: [...]}}` | | ||
| | `NOT IN (...)` | `{field: {$nin: [...]}}` | | ||
| | `IS NULL` | `{field: {$eq: null}}` | | ||
| | `IS NOT NULL` | `{field: {$ne: null}}` | | ||
|
|
||
| Multiple `where` predicates are AND'd at the top level of the filter document. Predicates with `OR` semantics are combined via `$or: [...]`. Any predicate shape that is not supported is omitted from the MongoDB filter and re-checked by Postgres after the rows are returned — the result is always correct, and the fallback is automatic. | ||
|
|
||
| ## Supported Data Types | ||
|
|
||
| | BSON Type | Postgres Type | Notes | | ||
| | ----------- | ----------------------- | ------------------------ | | ||
| | Boolean | bool | | | ||
| | Int32 | int2 / int4 / int8 | | | ||
| | Int64 | int8 / numeric | | | ||
| | Double | float8 / float4 | | | ||
| | Decimal128 | numeric | | | ||
| | String | text / varchar | | | ||
| | ObjectId | text | Returned as a 24-character lowercase hex string. A 24-char hex value in a qual is coerced back to `ObjectId` for the filter. | | ||
| | DateTime | timestamp / timestamptz | | | ||
| | Document | jsonb | | | ||
| | Array | jsonb | | | ||
| | Binary | bytea | | | ||
| | Null / missing | any | Column is set to `NULL` | | ||
|
|
||
| !!! note | ||
|
|
||
| `_id` fields that are `ObjectId` values are returned as their 24-character hex representation. When querying by `_id` with a 24-character hex string, the value is automatically coerced back to an `ObjectId` for the filter — no explicit casting is required. | ||
|
|
||
| ## Writes | ||
|
|
||
| INSERT, UPDATE, and DELETE are enabled when `rowid_column` is set on the foreign table. | ||
|
|
||
| - **INSERT**: Each non-null column is written as a top-level BSON field. Null columns are omitted entirely (not stored as explicit BSON nulls). If `_id` is not supplied, MongoDB generates an `ObjectId` automatically. | ||
| - **UPDATE**: Non-null columns are passed to `$set`; null columns are passed to `$unset`, which removes the field from the document. The document is located by matching `rowid_column`. | ||
| - **DELETE**: The document matching `rowid_column = rowid` is deleted via `delete_one`. | ||
|
|
||
| ## Limitations | ||
|
|
||
| This section describes important limitations and considerations when using this FDW: | ||
|
|
||
| - Aggregate pushdown is not supported — `COUNT`, `SUM`, `AVG`, `MIN`, and `MAX` are computed in Postgres after fetching rows | ||
| - `import foreign schema` is not supported; foreign table definitions must be declared manually | ||
| - `LIKE` predicates are not pushed down to MongoDB `$regex`; pattern matching happens in Postgres | ||
| - Nested field path access (e.g., `address.city`) is not supported as column names — use the `_doc jsonb` column and Postgres JSON operators instead | ||
| - Multi-document transactions and batched writes are not supported in v1 | ||
| - Change streams are not supported | ||
| - Materialized views using foreign tables may fail during logical backups | ||
|
|
||
| ## Examples | ||
|
|
||
| ### Basic example | ||
|
|
||
| This example shows how to query a MongoDB collection from Postgres. | ||
|
|
||
| ```sql | ||
| -- Create the server | ||
| create server mongo_server | ||
| foreign data wrapper mongodb_wrapper | ||
| options ( | ||
| conn_string 'mongodb://localhost:27017/' | ||
| ); | ||
|
|
||
| -- Create the foreign table | ||
| create foreign table mongo.users ( | ||
| _id text, | ||
| name text, | ||
| age int, | ||
| created_at timestamp, | ||
| _doc jsonb | ||
| ) | ||
| server mongo_server | ||
| options ( | ||
| database 'app', | ||
| collection 'users' | ||
| ); | ||
|
|
||
| -- Query all users | ||
| select _id, name, age from mongo.users; | ||
|
|
||
| -- Filter with pushdown | ||
| select name from mongo.users where age > 25; | ||
|
|
||
| -- Access nested fields via _doc | ||
| select _doc->>'email', _doc->'address'->>'city' | ||
| from mongo.users | ||
| where name = 'Alice'; | ||
| ``` | ||
|
|
||
| ### Data modification example | ||
|
|
||
| This example demonstrates INSERT, UPDATE, and DELETE on a foreign table. The `rowid_column` option is required for data modification: | ||
|
|
||
| ```sql | ||
| create foreign table mongo.users ( | ||
| _id text, | ||
| name text, | ||
| age int, | ||
| _doc jsonb | ||
| ) | ||
| server mongo_server | ||
| options ( | ||
| database 'app', | ||
| collection 'users', | ||
| rowid_column '_id' | ||
| ); | ||
|
|
||
| -- Insert a new document (_id is generated by MongoDB if omitted) | ||
| insert into mongo.users (name, age) | ||
| values ('Alice', 30); | ||
|
|
||
| -- Update a document (null columns remove the field via $unset) | ||
| update mongo.users | ||
| set age = 31 | ||
| where _id = '507f1f77bcf86cd799439011'; | ||
|
|
||
| -- Remove a field by setting it to null | ||
| update mongo.users | ||
| set age = null | ||
| where _id = '507f1f77bcf86cd799439011'; | ||
|
|
||
| -- Delete a document | ||
| delete from mongo.users | ||
| where _id = '507f1f77bcf86cd799439011'; | ||
| ``` | ||
|
|
||
| ### Using Vault for credentials | ||
|
|
||
| ```sql | ||
| -- Store the connection string in Vault | ||
| select vault.create_secret( | ||
| 'mongodb+srv://user:password@cluster.example.mongodb.net/', | ||
| 'mongodb_atlas', | ||
| 'MongoDB Atlas connection string' | ||
| ); | ||
|
|
||
| -- Create the server using the Vault secret ID | ||
| create server mongo_atlas_server | ||
| foreign data wrapper mongodb_wrapper | ||
| options ( | ||
| conn_string_id '<key_ID>' | ||
| ); | ||
|
|
||
| create foreign table mongo.products ( | ||
| _id text, | ||
| name text, | ||
| price numeric, | ||
| in_stock bool | ||
| ) | ||
| server mongo_atlas_server | ||
| options ( | ||
| database 'shop', | ||
| collection 'products', | ||
| rowid_column '_id' | ||
| ); | ||
|
|
||
| -- Query with multiple pushed-down predicates | ||
| select name, price | ||
| from mongo.products | ||
| where in_stock = true | ||
| and price < 100 | ||
| order by price | ||
| limit 10; | ||
| ``` | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.