Skip to content

ENG-1907 - Update content uniqueness and upsert surfaces#1198

Open
mdroidian wants to merge 3 commits into
mainfrom
eng-1907-update-content-uniqueness-and-upsert-surfaces
Open

ENG-1907 - Update content uniqueness and upsert surfaces#1198
mdroidian wants to merge 3 commits into
mainfrom
eng-1907-update-content-uniqueness-and-upsert-surfaces

Conversation

@mdroidian

@mdroidian mdroidian commented Jul 6, 2026

Copy link
Copy Markdown
Member

Updated content identity so multiple representations of the same source item can coexist. Content is now unique on (space_id, source_local_id, variant, content_type), and upsert_content uses that same key so, for example, a full markdown row and a full ATJSON row do not overwrite each other.

FileReference now includes content_type so asset references point to the exact parent Content representation. The current default is text/obsidian+markdown for compatibility with the existing Obsidian asset sync path; future callers should pass the parent content type explicitly.

Also updated the embedding view/types and added a feature scenario covering two full content rows with different content types.

Also fixes ENG-1908 because changing Content uniqueness to include content_type makes the old FileReference foreign key ambiguous. Updating FileReference to include content_type is part of making ENG-1907’s new content identity valid.


Open in Devin Review

…riants

- Introduced a new scenario in the feature file to test coexistence of different content types.
- Updated step definitions to include checks for content rows based on variant and content type.
- Modified database types to include `content_type` in relevant tables and relationships.
- Adjusted SQL schemas to enforce content type constraints and ensure proper indexing.
- Backfilled existing rows with default content types to maintain data integrity.
@linear-code

linear-code Bot commented Jul 6, 2026

Copy link
Copy Markdown

ENG-1907

ENG-1908

@vercel

vercel Bot commented Jul 6, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
discourse-graph Ready Ready Preview, Comment Jul 16, 2026 1:55pm

Request Review

@supabase

supabase Bot commented Jul 6, 2026

Copy link
Copy Markdown

Updates to Preview Branch (eng-1907-update-content-uniqueness-and-upsert-surfaces) ↗︎

Deployments Status Updated
Database Thu, 16 Jul 2026 14:07:07 UTC
Services Thu, 16 Jul 2026 14:07:07 UTC
APIs Thu, 16 Jul 2026 14:07:07 UTC

Tasks are run on every commit but only new migration files are pushed.
Close and reopen this PR if you want to apply changes from existing seed or migration files.

Tasks Status Updated
Configurations Thu, 16 Jul 2026 14:07:08 UTC
Migrations Thu, 16 Jul 2026 14:07:08 UTC
Seeding Thu, 16 Jul 2026 14:07:08 UTC
Edge Functions Thu, 16 Jul 2026 14:07:10 UTC

View logs for this Workflow Run ↗︎.
Learn more about Supabase for Git ↗︎.

@mdroidian

Copy link
Copy Markdown
Member Author

@codex review

filehash character varying NOT NULL, -- or binary?
"created" timestamp without time zone NOT NULL,
last_modified timestamp without time zone NOT NULL,
content_type character varying NOT NULL DEFAULT 'text/obsidian+markdown',

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Defaulting FileReference.content_type to text/obsidian+markdown preserves current behavior because Obsidian is the only file-reference writer today and does not pass this value yet. This is not a hard modeling choice: longer term, callers should probably pass the parent Content.content_type explicitly, e.g. Roam would use text/roam+markdown. Happy to make the DB stricter now if we prefer requiring that on insert.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Open in Devin Review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5e21302e23

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@graphite-app

This comment was marked as resolved.

@mdroidian
mdroidian requested a review from maparent July 6, 2026 06:34
@mdroidian

Copy link
Copy Markdown
Member Author

One concern with this PR: PR #1154 added Content.content_type with a table default of text/plain and backfilled existing full rows as markdown. This PR adds a function-level fallback that turns omitted content_type into text/plain before the new (space_id, source_local_id, variant, content_type) conflict key is evaluated.

That means any omitted content_type upsert for an existing markdown full row could insert a second full/text/plain row instead of updating the existing markdown row. Current Obsidian content upserts are safe because they pass content_type explicitly. Roam has a full writer that omits it, but it only runs behind includeFullContent, which does not appear to be enabled anywhere today.

My preference is to make content_type required for content upserts so this cannot silently happen. We could either do that in this PR or make it an explicit follow-up before enabling more full writers. That would mean updating all callers/examples to pass content_type explicitly.

maparent commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

TBH I'm not sure that's a good idea as is, and I remember deciding against it, and I thought it had been discussed. If we do this, we will have multiple formats of a content, and we won't know which one is the original/source of truth. First, what is your use case of having multiple formats? I can see an optimization to avoid repeating translation work. In that case, what I would suggest if you think the time saving is worth the storage cost (which is plausible but I think we should analyze a bit), is to add another converted_from column, saying that a given content row was converted from another content row. Then we would know which one is the source of truth, and we could still have a unique key for the original triple (without format) when the converted_from column is empty. Or am I missing a use case?

Comment on lines +16 to +18
CREATE UNIQUE INDEX IF NOT EXISTS content_space_local_id_variant_content_type_originals_idx ON public."Content" USING btree (
space_id, source_local_id, variant, original
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This unique index conflicts with the PR's goal. The index on (space_id, source_local_id, variant, original) prevents multiple Content rows with the same (space_id, source_local_id, variant) when original=true. However, the PR explicitly allows multiple representations (e.g., markdown and ATJSON) to coexist with the same variant='full' as shown in the test scenario (lines 137-184 of addContent.feature). Since Content.original defaults to true (line 93 of content.sql), this index will cause a unique constraint violation when inserting the second content row in the test.

-- This index should be removed or original should be nullable
-- and only set to true for one canonical representation
CREATE UNIQUE INDEX IF NOT EXISTS content_space_local_id_variant_content_type_originals_idx 
  ON public."Content" USING btree (space_id, source_local_id, variant, original);

The test expects 2 Content rows with variant='full' and different content_types, but this will fail because both will have original=true by default, violating this unique constraint.

Suggested change
CREATE UNIQUE INDEX IF NOT EXISTS content_space_local_id_variant_content_type_originals_idx ON public."Content" USING btree (
space_id, source_local_id, variant, original
);
CREATE UNIQUE INDEX IF NOT EXISTS content_space_local_id_variant_content_type_originals_idx ON public."Content" USING btree (
space_id, source_local_id, variant, content_type
);

Spotted by Graphite

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

@maparent
maparent force-pushed the eng-1907-update-content-uniqueness-and-upsert-surfaces branch from d10befd to 5b1ca3e Compare July 16, 2026 14:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants