-
Notifications
You must be signed in to change notification settings - Fork 331
Subqueries blog post #4138
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
Closed
Closed
Subqueries blog post #4138
Changes from all commits
Commits
Show all changes
70 commits
Select commit
Hold shift + click to select a range
69469d2
Add 'rob' to authors
robacourt b828d6e
Add agent generated draft
robacourt d1e2bd0
Remove link with low relevance
robacourt d465845
First pass
robacourt 956c396
Second pass
robacourt 50e98e6
Update title
robacourt 9e02647
Minor updates
robacourt ad11c8f
Add deets on DNF
robacourt 9c7e611
Add image
robacourt 3884eca
Another pass
robacourt dc7e609
Rename to fluent-subqueries
robacourt 47c829d
Quick pass
robacourt a7bdbd6
Make what's been added clear
robacourt fc9b016
Highlight AND
robacourt dbfd9d5
Human pass
robacourt f39cd4a
Remove how it works
robacourt 9c46849
fluent -> expressive
robacourt a123710
Rename files
robacourt 0f94138
Publish
robacourt 7db40e0
Review pass
robacourt d06bf84
State version
robacourt a7fed95
Update requirements
robacourt 818bf93
Review pass
robacourt f6c3c39
Drop links
robacourt 3cd4d1a
Trim
robacourt b3624a4
Update intro
robacourt 8db108f
Better explaination
robacourt de29258
Snappier opening
robacourt 067457b
Andother pass
robacourt bd9839a
Update warning
robacourt 0416939
Remove info box
robacourt fa14ad7
Change to Subqueries
robacourt f540b48
Mention pro plan
robacourt c64ab84
Another pass
robacourt e133423
Mention showing relational data
robacourt fbfa21a
Remove some examples
robacourt b22f357
Remove duplicate
robacourt 41632dd
Remove 'patterns' heading
robacourt cfb9ddc
Revamp intro
robacourt bf5c5df
General use
robacourt 9544ab2
Better
robacourt 2ec7bee
Review pass
robacourt b323c62
Review pass
robacourt 6a2dd17
publish
robacourt 512d2f0
Anotehr pass
robacourt e9c5ecd
Liven up tone
robacourt 0af3809
Show JS code ASAP
robacourt c9d2142
Update docs
robacourt d9f294e
Update where clause documentation
robacourt 2ce549e
Mention query-driven sync
robacourt 87649a9
Update warning formatting
robacourt e8e7249
Subqueries are optimised too
robacourt 1d2293c
Add banner
robacourt ab07811
Add banner to postgres sync
robacourt 1a2795f
Add ilia as author
robacourt d634c5a
Add InlineBanner
robacourt 939f198
Use banner in subqueries blog
robacourt d0b6382
Move banner
robacourt 2a5b7ce
Update banner
robacourt bb3df02
Attempt to make warning better
robacourt 7a1c5eb
Remove warnings
robacourt 2789373
Move info box
robacourt 2636052
Add summary
robacourt 02356ae
Remove cruft
robacourt a9cf5aa
Update blog date to today
robacourt 0c3087c
Prose up the summary
robacourt e5a21d8
Remove 'Get in touch to enable'
robacourt a981196
Make into normal info box
robacourt b070e45
Update banner
robacourt 60f847c
Improve summary
robacourt 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
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,178 @@ | ||
| --- | ||
| title: "Subqueries\u2014making sync work in practice" | ||
| description: >- | ||
| Subqueries let Electric shapes express relational filtering in SQL. | ||
| Electric 1.6 keeps complex AND/OR/NOT expressions incremental too, so | ||
| large shapes stay fast. | ||
| excerpt: >- | ||
| Sync only works in real apps if it can follow relationships. | ||
| Subqueries let Electric express relational filters for each user in SQL, | ||
| and Electric 1.6 keeps complex expressions incremental too. | ||
| authors: [rob, icehaunter] | ||
| image: /img/blog/subqueries/header.jpg | ||
| tags: [shapes, postgres-sync, release] | ||
| outline: [2, 3] | ||
| post: true | ||
| published: true | ||
| --- | ||
|
|
||
| Sync is what makes apps feel instant. The data is already there when a screen renders. Another user changes something and your UI stays current. You can refresh, reconnect, switch devices, and keep going. | ||
|
|
||
| That is the broad pitch. We have written more about how [sync replaces data fetching](/blog/2025/04/22/untangling-llm-spaghetti) and why it is the right foundation for [collaborative, real-time apps](/blog/2025/04/09/building-ai-apps-on-sync). | ||
|
|
||
| But there is a more practical question underneath all of it: | ||
|
|
||
| Which rows should this client actually receive? | ||
|
|
||
| In simple demos, a column filter is enough. In real systems, the rule usually lives in other tables. A document is visible because you own it, or because it was shared with you, or because you belong to the workspace that contains it. Comments sync because their issue belongs to a project you can access. Invoice line items sync because their parent invoice does. | ||
|
|
||
| This is where subqueries matter. | ||
|
|
||
| :::info Subqueries are now available for everyone in Electric 1.6 | ||
| [Try subqueries in Cloud](https://dashboard.electric-sql.cloud/). | ||
| ::: | ||
|
|
||
| ## Query-driven sync | ||
|
|
||
| Shapes are Electric's primitive for partial replication: a table and a `WHERE` clause. Define the subset once and Electric keeps that subset synced. | ||
|
|
||
| For flat cases, the filter is simple: | ||
|
|
||
| ```sql | ||
| owner_id = $1 | ||
| ``` | ||
|
|
||
| And here is how that looks in TanStack DB: | ||
|
|
||
| ```ts | ||
| const documentsCollection = createCollection( | ||
| electricCollectionOptions({ | ||
| id: 'my-documents', | ||
| shapeOptions: { | ||
| url: `${ELECTRIC_URL}/v1/shape`, | ||
| params: { | ||
| table: 'documents', | ||
| where: 'owner_id = $1', | ||
| params: { '1': currentUserId }, | ||
| }, | ||
| }, | ||
| }) | ||
| ) | ||
| ``` | ||
|
|
||
| Parameters (`$1`) are bound per client, so the same shape definition can serve different data to different users. | ||
|
|
||
| But real apps do not stay flat for long. Access control, tenant membership, and parent-child data all pull in related tables. Subqueries let you express those rules directly in SQL. | ||
|
|
||
| Sync documents for workspaces this user belongs to: | ||
|
|
||
| ```sql | ||
| workspace_id IN ( | ||
| SELECT workspace_id FROM workspace_members | ||
| WHERE user_id = $1 | ||
| ) | ||
| ``` | ||
|
|
||
|
|
||
| You can combine relational checks with ordinary predicates. For example, sync documents that I own, plus documents shared with me: | ||
|
|
||
| ```sql | ||
| owner_id = $1 | ||
| OR id IN ( | ||
| SELECT document_id FROM document_shares | ||
| WHERE shared_with = $1 | ||
| ) | ||
| ``` | ||
|
|
||
| You can also traverse multiple hops. Sync comments for a project by walking from comments to issues to tasks: | ||
|
|
||
| ```sql | ||
| issue_id IN ( | ||
| SELECT id FROM issues WHERE task_id IN ( | ||
| SELECT id FROM tasks WHERE project_id = $1 | ||
| ) | ||
| ) | ||
| ``` | ||
|
|
||
| This is mundane SQL. That is the point. | ||
|
|
||
| The rule stays close to the data, where you already reason about memberships, shares, and relationships. Electric evaluates it server-side and keeps only the matching rows on each client. | ||
|
|
||
| See the [WHERE clause docs](/docs/guides/shapes#where-clause) for the full reference on supported operators and subquery patterns. | ||
|
|
||
| :::info | ||
| Subqueries are available on [Electric Cloud](/cloud) and are included in the [Pro, Scale, and Enterprise plans](/pricing). | ||
| ::: | ||
|
|
||
| ## What changed in Electric 1.6 | ||
|
|
||
| Subqueries are not new. We have supported them for a while, behind feature flags, and they have already been battle tested by customers in production. | ||
|
|
||
| Electric 1.6 is the release that closes one of the last awkward cases. | ||
|
|
||
| Subqueries already supported incremental sync for simple expressions. Complex expressions using `AND`, `OR`, and `NOT` also worked, but when the subquery result changed Electric could fall back to a full resync. On small shapes you might never notice. On large ones you would feel it as lag between a write and the UI catching up. | ||
|
|
||
| With 1.6, those complex expressions stay incremental too. When memberships change, shares are granted, or related rows move in or out of scope, Electric now syncs only the affected rows. Large shapes keep the low-latency behavior that makes sync useful in the first place. | ||
|
|
||
| That is why we now consider subqueries suitable for general use. | ||
|
|
||
| This release also includes a client protocol update needed for the new incremental behavior. The feature flags are unchanged for now and we will remove them once we are confident clients have moved onto the newer protocol. | ||
|
|
||
| ## Using it now | ||
|
|
||
| Here is the shared-documents example wired into a TanStack DB collection: | ||
|
|
||
| ```ts | ||
| import { electricCollectionOptions } from '@tanstack/electric-db-collection' | ||
| import { createCollection } from '@tanstack/react-db' | ||
|
|
||
| const documentsCollection = createCollection( | ||
| electricCollectionOptions({ | ||
| id: 'my-documents', | ||
| shapeOptions: { | ||
| url: `${ELECTRIC_URL}/v1/shape`, | ||
| params: { | ||
| table: 'documents', | ||
| where: ` | ||
| owner_id = $1 | ||
| OR id IN ( | ||
| SELECT document_id FROM document_shares | ||
| WHERE shared_with = $1 | ||
| ) | ||
| `, | ||
| params: { '1': currentUserId }, | ||
| }, | ||
| }, | ||
| }) | ||
| ) | ||
| ``` | ||
|
|
||
| Update to the latest packages: | ||
|
|
||
| ```sh | ||
| npm install @tanstack/db@latest @tanstack/electric-db-collection@latest | ||
| ``` | ||
|
|
||
| Subqueries remain behind the same feature flags as before: | ||
|
|
||
| ```sh | ||
| ELECTRIC_FEATURE_FLAGS=allow_subqueries,tagged_subqueries | ||
| ``` | ||
|
|
||
| See the [WHERE clause docs](/docs/guides/shapes#where-clause) for the full reference. | ||
|
|
||
| ## Summary | ||
|
|
||
| The interesting part of sync is not a nicer `fetch()`. It is what you get once the right data is already local: live UIs, collaboration, resilient apps, instant navigation, fewer loading states. | ||
|
|
||
| But none of that survives contact with production unless sync can follow your actual data model. The moment you have shared documents, org membership, private projects, child records, or exclusions, a simple column filter stops being enough. | ||
|
|
||
| Subqueries are what make shapes fit real applications. They let you describe who can see a row, which child rows come along with a parent, how multiple access paths compose, and how exclusions or overrides work. | ||
|
|
||
| We now consider them ready for general use, and they are available to everyone in Electric 1.6. | ||
|
|
||
| If you want to use them on Cloud today, get in touch and we can enable them on your project. The remaining rollout flags are temporary, and we expect to lift them over the next few weeks. | ||
|
|
||
| *** | ||
|
|
||
| [Docs](/docs/guides/shapes#where-clause) · [Cloud](/cloud) · [Discord](https://discord.electric-sql.com) | ||
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They're both AI links but we don't mention AI in the blog. Maybe that's fine 🤷♂️