Skip to content

Conversation

@maparent
Copy link
Collaborator

@maparent maparent commented Aug 24, 2025

https://linear.app/discourse-graphs/issue/ENG-771/handle-empty-account-name

Context: Now that we do bulk account upsert, we sometimes get empty names from roam. We should not overwrite defined names with empty names.

Summary by CodeRabbit

  • New Features

    • More robust automatic linking of accounts to spaces, with synchronized editor access and email identity handling for smoother onboarding and updates.
  • Bug Fixes

    • Prevents existing account names from being overwritten by blank values.
    • Preserves and correctly updates trusted status for email identifiers.
    • Reduces accidental permission regressions when updating space access.
    • Handles missing or empty inputs gracefully to avoid unintended data changes.

@linear
Copy link

linear bot commented Aug 24, 2025

@supabase
Copy link

supabase bot commented Aug 24, 2025

Updates to Preview Branch (eng-771-handle-empty-account-name) ↗︎

Deployments Status Updated
Database Fri, 12 Sep 2025 14:08:13 UTC
Services Fri, 12 Sep 2025 14:08:13 UTC
APIs Fri, 12 Sep 2025 14:08:13 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 Fri, 12 Sep 2025 14:08:19 UTC
Migrations Fri, 12 Sep 2025 14:08:22 UTC
Seeding Fri, 12 Sep 2025 14:08:22 UTC
Edge Functions Fri, 12 Sep 2025 14:08:25 UTC

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

@maparent
Copy link
Collaborator Author

@CodeRabbit review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 24, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 24, 2025

📝 Walkthrough

Walkthrough

Adds a new SQL function public.upsert_account_in_space for upserting accounts into spaces, handling platform resolution, account name updates without overwriting with empty strings, space access updates, and optional email identifier upserts with trusted flags. Adjusts ON CONFLICT name update to treat empty string as NULL.

Changes

Cohort / File(s) Change summary
DB function: account upsert into space
packages/database/supabase/migrations/20250824134305_do_not_overwrite_with_empty_name.sql
Introduces public.upsert_account_in_space(bigint, account_local_input) that resolves platform by space_id, upserts PlatformAccount (preserving name when incoming is NULL/empty), upserts SpaceAccess editor flag, and conditionally upserts AgentIdentifier for email; returns account id.
Name update semantics tweak
packages/database/supabase/schemas/account.sql
Updates ON CONFLICT name update to treat empty string as NULL via COALESCE(CASE WHEN length(name)=0 THEN NULL ELSE name END, pa.name). No signature changes.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Client
  participant DB as public.upsert_account_in_space
  participant Space as Space
  participant Plat as Platform
  participant PA as PlatformAccount
  participant SA as SpaceAccess
  participant AI as AgentIdentifier

  Client->>DB: upsert_account_in_space(space_id, local_account)
  DB->>Space: SELECT platform_id FROM Space WHERE id=space_id
  Space-->>DB: platform_id
  DB->>Plat: SELECT platform FROM Platform WHERE id=platform_id
  Plat-->>DB: platform

  rect rgba(200,240,255,0.3)
    note right of DB: Upsert platform account (preserve name if NULL/empty)
    DB->>PA: INSERT ... ON CONFLICT (account_local_id, platform)<br/>DO UPDATE SET name = COALESCE(NULLIF(local_account.name,''), pa.name)
    PA-->>DB: RETURNING id INTO account_id
  end

  rect rgba(220,255,220,0.3)
    note right of DB: Upsert space access
    DB->>SA: INSERT ... ON CONFLICT (space_id, account_id)<br/>DO UPDATE SET editor = COALESCE(local_account.space_editor, sa.editor, true)
  end

  alt local_account.email IS NOT NULL
    rect rgba(255,245,200,0.4)
      note right of DB: Upsert email identifier
      DB->>AI: INSERT (account_id, identifier_type='email', value, trusted)<br/>ON CONFLICT (identifier_type,value)<br/>DO UPDATE SET trusted = COALESCE(local_account.email_trusted, ai.trusted, false)
    end
  end

  DB-->>Client: account_id
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • ENG-764 Bulk account uploading #357 — Modifies the same upsert_account_in_space logic to avoid overwriting names with empty strings, directly related to the name update behavior in this change.

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (3)
packages/database/supabase/migrations/20250824134305_do_not_overwrite_with_empty_name.sql (3)

1-6: SECURITY DEFINER + empty search_path is good; ensure intended owner and grants

Solid hardening by schema-qualifying objects and setting an empty search_path. Double-check that the function owner is the expected role (e.g., service role) and that execution privileges are granted only to roles that need it. If not handled elsewhere in migrations, add ALTER FUNCTION ... OWNER TO ... and GRANT EXECUTE statements.


12-22: Consider preventing insertion of empty/whitespace names on initial insert

The ON CONFLICT branch preserves existing names when the input is empty, but the initial INSERT can still write an empty or whitespace-only name (column is NOT NULL, so '' is permitted). If empty names should never be stored, consider normalizing at INSERT time or adding a CHECK constraint.

Two options:

  • Minimal change (normalize at INSERT, fallback to account_local_id or raise):
-            local_account.account_local_id, local_account.name, platform_
+            local_account.account_local_id,
+            COALESCE(NULLIF(BTRIM(local_account.name), ''), local_account.account_local_id),
+            platform_
  • Schema-level guard (follow-up migration): ALTER TABLE public."PlatformAccount" ADD CONSTRAINT platform_account_name_not_blank CHECK (BTRIM(name) <> '');

Confirm desired behavior before adopting, since the first option changes persisted values for blank inputs.


11-12: SELECT ... INTO STRICT is appropriate; consider exception message for DX

STRICT will raise if the space_id is invalid. Optionally wrap to provide a clearer SQLSTATE/message that includes space_id_, to help callers distinguish input errors from internal failures.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 40b63ec and 555b668.

📒 Files selected for processing (2)
  • packages/database/supabase/migrations/20250824134305_do_not_overwrite_with_empty_name.sql (1 hunks)
  • packages/database/supabase/schemas/account.sql (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: maparent
PR: DiscourseGraphs/discourse-graph#272
File: packages/ui/src/lib/supabase/contextFunctions.ts:50-111
Timestamp: 2025-07-08T14:48:38.048Z
Learning: The Supabase client does not offer transaction support, so idempotent upserts with proper conflict resolution (using onConflict with ignoreDuplicates: false) are the preferred approach for multi-step database operations in packages/ui/src/lib/supabase/contextFunctions.ts. This pattern prevents orphaned records when retrying operations.
📚 Learning: 2025-07-08T14:48:38.048Z
Learnt from: maparent
PR: DiscourseGraphs/discourse-graph#272
File: packages/ui/src/lib/supabase/contextFunctions.ts:50-111
Timestamp: 2025-07-08T14:48:38.048Z
Learning: The Supabase client does not offer transaction support, so idempotent upserts with proper conflict resolution (using onConflict with ignoreDuplicates: false) are the preferred approach for multi-step database operations in packages/ui/src/lib/supabase/contextFunctions.ts. This pattern prevents orphaned records when retrying operations.

Applied to files:

  • packages/database/supabase/migrations/20250824134305_do_not_overwrite_with_empty_name.sql
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Supabase Preview
  • GitHub Check: Supabase Preview
🔇 Additional comments (3)
packages/database/supabase/migrations/20250824134305_do_not_overwrite_with_empty_name.sql (3)

23-25: SpaceAccess upsert logic looks solid

Insert defaulting editor to true, and conflict update preserving existing value when input is NULL is sensible and idempotent. Nice alignment with the Supabase no-transaction upsert pattern noted in past learnings.


23-34: RETURNING ... INTO STRICT pattern is correct and safe

Good use of RETURNING with INTO STRICT to guarantee account_id_ is set in both insert and update paths.


1-36: Overall: Idempotent upsert flow aligns with our Supabase retry pattern

The function composes PlatformAccount, SpaceAccess, and AgentIdentifier upserts in an idempotent way suitable for clients without transaction support. Good job minimizing race surfaces with proper ON CONFLICT clauses.

Comment on lines 16 to 22
) ON CONFLICT (account_local_id, platform) DO UPDATE SET
name = COALESCE(
CASE WHEN length(local_account.name) = 0
THEN NULL ELSE local_account.name END,
pa.name
)
RETURNING id INTO STRICT account_id_;
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Make name update whitespace-safe and rely on EXCLUDED to reflect the proposed value

Same point as in schema: use NULLIF(BTRIM(...), '') and EXCLUDED for clarity and to prevent overwriting with whitespace-only input.

-        ) ON CONFLICT (account_local_id, platform) DO UPDATE SET
-            name = COALESCE(
-                CASE WHEN length(local_account.name) = 0
-                THEN NULL ELSE local_account.name END,
-                pa.name
-            )
+        ) ON CONFLICT (account_local_id, platform) DO UPDATE SET
+            name = COALESCE(NULLIF(BTRIM(EXCLUDED.name), ''), pa.name)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
) ON CONFLICT (account_local_id, platform) DO UPDATE SET
name = COALESCE(
CASE WHEN length(local_account.name) = 0
THEN NULL ELSE local_account.name END,
pa.name
)
RETURNING id INTO STRICT account_id_;
) ON CONFLICT (account_local_id, platform) DO UPDATE SET
name = COALESCE(NULLIF(BTRIM(EXCLUDED.name), ''), pa.name)
RETURNING id INTO STRICT account_id_;
🤖 Prompt for AI Agents
In
packages/database/supabase/migrations/20250824134305_do_not_overwrite_with_empty_name.sql
around lines 16 to 22, the ON CONFLICT update currently uses a CASE on
local_account.name and allows whitespace-only strings to overwrite the existing
name; change it to use EXCLUDED (the proposed insert value) and make the check
whitespace-safe by trimming and nullifying empty results, e.g. set name =
COALESCE(NULLIF(BTRIM(EXCLUDED.name), ''), pa.name) so whitespace-only or empty
names do not overwrite the prior value.

Comment on lines +26 to +27
IF local_account.email IS NOT NULL THEN
-- TODO: how to distinguish basic untrusted from platform placeholder email?
INSERT INTO public."AgentIdentifier" as ai (account_id, value, identifier_type, trusted) VALUES (account_id_, local_account.email, 'email', COALESCE(local_account.email_trusted, false))
ON CONFLICT (value, identifier_type, account_id)
DO UPDATE SET trusted = COALESCE(local_account.email_trusted, ai.trusted, false);
END IF;
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Normalize email value and guard against blank/whitespace; consider case-insensitive storage

To avoid duplicates and inconsistent matching:

  • Skip upsert when email is blank/whitespace.
  • Normalize to lower(trim(email)) before storage and conflict checks.
-    IF local_account.email IS NOT NULL THEN
+    IF local_account.email IS NOT NULL AND BTRIM(local_account.email) <> '' THEN
-        INSERT INTO public."AgentIdentifier" as ai (account_id, value, identifier_type, trusted) VALUES (account_id_, local_account.email, 'email', COALESCE(local_account.email_trusted, false))
+        INSERT INTO public."AgentIdentifier" as ai (account_id, value, identifier_type, trusted)
+        VALUES (account_id_, LOWER(BTRIM(local_account.email)), 'email', COALESCE(local_account.email_trusted, false))
         ON CONFLICT (value, identifier_type, account_id)
         DO UPDATE SET trusted = COALESCE(local_account.email_trusted, ai.trusted, false);

Note: If existing data isn’t normalized yet and primary key is case-sensitive, this may require a data backfill and possibly switching value to CITEXT or a functional PK using LOWER(value). Suggest as a follow-up migration.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
IF local_account.email IS NOT NULL THEN
-- TODO: how to distinguish basic untrusted from platform placeholder email?
INSERT INTO public."AgentIdentifier" as ai (account_id, value, identifier_type, trusted) VALUES (account_id_, local_account.email, 'email', COALESCE(local_account.email_trusted, false))
ON CONFLICT (value, identifier_type, account_id)
DO UPDATE SET trusted = COALESCE(local_account.email_trusted, ai.trusted, false);
END IF;
IF local_account.email IS NOT NULL AND BTRIM(local_account.email) <> '' THEN
-- TODO: how to distinguish basic untrusted from platform placeholder email?
INSERT INTO public."AgentIdentifier" as ai (account_id, value, identifier_type, trusted)
VALUES (
account_id_,
LOWER(BTRIM(local_account.email)),
'email',
COALESCE(local_account.email_trusted, false)
)
ON CONFLICT (value, identifier_type, account_id)
DO UPDATE SET trusted = COALESCE(local_account.email_trusted, ai.trusted, false);
END IF;
🤖 Prompt for AI Agents
packages/database/supabase/migrations/20250824134305_do_not_overwrite_with_empty_name.sql
around lines 26-31: the upsert currently inserts raw local_account.email and
will overwrite or duplicate entries for blank/whitespace or case variants;
change the logic to skip the INSERT when email is NULL or consists only of
whitespace, compute a normalized value once (lower(trim(local_account.email)))
and use that normalized value in the INSERT and the ON CONFLICT key checks, and
preserve the existing trusted flag when COALESCE’ing (i.e., DO UPDATE SET
trusted = COALESCE(local_account.email_trusted, ai.trusted, false)); add a note
to follow up with a data backfill and/or switching the column to CITEXT or
creating a functional unique index on lower(value) if existing data is not
normalized.

