Skip to content

feat(ui): multi-schema awareness across Editor, Brain, and Advisor - #55

Merged
geekypunk merged 5 commits into
mainfrom
cursor/multi-schema-ui-c497
Aug 15, 2026
Merged

feat(ui): multi-schema awareness across Editor, Brain, and Advisor#55
geekypunk merged 5 commits into
mainfrom
cursor/multi-schema-ui-c497

Conversation

@venkateshsakamuri-lab

@venkateshsakamuri-lab venkateshsakamuri-lab commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Summary

End-to-end multi-schema UI pass so connections like Multi Schema Shop (crm / sales) no longer look like a single bare-name catalog.

P0 — Authoring

  • Shared src/lib/schemaNames.js helpers (canonicalTableReference, objectKey, qualifyForSql, …)
  • SQL Editor + Tables overview: schema-prefixed labels, qualified autocomplete/insertText, generated SQL (FROM crm.orders), collision-safe React keys, schema-aware search
  • Clicking a table name inserts SELECT … FROM schema.table (chevron expands columns)
  • Index/stats REST paths accept schema.table; Postgres/MySQL introspection scopes indexes by schema

P1 — Brain / Knowledge / ERD

  • Schema Docs display + note persist use tableReference (no cross-schema note collisions)
  • Knowledge @ / @@ suggestions show qualified labels; bare-name lookup only when unique
  • ERD / classification / key-column grouping use qualified ids
  • DetailsLibrary CSV example + NoteModal placeholder encourage schema.table

P2 — Advisor / ops copy

  • Database advisor scans non-public schemas and emits schema-qualified CREATE INDEX … ON
  • Performance cards / heatmap keep schema in labels
  • Privileges accordion documents multi-schema GRANT pattern
  • Dashboard-design skill nudges schema-qualified SQL

Verification

On Multi Schema Shop:

  • Editor lists crm.customers, sales.order_items, sales.orders
  • Click inserts FROM crm.customers
  • Brain Schema Docs shows the same qualified names

Editor with schema-qualified tables
Generated SQL FROM crm.customers
Brain Schema Docs with crm/sales tables

Test plan

  • Select Multi Schema Shop → Editor lists crm.* / sales.*
  • Click table generates FROM schema.table SQL
  • Brain Schema Docs shows schema.table
  • Regression: Demo Shop (public-only) still shows bare table names
  • node --test src/lib/schemaNames.test.js

Note: PR #53 is docs-only Cloud caveats; this PR is the multi-schema UI work.

To show artifacts inline, enable in settings.

Open in Web Open in Cursor 

cursoragent and others added 4 commits August 14, 2026 19:13
Add shared schemaNames helpers and wire the SQL editor / tables
overview so object browser, autocomplete, generated SQL, and index
lookup APIs use schema.table for non-default schemas.

Co-authored-by: Venkat SF <venkatesh.sakamuri@stayflexi.com>
Persist/display tableReference in Schema Docs notes, show schema in
knowledge @ suggestions, and use qualified ids in ERD / classification /
key-column grouping so crm.orders and sales.orders do not collide.

Co-authored-by: Venkat SF <venkatesh.sakamuri@stayflexi.com>
Advisor scans non-public schemas, emits schema-qualified CREATE INDEX
SQL, and Performance/privileges/dashboard skill copy follow suit.

Co-authored-by: Venkat SF <venkatesh.sakamuri@stayflexi.com>
Keep expand/collapse on the chevron only so a normal click inserts
schema-qualified SELECT … FROM schema.table.

Co-authored-by: Venkat SF <venkatesh.sakamuri@stayflexi.com>
@venkateshsakamuri-lab
venkateshsakamuri-lab marked this pull request as ready for review August 14, 2026 19:20
@venkateshsakamuri-lab
venkateshsakamuri-lab requested a review from a team as a code owner August 14, 2026 19:20
@cursor

cursor Bot commented Aug 14, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@geekypunk geekypunk 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.

Reviewed backend + the new shared util.

Backend. PostgresIntrospectionProvider.getTableIndexes now joins pg_namespace and filters with (?::text IS NULL OR n.nspname = ?) — fully parameterized, and every column is table-qualified per the repo's SQL rule. The null-schema branch keeps bare names working, so this is backward compatible. {tableName:.+} on the two /tables/{id}/… mappings is the right fix for schema-qualified ids, and PathPatternParser keeps it to a single path segment.

Shared util. schemaNames.js is pure, documented, and ships with tests. objectKey including the schema is what stops crm.orders and sales.orders colliding as React keys — the actual bug class this PR exists to close. qualifyForSql correctly leaves public/dbo bare.

One non-blocking note for a follow-up: with a bare table name the Postgres schema filter is disabled (schemaName stays null), so indexes can still merge across same-named tables in different schemas. That only matters for callers that haven't been migrated to encodeTablePathId; the UI paths in this PR all pass qualified ids. Worth tightening to the session search_path later rather than blocking here.

@geekypunk
geekypunk merged commit 5e08481 into main Aug 15, 2026
9 checks passed
@geekypunk
geekypunk deleted the cursor/multi-schema-ui-c497 branch August 15, 2026 15:52
geekypunk pushed a commit that referenced this pull request Aug 15, 2026
<!-- CURSOR_AGENT_PR_BODY_BEGIN -->
## Summary

Prepares and documents the **v1.1.0** product cut from `main` since
`v1.0.0`, and locks in a **weekly release cadence: Saturday 09:00
America/Los_Angeles**.

### Since v1.0.0
- Progressive dashboards (#57), dashboard improvements (#51)
- Multi-schema UI (#55), Performance hub (#52)
- CI CodeQL unblock (#56), cloud env caveats (#53)

### This PR
- Bump `backend/pom.xml` → `1.1.0`
- `CHANGELOG.md` + `docs/releases/RELEASE_NOTES-v1.1.0.md`
- Cadence in `docs/oss-ux/RELEASE.md`
- New `docs/oss-ux/WEEKLY_RELEASE_AUTOMATION.md` (cron + paste-ready
prompt)
- Daily triage doc clarified as optional (not the release cut)

### After merge
1. Tag `v1.1.0` on the merge commit and push →
`.github/workflows/release.yml` publishes the GitHub Release.
2. Create the Cursor Automation once from `WEEKLY_RELEASE_AUTOMATION.md`
(cannot be created via API).

### Pre-flight
`scripts/self-host/e2e-agent-check.py` → `AGENT_OK True`, `DASH_OK True`
on the current stack before this bump.
<!-- CURSOR_AGENT_PR_BODY_END -->

<div><a
href="https://cursor.com/agents/bc-8ce91e70-c67b-48c6-84b3-05bb9d06231a?cursor_ref=pr_footer&cursor_cta=open_in_web"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source
media="(prefers-color-scheme: light)"
srcset="https://cursor.com/assets/images/open-in-web-light.png"><img
alt="Open in Web" width="114" height="28"
src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a>&nbsp;<a
href="https://cursor.com/background-agent?bcId=bc-8ce91e70-c67b-48c6-84b3-05bb9d06231a&cursor_ref=pr_footer&cursor_cta=open_in_cursor"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source
media="(prefers-color-scheme: light)"
srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img
alt="Open in Cursor" width="131" height="28"
src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a>&nbsp;</div>

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
cursor Bot pushed a commit that referenced this pull request Aug 16, 2026
`getTablesAndViews()` passes the schema each table was found in, but
`getTableColumns` bound `DEFAULT_SCHEMA` (`'public'`) and discarded the
argument. Every table on a database whose objects live outside `public`
came back with **zero columns**.

Measured against a real dbt warehouse (195 tables across 17 schemas,
nothing in `public`):

```
marts.dim_person, schema bound to 'public' ->  0 columns
                                   'marts' -> 91 columns
```

The PK subquery also had no schema predicate at all. Postgres auto-names
primary keys `<table>_pkey`, so two schemas holding a same-named table
cross-match by construction — producing duplicated `ColumnInfo` rows and
false-positive PK flags. This adds `tc.table_schema = ku.table_schema`
and `ku.table_schema = ?`.

Both problems were latent while the provider only ever read `public`.
#55 made them live: now that introspection walks every non-system
schema, a `staging`/`marts`/`public` collision on `orders` or
`customers` is the normal shape of a dbt warehouse, not the exception.

## Verification

Deployed and measured on a live 195-table warehouse:

| | before | after |
|---|---:|---:|
| schema snapshot | 37 tables / 837 cols | **195 / 4,070** |
| tables returning zero columns | 195 of 195 | **0 of 195** |
| tables with correctly-detected PKs | — | 67 |
| tables with duplicated column rows | — | 0 |
| tables with every column flagged PK | — | 0 |

A full brain re-init on that connection went from a 37-table snapshot to
195 tables / 4,070 columns, lifting `table_classification` 25 → 161 and
`inferred_table_relationship` 17 → 280. The agent went from being blind
to 14 of 18 schemas to correctly describing them.

Unit tests: `PostgresIntrospectionProviderTest` passes. A mocked
`ResultSet` can't exercise SQL semantics, so the correctness evidence is
the measurement above, against Postgres 18.

## Relationship to #40

#40 is open and `CONFLICTING`. It fixed the same class of problem with a
`current_schema()` approach that #55 superseded by scanning all
non-system schemas. This PR sits on top of #55 instead and is
independent of #40#40 can likely be closed once this lands.

Co-authored-by: deepsql-deploy <venkatesh.sakamuri@stayflexi.com>
geekypunk added a commit that referenced this pull request Aug 19, 2026
…s across schemas

The previous commit matched a protected table against a query's tables by
adding both the qualified name and its bare part to each side, then
intersecting. That collapsed public.customer_profiles and
marts.customer_profiles to the same key, so protecting one refused queries
against the other -- with a message naming a table the user never referenced.

This product added multi-schema support in #55 and an acme_erp fixture with
crm/sales/finance/hr/inventory in #65, so same-named tables across schemas are
the expected shape here, not a corner case. Over-blocking is the safe
direction, which is exactly why it would have survived review and surfaced
later as unexplained refusals.

Matching is now asymmetric, because the two sides carry different information.
ConnectionChatAccessPolicyService.qualifyTable() drops the schema when it is
"public", so a bare PROTECTED name means public.<table> -- it is not unknown. A
bare REFERENCE in a query is genuinely unknown: it resolves through the session
search_path and could be any schema.

    reference unqualified -> match on bare name (ambiguous, so block)
    protected public      -> a qualified reference must actually say public
    both qualified        -> exact match

Every bypass stays closed: an unqualified reference to a protected table is
still refused, and hr.salaries still matches a bare "salaries".

Also replaces a characterization test asserting the opposite. It was written
before qualifyTable's public-collapsing was discovered and encoded the wrong
belief that a bare protected name is ambiguous; the case genuinely worth
pinning is a bare reference, which it now covers.

Verified: 17/17 in UserDataAccessPolicyServiceTest. Regression baseline on the
same suite selection unchanged at 8 failures / 16 errors (414 run vs 412
before, the delta being these two tests passing).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.

3 participants