From 4762b5f79f3077a7f35e1185af75159c6e75fbf0 Mon Sep 17 00:00:00 2001 From: "alessio.vigilante" Date: Mon, 12 Oct 2026 19:40:44 +0200 Subject: [PATCH 1/2] docs(databases): add the Firebase page for Firestore and Realtime Database Co-Authored-By: Claude Opus 5.5 (1M context) --- connections/connection-form.mdx | 1 + connections/ssl.mdx | 2 +- databases/firebase.mdx | 178 ++++++++++++++++++++++++++++++++ databases/index.mdx | 6 +- docs.json | 1 + index.mdx | 2 +- scripts/proper-nouns.txt | 3 + snippets/driver-counts.mdx | 4 +- 8 files changed, 191 insertions(+), 6 deletions(-) create mode 100644 databases/firebase.mdx diff --git a/connections/connection-form.mdx b/connections/connection-form.mdx index 9a564e93..2dabc147 100644 --- a/connections/connection-form.mdx +++ b/connections/connection-form.mdx @@ -172,6 +172,7 @@ Metadata connections are the extra ones opened to read a database's object list | [DynamoDB](/databases/dynamodb) | AWS API | No | No | No | No | No | No | | [BigQuery](/databases/bigquery) | Cloud API | No | No | No | No | No | No | | [Spanner](/databases/spanner) | Cloud API | No | No | No | No | No | No | +| [Firestore and Firebase Realtime Database](/databases/firebase) | Cloud API | No | No | No | No | No | No | | [Cloudflare D1](/databases/cloudflare-d1) | Cloud API | No | No | No | No | No | No | | [Cloudflare R2 SQL](/databases/cloudflare-r2-sql) | Cloud API | No | No | No | No | No | No | | [libSQL / Turso](/databases/libsql) | URL | No | No | No | No | No | No | diff --git a/connections/ssl.mdx b/connections/ssl.mdx index 06f7c093..58edea42 100644 --- a/connections/ssl.mdx +++ b/connections/ssl.mdx @@ -48,7 +48,7 @@ A new connection starts on the mode that matches the driver's own default, and s | Trino | Disabled | Sends every request over HTTPS with no fallback, again like Required | | SAP HANA | Verify Identity | Nothing. No fallback exists, so Preferred forces TLS exactly like Required | | Oracle | Disabled | Connects in plain TCP, so it behaves like Disabled. A red warning appears under the picker; use Required to enforce TCPS | -| Snowflake, BigQuery, Spanner, DynamoDB, Cloudflare D1, Cloudflare R2 SQL, libSQL / Turso | Always encrypted | These drivers are HTTPS and manage TLS themselves. No **SSL Mode** | +| Snowflake, BigQuery, Spanner, Firestore, Firebase Realtime Database, DynamoDB, Cloudflare D1, Cloudflare R2 SQL, libSQL / Turso | Always encrypted | These drivers are HTTPS and manage TLS themselves. No **SSL Mode** | | SQLite, DuckDB, Beancount, PGlite | None | Local files or an in-process engine. No **SSL Mode** | ## Ports that mean TLS diff --git a/databases/firebase.mdx b/databases/firebase.mdx new file mode 100644 index 00000000..681ae02c --- /dev/null +++ b/databases/firebase.mdx @@ -0,0 +1,178 @@ +--- +title: Firebase +description: Connect to Cloud Firestore and the Firebase Realtime Database, browse collections and nodes, and edit documents +--- + +import RegistryPlugin from "/snippets/registry-plugin.mdx"; + +One plugin serves two connection types. **Cloud Firestore** shows collections as tables and documents as rows. **Firebase Realtime Database** shows the children of a node as rows. Both talk to the REST APIs, and neither has SQL: the editor takes request lines such as `GET users/alice`. + + + +## Quick setup + +Click **New Connection…**, select **Cloud Firestore** or **Firebase Realtime Database**, pick an **Auth Method**, then click **Save & Connect**. + +With a service account key, **Project ID** can stay empty: the project comes from the key. For the Realtime Database, **Database URL** can stay empty too. The plugin asks Firebase for the project's instances and opens the default one. + +## Connection settings + +| Field | Description | +|-------|-------------| +| **Auth Method** | Service Account Key, Application Default Credentials, or Emulator | +| **Service Account Key** | A path to the JSON key file, or the JSON pasted in. Stored in the Keychain | +| **Project ID** | The Google Cloud project. Taken from the key or from Application Default Credentials when empty | +| **Database** | Firestore only. `(default)` when empty. Other databases in the project are in the database switcher | +| **Database URL** | Realtime Database only, such as `https://..firebasedatabase.app`. Must be a `firebaseio.com` or `firebasedatabase.app` address | +| **Emulator Host** | Shown for the Emulator method, as `host:port` | + +## Authentication + +| Auth Method | What is sent | +|-------------|--------------| +| **Service Account Key** | An OAuth token signed with the key. The account needs a role that reads the data, such as Cloud Datastore Viewer for Firestore or Firebase Realtime Database Viewer | +| **Application Default Credentials** | The token from `gcloud auth application-default login`, or from the file `GOOGLE_APPLICATION_CREDENTIALS` names | +| **Emulator** | The token `owner`, which the Firebase emulators accept as an administrator | + +A service account and a signed-in Google account both act as administrators to the Realtime Database, so its security rules do not limit them. When IAM or the rules refuse an operation, the error says **Permission denied** and names the operation, rather than showing an empty table. + +## Cloud Firestore + +### Browsing collections + +The sidebar lists the root collections. Subcollections are found under the first documents of each root collection and listed as collection groups, such as `*/orders`, which reads every `orders` collection at any depth. + +Every grid has `__name__` first: the document ID, or the full path in a collection group. The other columns are the fields the documents on the page hold. A field a document does not have shows as missing, not as NULL. Maps and arrays show as JSON. + +### Filtering and sorting + +Filters and sorts become a structured query. A filter value is read by the type the field holds, so `age = 30` matches the integer 30 and `zip = 02134` matches the string when `zip` is a string field. + +| Operators | What Firestore runs | +|----------|--------------------| +| equals, not equals | `EQUAL`, `NOT_EQUAL` | +| greater than, greater or equal, less than, less or equal | Range filters | +| in list, not in list | `IN`, `NOT_IN`, from 1 to 30 values | +| between | Two bounds, both inclusive | +| starts with | A range from the prefix up to the prefix followed by `` | +| contains | `ARRAY_CONTAINS`, on an array field only | +| is NULL, is not NULL | `IS_NULL`, `IS_NOT_NULL` | + +Firestore has no text search, so **contains** on a string field, **ends with**, the empty checks and **REGEX** report why instead of running. The raw filter takes a `Filter` object from the Firestore REST API, such as `{"fieldFilter": {"field": {"fieldPath": "address.city"}, "op": "EQUAL", "value": {"stringValue": "Milan"}}}`, which reaches nested fields. A filter that needs a composite index returns Firestore's error with the link that creates it. + +### Editing + +Grid edits are REST calls. An edited cell keeps the type the field had: a string stays a string even when the new text is a number, and a timestamp takes RFC 3339 text such as `2026-10-12T08:00:00Z`. + +| Change | Request | +|--------|---------| +| New row | `POST users`, with `documentId` when `__name__` is set | +| Edited cells | `PATCH users/alice?updateMask=age`, only the fields you changed | +| Removed field | The field in `updateMask` and not in the body | +| Deleted row | `DELETE users/alice` | + +**Edit Document** opens the whole document as JSON. Types JSON lacks are written as one-key objects: + +```json +{ + "__name__": "users/alice", + "age": 31, + "since": {"$timestamp": "2026-10-12T08:00:00Z"}, + "owner": {"$ref": "teams/core"}, + "home": {"$geo": {"latitude": 45.46, "longitude": 9.19}}, + "avatar": {"$bytes": "aGk="}, + "ratio": {"$double": 2} +} +``` + +Saving checks that the stored document still matches what you opened, and writes with its update time as a precondition. If someone changed it in between, the save is refused. + +### Request editor + +| Request | What it does | +|---------|--------------| +| `GET users` | The first 100 documents of a collection. `?pageSize=` sets another number | +| `GET users/alice` | One document | +| `GET /` | The root collection IDs. `GET users/alice?collections` lists a document's subcollections | +| `QUERY {"structuredQuery": {...}}` | A structured query from the REST reference. `QUERY users/alice {...}` runs it under a document | +| `POST users {"name": "Bob"}` | Creates a document with a generated ID | +| `PATCH users/alice?updateMask=age {"age": 31}` | Changes the named fields. Without `updateMask` the body replaces the document | +| `DELETE users/alice` | Deletes a document | + +Bodies use the same JSON as **Edit Document**. + +## Realtime Database + +### Browsing nodes + +The sidebar lists `/`, the root, and the root's children when there are 500 or fewer. Open `/` for a root with more. + +Every grid has `$key` first. Children that are objects add their fields as columns; children that are plain values go in `$value`. Nested objects show as JSON. The database switcher lists the project's instances. + +### Filtering and sorting + +The REST API filters on one field at a time and has no offset, so the plugin asks for the first offset plus limit children in order and drops the offset. + +| Operators | What the Realtime Database runs | +|----------|--------------------| +| equals | `equalTo` | +| greater than, greater or equal | `startAfter`, `startAt` | +| less than, less or equal | `endBefore`, `endAt` | +| between | `startAt` and `endAt` | +| starts with | `startAt` and `endAt` with `` | +| is NULL | `equalTo=null` | + +Filter or sort on `$key`, `$value`, or a child field. A second filter, an OR, or a sort on another column than the filter is refused with the reason. Ordering by a child field needs `.indexOn` in the rules, and Firebase's own error names the rule to add. The raw filter takes a query string as written, such as `orderBy="age"&startAt=18`. + +### Editing + +| Change | Request | +|--------|---------| +| New row | `PUT` under the key you typed, or `POST` for a generated key | +| Edited cells | `PATCH` on the child with the changed fields | +| Edited `$value` | `PUT` on the child | +| Removed field | The field set to `null` in the `PATCH` | +| Deleted row | `DELETE` on the child | + +**Edit Document** opens a child as `{"$key": "", "$value": }`. Saving sends the node's ETag with `if-match`, so a node that changed after you opened it is not overwritten. + +### Request editor + +```http +GET /chats?orderBy="$key"&limitToFirst=20 +``` + +`GET`, `PUT`, `PATCH`, `POST` and `DELETE` take a path and, for writes, a JSON body on the same line or the lines under it. Query parameters are passed through. `DELETE /` is refused. + +## The emulators + +Pick **Emulator**, enter the project the emulators run, and set **Emulator Host** to the Firestore or Database emulator, by default `127.0.0.1:8080` and `127.0.0.1:9000`. + +## Limitations + +- No real-time listeners. Grids show what was read and refresh on request. +- Firestore paging uses `offset`, which Firestore bills as one read per skipped document. Export pages with a cursor instead. +- Realtime Database paging downloads the skipped children, so late pages of a large node are slow. +- Export fixes its columns from the first page. Fields that appear only later go into an `__extra__` column as JSON. +- Datastore-mode databases are not listed. Use a Firestore Native database. +- Collection groups come from the first two documents of up to 30 root collections. Open any other path from the request editor. +- The plugin is registry-only. It is not on iPhone or iPad. + +## Troubleshooting + +### Permission denied: … + +The credential's IAM role does not cover the operation, or a security rule blocks it. The message names what was attempted. + +### Database lives in a different region + +The Database URL points at the wrong host. Leave it empty and the plugin finds the instance, or copy the URL from the Firebase console. + +### No project to connect to + +The error reads "Enter a Project ID, or use a service account key that names one". Application Default Credentials from a user login name no project. Fill in **Project ID**. + +## Related + +- [Filtering](/features/filtering) +- [Import & Export](/features/import-export) diff --git a/databases/index.mdx b/databases/index.mdx index 3e87e524..222b1484 100644 --- a/databases/index.mdx +++ b/databases/index.mdx @@ -1,11 +1,11 @@ --- title: Supported Databases -description: All 37 engines TablePro connects to, their default ports, and which ones need a plugin +description: All 39 engines TablePro connects to, their default ports, and which ones need a plugin --- import DriverCounts from "/snippets/driver-counts.mdx"; -Thirty-seven engines, and every one of them is free to use. What differs between them is where the +Thirty-nine engines, and every one of them is free to use. What differs between them is where the driver comes from, not what the license covers. @@ -28,6 +28,8 @@ driver comes from, not what the license covers. | [DynamoDB](/databases/dynamodb) | API-based | Plugin | | [Elasticsearch](/databases/elasticsearch) | 9200 | Plugin | | [etcd](/databases/etcd) | 2379 | Plugin | +| [Firebase Realtime Database](/databases/firebase) | API-based | Plugin | +| [Firestore](/databases/firebase) | API-based | Plugin | | [libSQL](/databases/libsql) | API-based | Plugin | | [Kafka](/databases/kafka) | 9092 | Plugin | | [MariaDB](/databases/mariadb) | 3306 | Built-in | diff --git a/docs.json b/docs.json index 564b071a..11970a68 100644 --- a/docs.json +++ b/docs.json @@ -246,6 +246,7 @@ "databases/duckdb", "databases/elasticsearch", "databases/etcd", + "databases/firebase", "databases/bigquery", "databases/kafka", "databases/libsql", diff --git a/index.mdx b/index.mdx index a6d6a859..f4260076 100644 --- a/index.mdx +++ b/index.mdx @@ -1,6 +1,6 @@ --- title: Introduction -description: Native macOS database client for MySQL, PostgreSQL, SQLite, MongoDB, Redis, and 32 more +description: Native macOS database client for MySQL, PostgreSQL, SQLite, MongoDB, Redis, and 34 more --- import DriverCounts from "/snippets/driver-counts.mdx"; diff --git a/scripts/proper-nouns.txt b/scripts/proper-nouns.txt index 5536f4c3..e749715b 100644 --- a/scripts/proper-nouns.txt +++ b/scripts/proper-nouns.txt @@ -13,9 +13,11 @@ Beancount Cloud Code Connector +Database Drive Entra Finder +Firestore Homebrew Insights Instant @@ -30,6 +32,7 @@ Microsoft Mode Proxy Quack +Realtime Safe Shortcuts Studio diff --git a/snippets/driver-counts.mdx b/snippets/driver-counts.mdx index c7a6c8bf..f399ca53 100644 --- a/snippets/driver-counts.mdx +++ b/snippets/driver-counts.mdx @@ -1,3 +1,3 @@ -Five drivers ship inside the app and cover twelve databases. Twenty-three registry plugins cover the -other twenty-five and install on the first connection that needs one. See +Five drivers ship inside the app and cover twelve databases. Twenty-four registry plugins cover the +other twenty-seven and install on the first connection that needs one. See [Plugins](/features/plugins). From 66d7ff559b2950b3601d7046e23107a2fbce1489 Mon Sep 17 00:00:00 2001 From: Ngo Quoc Dat Date: Tue, 13 Oct 2026 15:12:13 +0700 Subject: [PATCH 2/2] docs(databases): match the Firebase page to the merged driver --- databases/firebase.mdx | 152 ++++++++++++++++++++++----------- features/change-tracking.mdx | 2 +- features/copy-objects.mdx | 3 +- features/data-grid.mdx | 2 +- features/filtering.mdx | 2 +- features/plugins.mdx | 1 + features/routines-triggers.mdx | 2 +- features/table-operations.mdx | 2 +- features/table-structure.mdx | 2 +- 9 files changed, 112 insertions(+), 56 deletions(-) diff --git a/databases/firebase.mdx b/databases/firebase.mdx index 681ae02c..72ccd86a 100644 --- a/databases/firebase.mdx +++ b/databases/firebase.mdx @@ -5,7 +5,7 @@ description: Connect to Cloud Firestore and the Firebase Realtime Database, brow import RegistryPlugin from "/snippets/registry-plugin.mdx"; -One plugin serves two connection types. **Cloud Firestore** shows collections as tables and documents as rows. **Firebase Realtime Database** shows the children of a node as rows. Both talk to the REST APIs, and neither has SQL: the editor takes request lines such as `GET users/alice`. +One plugin serves two connection types. In **Cloud Firestore** a collection is a table and a document is a row. In **Firebase Realtime Database** the children of a node are the rows. Neither has SQL: the editor takes REST request lines such as `GET users/alice`. @@ -13,64 +13,78 @@ One plugin serves two connection types. **Cloud Firestore** shows collections as Click **New Connection…**, select **Cloud Firestore** or **Firebase Realtime Database**, pick an **Auth Method**, then click **Save & Connect**. -With a service account key, **Project ID** can stay empty: the project comes from the key. For the Realtime Database, **Database URL** can stay empty too. The plugin asks Firebase for the project's instances and opens the default one. +With a service account key, leave **Project ID** empty: the key names the project. Leave **Database URL** empty as well, and the Realtime Database connection opens the project's default instance. ## Connection settings -| Field | Description | -|-------|-------------| -| **Auth Method** | Service Account Key, Application Default Credentials, or Emulator | -| **Service Account Key** | A path to the JSON key file, or the JSON pasted in. Stored in the Keychain | -| **Project ID** | The Google Cloud project. Taken from the key or from Application Default Credentials when empty | -| **Database** | Firestore only. `(default)` when empty. Other databases in the project are in the database switcher | -| **Database URL** | Realtime Database only, such as `https://..firebasedatabase.app`. Must be a `firebaseio.com` or `firebasedatabase.app` address | -| **Emulator Host** | Shown for the Emulator method, as `host:port` | +| Field | Shown for | Notes | +|-------|-----------|-------| +| **Auth Method** | Both | **Service Account Key** (the default), **Application Default Credentials**, or **Emulator (no credentials)** | +| **Service Account Key** | Service Account Key | A path to the JSON key file, or the key JSON itself. Stored in the Keychain | +| **Project ID** | Both | Empty takes the project from the credentials | +| **Database** | Cloud Firestore | Empty means `(default)`. The database switcher lists the project's other Firestore Native databases | +| **Database URL** | Realtime Database | `https://.firebaseio.com` or `https://..firebasedatabase.app`. Empty finds the project's default instance, and the database switcher lists the others | +| **Emulator Host** | Emulator | `host:port` of the emulator | + +Both are managed services, so any version works, and there is no host, port or SSH tunnel to set. + +## Connection URL + +There is no URL scheme for Firebase. Create the connection from the form. ## Authentication +Pick **Service Account Key** and generate a key in the Firebase console under Project settings, Service accounts. On a Mac where `gcloud` is already signed in, **Application Default Credentials** needs no file. + | Auth Method | What is sent | |-------------|--------------| -| **Service Account Key** | An OAuth token signed with the key. The account needs a role that reads the data, such as Cloud Datastore Viewer for Firestore or Firebase Realtime Database Viewer | -| **Application Default Credentials** | The token from `gcloud auth application-default login`, or from the file `GOOGLE_APPLICATION_CREDENTIALS` names | -| **Emulator** | The token `owner`, which the Firebase emulators accept as an administrator | +| **Service Account Key** | An OAuth token signed with the key | +| **Application Default Credentials** | The token from `gcloud auth application-default login`, or from the key file `GOOGLE_APPLICATION_CREDENTIALS` names. An empty **Project ID** falls back to the project in that file, then to `GOOGLE_CLOUD_PROJECT` | +| **Emulator (no credentials)** | The token `owner`, which the Firebase emulators treat as an administrator | -A service account and a signed-in Google account both act as administrators to the Realtime Database, so its security rules do not limit them. When IAM or the rules refuse an operation, the error says **Permission denied** and names the operation, rather than showing an empty table. +Access comes from the account's IAM roles, not from security rules. Cloud Datastore Viewer reads Firestore and Cloud Datastore User also writes it. Firebase Realtime Database Viewer reads the Realtime Database and Firebase Realtime Database Admin also writes it. ## Cloud Firestore ### Browsing collections -The sidebar lists the root collections. Subcollections are found under the first documents of each root collection and listed as collection groups, such as `*/orders`, which reads every `orders` collection at any depth. +The sidebar lists the root collections, then their subcollections as collection groups such as `*/orders`, which reads every `orders` collection at any depth. -Every grid has `__name__` first: the document ID, or the full path in a collection group. The other columns are the fields the documents on the page hold. A field a document does not have shows as missing, not as NULL. Maps and arrays show as JSON. +Every grid has `__name__` first: the document ID, or the full path in a collection group. The other columns are the fields the documents on the page hold, in alphabetical order. A field a document lacks shows as **No Field**, not NULL. Maps and arrays show as JSON, a geopoint as `latitude, longitude`, and a reference as the path it points to. ### Filtering and sorting -Filters and sorts become a structured query. A filter value is read by the type the field holds, so `age = 30` matches the integer 30 and `zip = 02134` matches the string when `zip` is a string field. +Filters and sorts run as one structured query. A filter value takes the type the field holds, so `age = 30` matches the integer 30, and `zip = 02134` matches the string when `zip` holds strings. | Operators | What Firestore runs | -|----------|--------------------| +|-----------|---------------------| | equals, not equals | `EQUAL`, `NOT_EQUAL` | | greater than, greater or equal, less than, less or equal | Range filters | -| in list, not in list | `IN`, `NOT_IN`, from 1 to 30 values | +| in list, not in list | `IN`, `NOT_IN`, with 1 to 30 values | | between | Two bounds, both inclusive | -| starts with | A range from the prefix up to the prefix followed by `` | +| starts with | A range from the prefix to the prefix followed by `U+F8FF` | | contains | `ARRAY_CONTAINS`, on an array field only | | is NULL, is not NULL | `IS_NULL`, `IS_NOT_NULL` | -Firestore has no text search, so **contains** on a string field, **ends with**, the empty checks and **REGEX** report why instead of running. The raw filter takes a `Filter` object from the Firestore REST API, such as `{"fieldFilter": {"field": {"fieldPath": "address.city"}, "op": "EQUAL", "value": {"stringValue": "Milan"}}}`, which reaches nested fields. A filter that needs a composite index returns Firestore's error with the link that creates it. +**contains** and **not contains** on a string field, **ends with**, the empty checks and **REGEX** are refused with the reason, since Firestore has no text search. Matching is always case-sensitive. **Match any** joins the rows with OR. + +A raw filter row takes one `Filter` object from the Firestore REST API, which reaches nested fields: `{"fieldFilter": {"field": {"fieldPath": "address.city"}, "op": "EQUAL", "value": {"stringValue": "Milan"}}}`. + +A sort on a field leaves out the documents that do not have it, and the row count leaves them out too. ### Editing -Grid edits are REST calls. An edited cell keeps the type the field had: a string stays a string even when the new text is a number, and a timestamp takes RFC 3339 text such as `2026-10-12T08:00:00Z`. +Each saved change is one request: | Change | Request | |--------|---------| -| New row | `POST users`, with `documentId` when `__name__` is set | -| Edited cells | `PATCH users/alice?updateMask=age`, only the fields you changed | +| New row | `POST users?documentId=alice`. With `__name__` empty, Firestore generates the ID. In a collection group, `__name__` is the full path, such as `users/alice/orders/o1` | +| Edited cells | `PATCH users/alice?updateMask=age`, with only the fields you changed. It fails on a document deleted in the meantime instead of recreating it | | Removed field | The field in `updateMask` and not in the body | | Deleted row | `DELETE users/alice` | +An edited cell keeps the type the field had: a string stays a string even when the new text is a number. A cell that held null, or no field, takes the type the column holds on other rows. Timestamps take RFC 3339 text such as `2026-10-12T08:00:00Z`, geopoints `45.46, 9.19`, bytes base64, and references a document path. + **Edit Document** opens the whole document as JSON. Types JSON lacks are written as one-key objects: ```json @@ -85,21 +99,24 @@ Grid edits are REST calls. An edited cell keeps the type the field had: a string } ``` -Saving checks that the stored document still matches what you opened, and writes with its update time as a precondition. If someone changed it in between, the save is refused. +`$double` also takes `"NaN"`, `"Infinity"` and `"-Infinity"`. A map whose only key is one of these tags goes inside `$map`. **Insert Document** takes the same JSON, with `__name__` as the ID or left out for a generated one. + +Saving reads the document again and stops if it changed after you opened it, so another writer's change is never overwritten. ### Request editor | Request | What it does | |---------|--------------| | `GET users` | The first 100 documents of a collection. `?pageSize=` sets another number | +| `GET */orders` | Every `orders` collection at any depth | | `GET users/alice` | One document | | `GET /` | The root collection IDs. `GET users/alice?collections` lists a document's subcollections | -| `QUERY {"structuredQuery": {...}}` | A structured query from the REST reference. `QUERY users/alice {...}` runs it under a document | -| `POST users {"name": "Bob"}` | Creates a document with a generated ID | -| `PATCH users/alice?updateMask=age {"age": 31}` | Changes the named fields. Without `updateMask` the body replaces the document | +| `QUERY {"structuredQuery": {...}}` | A structured query from the REST reference, with REST values such as `{"integerValue": "30"}`. `QUERY users/alice {...}` runs it under a document | +| `POST users {"name": "Bob"}` | Creates a document with a generated ID. `POST users?documentId=bob` names it | +| `PATCH users/alice?updateMask=age {"age": 31}` | Changes the named fields. Without `updateMask` the body replaces the document. Never creates one | | `DELETE users/alice` | Deletes a document | -Bodies use the same JSON as **Edit Document**. +`POST` and `PATCH` bodies use the **Edit Document** JSON, on the same line or the lines under it. There is no `PUT`. Write a space or any of `% ? # & = /` inside an ID as its percent escape, such as `GET users/John%20Doe`. `DELETE` raises the dangerous query warning even on **Silent**. Other writes follow the connection's [Safe Mode](/features/safe-mode) level. ## Realtime Database @@ -107,34 +124,40 @@ Bodies use the same JSON as **Edit Document**. The sidebar lists `/`, the root, and the root's children when there are 500 or fewer. Open `/` for a root with more. -Every grid has `$key` first. Children that are objects add their fields as columns; children that are plain values go in `$value`. Nested objects show as JSON. The database switcher lists the project's instances. +Every grid has `$key` first. Children that are objects add their fields as columns, and children that are plain values go in `$value`. Nested objects show as JSON. A node holding one plain value, such as `/maintenance`, shows one row with that path as its `$key`, and editing it writes the node itself. ### Filtering and sorting -The REST API filters on one field at a time and has no offset, so the plugin asks for the first offset plus limit children in order and drops the offset. +Filter on one column, `$key`, `$value` or a child field, and sort only by that column. The filter bar offers **Match all** only. | Operators | What the Realtime Database runs | -|----------|--------------------| +|-----------|---------------------------------| | equals | `equalTo` | | greater than, greater or equal | `startAfter`, `startAt` | | less than, less or equal | `endBefore`, `endAt` | | between | `startAt` and `endAt` | -| starts with | `startAt` and `endAt` with `` | +| starts with | `startAt` and `endAt` with `U+F8FF` | | is NULL | `equalTo=null` | -Filter or sort on `$key`, `$value`, or a child field. A second filter, an OR, or a sort on another column than the filter is refused with the reason. Ordering by a child field needs `.indexOn` in the rules, and Firebase's own error names the rule to add. The raw filter takes a query string as written, such as `orderBy="age"&startAt=18`. +Other operators are refused with the reason. Ordering by a child field needs `.indexOn` for it in the database rules. + +A raw filter row takes a query string as written, such as `orderBy="age"&startAt=18`. When it names no limit, `limitToFirst` is added for the page, and `orderBy="$key"` too when it names no order. + +Each page asks for every child up to the end of that page and drops the ones before it. Counting lists every key, so a grid shows no total until you click **Count Exactly**, and a filtered grid shows none at all. ### Editing | Change | Request | |--------|---------| -| New row | `PUT` under the key you typed, or `POST` for a generated key | +| New row | `PUT` under the `$key` you typed, replacing any child already there, or `POST` for a generated key | | Edited cells | `PATCH` on the child with the changed fields | | Edited `$value` | `PUT` on the child | | Removed field | The field set to `null` in the `PATCH` | | Deleted row | `DELETE` on the child | -**Edit Document** opens a child as `{"$key": "", "$value": }`. Saving sends the node's ETag with `if-match`, so a node that changed after you opened it is not overwritten. +A key cannot be empty or contain `.`, `#`, `$`, `[`, `]` or `/`. + +**Edit Document** opens a child as `{"$key": "", "$value": }`. Saving reads the node again and writes only if it still matches what you opened. **Insert Document** takes the same shape, or bare JSON for a child under a generated key. ### Request editor @@ -142,37 +165,68 @@ Filter or sort on `$key`, `$value`, or a child field. A second filter, an OR, or GET /chats?orderBy="$key"&limitToFirst=20 ``` -`GET`, `PUT`, `PATCH`, `POST` and `DELETE` take a path and, for writes, a JSON body on the same line or the lines under it. Query parameters are passed through. `DELETE /` is refused. +`GET`, `PUT`, `PATCH`, `POST` and `DELETE` take a path and, for writes, a JSON body on the same line or the lines under it. Query parameters apply to `GET` only, and a `PATCH` body is an object of the children to change. `PUT /` and `DELETE /` are refused: write or delete the root's children one at a time. + +A `DELETE`, a `PUT` that leaves the node empty and a `PATCH` that sets a child to `null` raise the dangerous query warning even on **Silent**. Other writes follow the connection's [Safe Mode](/features/safe-mode) level. + +## Emulators -## The emulators +Pick **Emulator (no credentials)**, enter the emulators' project as **Project ID**, and set **Emulator Host** to the Firestore or Database emulator. Unless `firebase.json` moves them, they listen on `127.0.0.1:8080` and `127.0.0.1:9000`. -Pick **Emulator**, enter the project the emulators run, and set **Emulator Host** to the Firestore or Database emulator, by default `127.0.0.1:8080` and `127.0.0.1:9000`. +The Realtime Database connection opens the `-default-rtdb` namespace. For another, set **Database URL** to `http://127.0.0.1:9000/?ns=`. + +## SSL/TLS + +Requests to Google are always HTTPS, and there is no **SSL Mode**. The emulators are plain HTTP. ## Limitations -- No real-time listeners. Grids show what was read and refresh on request. -- Firestore paging uses `offset`, which Firestore bills as one read per skipped document. Export pages with a cursor instead. -- Realtime Database paging downloads the skipped children, so late pages of a large node are slow. +- No real-time listeners. A grid shows what was read; refresh it to see later changes. +- Firestore pages with `offset`, which Firestore bills as one read per skipped document. Filter to reach late rows rather than paging to them. Export reads with a cursor and skips nothing. +- Realtime Database pages download every child before the page, so late pages of a large node are slow. Filter on `$key` to start further in. - Export fixes its columns from the first page. Fields that appear only later go into an `__extra__` column as JSON. +- Import is not available. Write documents from the request editor. +- The Structure tab lists the fields of a 50-document sample and is read-only. A field few documents hold can be missing from it. - Datastore-mode databases are not listed. Use a Firestore Native database. -- Collection groups come from the first two documents of up to 30 root collections. Open any other path from the request editor. -- The plugin is registry-only. It is not on iPhone or iPad. +- Collection groups come from the first two documents of up to 30 root collections, one level down, and the list is kept for 10 minutes. Read any other path from the request editor, such as `GET users/alice/orders`. +- Not available on iPhone or iPad. ## Troubleshooting ### Permission denied: … -The credential's IAM role does not cover the operation, or a security rule blocks it. The message names what was attempted. +The account lacks the IAM role for the operation named after the colon. Grant one of the roles under [Authentication](#authentication), or connect with an account that has it. + +### Enter a Project ID, or use a service account key that names one. + +A Firestore connection found no project: the Application Default Credentials file names none and `GOOGLE_CLOUD_PROJECT` is not set. Fill in **Project ID**. + +### Enter a Database URL or a Project ID. + +A Realtime Database connection needs one of the two to find the instance. Fill in either. + +### "…" is not a Realtime Database URL. + +**Database URL** is not a Firebase address. Copy it from the Realtime Database page of the Firebase console, or clear it to use the default instance. + +### The query requires an index. … + +Firestore needs a composite index for this combination of filters and sort. Open the link, create the index, and run the filter again once the index has built. + +### Index not defined, add ".indexOn": "…", for path "…", to the rules + +The Realtime Database orders by a child field only with an index. Add the rule the message names in the Firebase console, under Realtime Database, Rules. -### Database lives in a different region +### The document changed after it was opened. Reopen it and edit again. -The Database URL points at the wrong host. Leave it empty and the plugin finds the instance, or copy the URL from the Firebase console. +Another writer saved the document between **Edit Document** opening it and your save. Open it again and redo the change. -### No project to connect to +### The node changed after it was opened. Reopen it and edit again. -The error reads "Enter a Project ID, or use a service account key that names one". Application Default Credentials from a user login name no project. Fill in **Project ID**. +The Realtime Database node was written after **Edit Document** read it. Open it again and redo the change. ## Related - [Filtering](/features/filtering) +- [Safe Mode](/features/safe-mode) - [Import & Export](/features/import-export) diff --git a/features/change-tracking.mdx b/features/change-tracking.mdx index f5354b18..7d161658 100644 --- a/features/change-tracking.mdx +++ b/features/change-tracking.mdx @@ -38,7 +38,7 @@ Double-click a cell, or press `Enter` on it. `Enter` commits the edit to the que Right-click an editable cell and open **Set Value** for the common ones without typing: **Empty**, **NULL** on a nullable column, **Default** on a column the server fills in, and date functions such as `NOW()` or `CURRENT_DATE` on date columns. -On MongoDB a document can lack a field altogether, which the grid shows as **No Field**. **Remove Field** on the same right-click menu deletes the field rather than storing NULL. See [missing fields and null](/databases/mongodb#missing-fields-and-null). +On MongoDB, Firestore and the Firebase Realtime Database a document can lack a field altogether, which the grid shows as **No Field**. **Remove Field** on the same right-click menu deletes the field rather than storing NULL. See [missing fields and null](/databases/mongodb#missing-fields-and-null). ## Adding and deleting rows diff --git a/features/copy-objects.mdx b/features/copy-objects.mdx index c52ce8a9..beb1c3d8 100644 --- a/features/copy-objects.mdx +++ b/features/copy-objects.mdx @@ -255,7 +255,8 @@ have none, so the command does not appear on them. ## Limitations -Copying is SQL only. MongoDB, Redis, DynamoDB, Elasticsearch, Typesense, Kafka, etcd and SurrealDB have no +Copying is SQL only. MongoDB, Redis, DynamoDB, Firestore, the Firebase Realtime Database, Elasticsearch, +Typesense, Kafka, etcd and SurrealDB have no `CREATE TABLE` and no row writer to copy through, so neither command appears on them, and neither end of a copy may be one of them. diff --git a/features/data-grid.mdx b/features/data-grid.mdx index 0fe209a9..d8af2a95 100644 --- a/features/data-grid.mdx +++ b/features/data-grid.mdx @@ -138,7 +138,7 @@ A table tab pages. The status bar offers 5, 10, 20, 100, 500 or 1,000 rows per p A large table shows an estimated total prefixed with `~` instead of running a slow `COUNT(*)`. **Count Exactly** beside it runs the real count in the background, and `Cmd+.` cancels that. While a total is an estimate, **Last** and **All rows…** stay unavailable and **Next** works as long as the last page came back full. -Cassandra and ScyllaDB have no estimate, and their count reads every partition, so a table there shows no total until you click **Count Exactly**. +Cassandra, ScyllaDB and the Firebase Realtime Database have no estimate, and their count reads every partition or key, so a table there shows no total until you click **Count Exactly**. A query tab does not page. It stops at the [row cap](/customization/data-settings) instead and offers **Fetch All** to load the rest, and a query carrying its own `LIMIT`, `FETCH FIRST`, or `TOP` is never capped. `Cmd+.` cancels a running query or a Fetch All. diff --git a/features/filtering.mdx b/features/filtering.mdx index a382113b..a26d2051 100644 --- a/features/filtering.mdx +++ b/features/filtering.mdx @@ -72,7 +72,7 @@ On MySQL, MariaDB, SQL Server, SQLite, libSQL, and Cloudflare D1 the column's co ## Combining conditions -**Match all** and **Match any** in the header join every row with AND or OR. Cassandra and ScyllaDB have no OR, so their filter bar offers **Match all** only. There is no per-row choice and no grouping, so a mixed expression cannot be built out of rows. Put the parentheses in a raw SQL row instead, which enters the WHERE clause as a single parenthesized condition: +**Match all** and **Match any** in the header join every row with AND or OR. Cassandra, ScyllaDB and the Firebase Realtime Database have no OR, so their filter bar offers **Match all** only. There is no per-row choice and no grouping, so a mixed expression cannot be built out of rows. Put the parentheses in a raw SQL row instead, which enters the WHERE clause as a single parenthesized condition: ```sql (status = 'open' AND priority > 3) OR escalated diff --git a/features/plugins.mdx b/features/plugins.mdx index f57bfc0d..7f70a3c6 100644 --- a/features/plugins.mdx +++ b/features/plugins.mdx @@ -42,6 +42,7 @@ These install from the registry when you need them: | DynamoDB | DynamoDB | | BigQuery | BigQuery | | Spanner | Spanner | +| Firebase | Cloud Firestore, Firebase Realtime Database | | Snowflake | Snowflake | | libSQL | libSQL, Turso | | Elasticsearch | Elasticsearch | diff --git a/features/routines-triggers.mdx b/features/routines-triggers.mdx index ef940431..9a5225e5 100644 --- a/features/routines-triggers.mdx +++ b/features/routines-triggers.mdx @@ -27,7 +27,7 @@ Expand a schema and **Procedures**, **Functions**, and **Triggers** sit beside * | DuckDB | No | Macros | No | | SQLite, LibSQL, Cloudflare D1 | No | No | Yes | | Redshift, CockroachDB, PGlite, SAP HANA | No | No | No | -| MongoDB, Redis, Elasticsearch, Typesense, DynamoDB, etcd, SurrealDB, Trino, Beancount, Spanner | No | No | No | +| MongoDB, Redis, Elasticsearch, Typesense, DynamoDB, etcd, SurrealDB, Trino, Beancount, Spanner, Firestore, Firebase Realtime Database | No | No | No | A section appears only where the engine has that kind of object. An engine that has them and currently holds none keeps the section and shows **No items** inside it, so a database with no tables still lists its procedures and functions. A section whose list is still on its way shows **Loading…**, and one the server refused shows the error in place of its rows. diff --git a/features/table-operations.mdx b/features/table-operations.mdx index 8637672d..bbfc66f3 100644 --- a/features/table-operations.mdx +++ b/features/table-operations.mdx @@ -65,7 +65,7 @@ The statement runs at once instead of joining the drop and truncate queue, so ** | SAP HANA | No | No | No | | MongoDB | Yes | No | No | -No other engine has a rename, so the item never appears on Cassandra, DynamoDB, Elasticsearch, etcd, Kafka, Redis, SurrealDB, Typesense or Beancount. What resembles one there is a copy followed by a delete, and it changes record identity. +No other engine has a rename, so the item never appears on Cassandra, DynamoDB, Elasticsearch, etcd, Firestore, the Firebase Realtime Database, Kafka, Redis, SurrealDB, Typesense or Beancount. What resembles one there is a copy followed by a delete, and it changes record identity. **Rename Database** and **Rename Schema** are absent on the container the connection is browsing. Switch to another one first, then rename the one you left. diff --git a/features/table-structure.mdx b/features/table-structure.mdx index c7f4e8ef..ea275a2b 100644 --- a/features/table-structure.mdx +++ b/features/table-structure.mdx @@ -280,7 +280,7 @@ Edit a field's **Name** to rename it in every document, or remove its row to rem - **Check constraints and generated columns** have no field in the grid. Both show in the DDL tab; change them by running DDL yourself. - **Cassandra / ScyllaDB**: add and drop column only, no index editing, no visual table creation. - **ClickHouse**: the Indexes tab drops a data-skipping index but cannot add or change one. A save or a new table that tries is refused before anything runs. Run `ALTER TABLE … ADD INDEX … TYPE bloom_filter` in the SQL editor instead. -- **Redshift, CockroachDB, BigQuery, Spanner, Elasticsearch, Typesense, SurrealDB, Beancount**: structure is read-only. +- **Redshift, CockroachDB, BigQuery, Spanner, Elasticsearch, Typesense, SurrealDB, Beancount, Firestore, Firebase Realtime Database**: structure is read-only. - **Redis, etcd, DynamoDB**: no table schema to edit. ## Refreshing