Comment on lines 129 to 134
name = COALESCE(
CASE WHEN length(local_account.name) = 0
THEN NULL ELSE local_account.name END,
pa.name
)
RETURNING id INTO STRICT account_id_;
Copy link
Contributor

@coderabbitai coderabbitai bot Aug 24, 2025

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Prefer NULLIF(BTRIM(...), '') and EXCLUDED for clearer, whitespace-safe updates

Current CASE handles empty string but not whitespace-only names, and references the PL/pgSQL parameter instead of the proposed row via EXCLUDED. Recommend simplifying and making it robust to whitespace:

  • Treat whitespace-only names as empty.
  • Use EXCLUDED.name to reflect the incoming value in the ON CONFLICT branch.

Apply:

-            name = COALESCE(
-                CASE WHEN length(local_account.name) = 0
-                THEN NULL ELSE local_account.name END,
-                pa.name
-            )
+            name = COALESCE(NULLIF(BTRIM(EXCLUDED.name), ''), pa.name)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
name = COALESCE(
CASE WHEN length(local_account.name) = 0
THEN NULL ELSE local_account.name END,
pa.name
)
RETURNING id INTO STRICT account_id_;
name = COALESCE(NULLIF(BTRIM(EXCLUDED.name), ''), pa.name)
RETURNING id INTO STRICT account_id_;

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Good catch, made the change. But I used trim, curious why you thought to use btrim? The name is a varchar.

Copy link
Contributor

Choose a reason for hiding this comment

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

@maparent You're absolutely right! For a VARCHAR field, TRIM is actually the better choice here. Both TRIM and BTRIM would work similarly for removing leading and trailing whitespace, but:

  • TRIM is the SQL standard function and more portable
  • BTRIM is PostgreSQL-specific and essentially equivalent for basic whitespace removal
  • For VARCHAR fields with standard whitespace trimming, TRIM is the cleaner, more conventional choice

I suggested BTRIM out of PostgreSQL-specific habit, but your use of TRIM is more appropriate and standard. Thanks for the good catch and question!

@maparent maparent force-pushed the eng-771-handle-empty-account-name branch from 555b668 to dc62510 Compare August 24, 2025 14:17
@maparent maparent requested a review from mdroidian August 24, 2025 16:22
Copy link
Contributor

@mdroidian mdroidian left a comment

Choose a reason for hiding this comment

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

Why is there a large CREATE OR REPLACE in the migration, but I don't see this code in any new .sql file?

Also, could you give an example of how this would be used in practice on the client side?

@maparent maparent force-pushed the eng-771-handle-empty-account-name branch 3 times, most recently from a5ea9c5 to 46c1d36 Compare August 27, 2025 23:33
@maparent maparent force-pushed the eng-771-handle-empty-account-name branch from 46c1d36 to 5cdbbd8 Compare August 31, 2025 17:46
@maparent maparent force-pushed the eng-771-handle-empty-account-name branch from 5cdbbd8 to 5212d4c Compare September 12, 2025 13:50
@maparent
Copy link
Collaborator Author

Missed that comment. the migration has a (smallish?) create or replace for upsert_account_in_space, which is defined at schemas/account.sql:111.
Did not document, because mostly used as a subroutine of upsert_content, which is documented, but you're right that it can also be used directly and added appropriate documentation to do so.

@maparent maparent merged commit b688e35 into main Sep 12, 2025
5 checks passed
@github-project-automation github-project-automation bot moved this to Done in General Sep 12, 2025
@maparent maparent deleted the eng-771-handle-empty-account-name branch September 12, 2025 18:16
trangdoan982 pushed a commit that referenced this pull request Oct 3, 2025
* Do not overwrite name with empty name

* document upsert_accounts_in_space
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

No open projects
Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants