Repository navigation
[CXH-2722] - Add Unity Catalog schema sync - Databricks Connector - #72
Conversation
d90274a to
aabdb53
Compare
920df46 to
f9142b4
Compare
aabdb53 to
b12726a
Compare
f9142b4 to
29274ee
Compare
b12726a to
b2ecaf2
Compare
29274ee to
e74210e
Compare
b2ecaf2 to
00adc02
Compare
e74210e to
7deff05
Compare
00adc02 to
5933db6
Compare
7deff05 to
1253d7e
Compare
FeliLucero1
left a comment
There was a problem hiding this comment.
Did a pass on this one. The main things are the two mediums: Revoke on a dropped schema fails instead of returning already-revoked, and List swallows a 404 into an empty page. The rest is perf/docs/nits.
Stuff tagged (out of scope) is inherited from #71 or is stack-wide, so FYI, not blocking for this PR. Also checked the schema privilege set against the Databricks docs and it matches exactly (27, minus BROWSE / CREATE_SCHEMA / USE_CATALOG) 👍
FeliLucero1
left a comment
There was a problem hiding this comment.
Another pass. Nice work on the live checks (SCHEMA_DOES_NOT_EXIST, the privilege PATCH) and moving Grant/Revoke onto the shared helpers 👍
The main things this round are about schemas silently disappearing: the partial-listing skip (replied on the :73 thread), the usable[0] regression (replied on Sergio's :353 thread), and a 404 that still comes out as NotFound. The rest are nits. (out of scope): the same NotFound-wrapping shape exists in catalog.go pushRoute (#71).
sergiocorral-conductorone
left a comment
There was a problem hiding this comment.
Automated review (follow-up)
Head moved since our last LGTM (ce73192 → abee6af). Resolved at head: the usable[0] regression (workspace walk is back, TestSchemaListTriesTheNextWorkspace restored), the partial-listing fail-closed behaviour, the docs nit on the workspace-admins-group exception. Still open from earlier rounds: FeliLucero1's thread on schema.go:269 (plain %w on the metastore-assignment / workspace / metastore-owner reads lets a NotFound become an SDK warning). New this round: 1 regression (Revoke on a dropped schema), 1 medium (built-in catalog skip), 2 low.
drafted 4 → confirmed 4, low-confidence 0, dropped 0, added 0 (panel: opus-5.5 / gpt-6-astra / gemini-4-argon, all high)
sergiocorral-conductorone
left a comment
There was a problem hiding this comment.
Automated review (follow-up round on new commits).
Stats: drafted 2 → confirmed 2, low-confidence 0, dropped 0, added 0 (panel: opus-5.5 / gpt-6-astra / gemini-4-argon, all high).
sergiocorral-conductorone
left a comment
There was a problem hiding this comment.
Automated review
drafted 3 → confirmed 3, low-confidence 0, dropped 0, added 0 (panel: opus-5.5 / gpt-6-astra / gemini-4-argon, all high).
60501e2 to
a343b68
Compare
CXH-2722. Schemas hang from the catalog, keyed {metastore_id}::{catalog}.{schema},
with the catalog privilege set minus BROWSE, CREATE_SCHEMA and USE_CATALOG,
plus a read-only owner. information_schema is skipped. Grant and revoke use
the same echo-confirmed idempotency as the catalog.
The tree is unchanged from 433e33c, the state reviewed and approved on #72.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
433e33c to
b704b02
Compare
CXH-2722. Schemas hang from the catalog, keyed {metastore_id}::{catalog}.{schema},
with the catalog privilege set minus BROWSE, CREATE_SCHEMA and USE_CATALOG,
plus a read-only owner. information_schema is skipped. Grant and revoke use
the same echo-confirmed idempotency as the catalog.
The tree is unchanged from 433e33c, the state reviewed and approved on #72.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
b704b02 to
f63db64
Compare
CXH-2722. Schemas hang from the catalog, keyed {metastore_id}::{catalog}.{schema},
with the catalog privilege set minus BROWSE, CREATE_SCHEMA and USE_CATALOG,
plus a read-only owner. information_schema is skipped. Grant and revoke use
the same echo-confirmed idempotency as the catalog.
The tree is unchanged from 433e33c, the state reviewed and approved on #72.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The trust read is the first call that can prove a securable gone, and it runs before the permissions read. syncGrants returned its error as is, so a schema deleted between List and Grants failed the sync with the bare errSecurableGone sentinel: codes.Unknown, no securable name. Twelve lines below, the permissions read already treats that same deletion as an empty result rather than a failure. The two reads see the same fact and now answer the same way. A dropped parent catalog reaches the same place: the trust probe reads the schema's effective permissions, a dropped catalog answers 404 with CATALOG_DOES_NOT_EXIST, and trustReadError classifies it with the level's full gone-code set. The tests use a principal that owns neither the metastore nor the catalog. With ownership the trust check short-circuits and never reaches the probe, which is why the existing dropped-schema test did not cover this. Also removes a docs bullet that said the built-in system catalogs are synced while the connector owns the metastore. Catalog List skips them unconditionally, as the bullet three lines above already says. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
f63db64 to
068e05d
Compare
| workspace: route.Workspace, | ||
| workspaceID: workspaceID, | ||
| }, annos) | ||
| if err != nil { |
There was a problem hiding this comment.
[low] schemaListingIsComplete returns errCatalogGone on purpose (:408-409), but here it just return errs, so it goes up raw (codes.Unknown, no prefix) and fails the sync. It's the race where GetCatalog says 200 and the permissions read then says CATALOG_DOES_NOT_EXIST. The catalogThrough branch above already does sawGone = true; continue, so maybe do the same here? There's no test for it either.
|
|
||
| Skip this section if you are not syncing schemas, or if the service principal owns the metastore or the catalog. | ||
|
|
||
| Workspace **Admin** does not list every schema, except when the catalog is owned by the workspace admins group (`_workspace_admins_<workspace>_<id>`) and the service principal is a direct Admin of that workspace. On any other catalog the service principal does not own, Databricks returns only the schemas it owns or can use, with HTTP 200. To list every schema, the service principal must own the metastore, own the catalog, be that direct Admin, or hold **MANAGE**, **USE SCHEMA**, or **ALL PRIVILEGES** on the catalog. |
There was a problem hiding this comment.
nit: MANAGE / USE SCHEMA / ALL PRIVILEGES only makes the listing complete. Grants/Grant/Revoke still need ownership or MANAGE on the schema or catalog (schemaGrantsAreTrustworthy, and ALL_PRIVILEGES is excluded on purpose). So someone who follows this with just USE SCHEMA gets the schemas listed, and then Grants fails with PermissionDenied. Maybe add a line about what reading schema privileges needs, like the catalog section has?



Description
Third of four stacked PRs for Unity Catalog (CXH-2722). A schema sits between a catalog and the tables and volumes an access review certifies. A principal can hold
USE_SCHEMAwithout holding anything on those tables, so the schema is its own resource.#70 is merged. #71 merges before this one.
What the connector syncs
Before, with #71 applied. The catalog is a leaf.
After this PR. Schemas hang from the catalog, with their own entitlements and grants.
staginglanded onmainmainSync:
schema, NEW) — child ofcatalog, read withGET /api/2.1/unity-catalog/schemas?catalog_name=. Keyed{metastore_id}::{catalog}.{schema}. The privilege set is the catalog set withoutBROWSE,CREATE_SCHEMAandUSE_CATALOG, plus a read-onlyowner.error_code: that is the catalog itself being gone, and the catalog syncer removes the parent in the same sync.information_schemais skipped. Its system views are owned by principals that do not exist in SCIM.Provisioning:
PATCH .../permissionspath and the same echo-confirmed idempotency as the catalog:GrantAlreadyExistsandGrantAlreadyRevoked.SCHEMA_DOES_NOT_EXISTorCATALOG_DOES_NOT_EXISTis already revoked, whether the code comes back from the permissions read or from the trust check before it. Only the error code proves the absence, because an unbound or unprivileged workspace answers 404 as well.Auth:
Credentials are unchanged. There is no new config field.
Schema List fails with
PermissionDeniedunless the service principal owns the metastore, owns the catalog, or holdsMANAGE,USE_SCHEMAorALL_PRIVILEGESon the catalog. Workspace Admin alone is not enough.A direct Admin is also enough when the catalog is owned by that workspace's admins group. The connector reads the workspace id off the group name
_workspace_admins_<workspace>_<id>and checks that workspace, whatever route the schema is listed through. Permission visibility follows the caller, not the host.The visibility probe walks the attached workspaces. A 401 or 403 leaves the privilege unproven and the probe tries the next workspace. A workspace answering
CATALOG_DOES_NOT_EXISThas only said it does not serve the catalog, since anISOLATEDcatalog answers the same way from every workspace it is not bound to, so the walk moves on there too. Only every workspace answering that way, with no refusal among them, lists no schemas. Any other probe error fails that catalog's schema List. The page token carries the workspace and the metastore owner. It does not carry the catalog owner.Architecture highlights:
holdsPrivilegeOnis the one uncached read of the connector's own effective privileges. Each level passes the privileges it accepts and the error codes that prove the securable is gone. The schema probes carry those codes, so a 404 naming the missing securable is read as an absence rather than as a privilege the caller cannot see. Without them a principal holdingMANAGEbut owning neither the metastore nor the catalog gotPermissionDeniedon a dropped parent instead of a revoke reporting the grant already gone.resolveChangeruns it before the permissions read.grantandrevokeanswer that proof withNotFoundandGrantAlreadyRevokedrather than returning it as an unreadable securable.Find*filters for an owner-only grant.Useful links:
Merge order and release flow
staginglands onmainas its own PR, #91. The rest of the stack is rebased on top of it, so each PR below shows only the layer it adds. #84 is independent of this stack and merges on its own.staginglanded onmain(46 files)#90 and #71 target #91 until it merges, and GitHub retargets them to
mainat that point. #72 and #73 keep their parent branch as base.Rebased on 2026-11-03, as requested on #90. Before this, #90 and #71 showed 42
staging-only commits and 50+ files, which hid the layer each one adds. Every branch was rebuilt on top of #91. The tree at each head is byte-identical to the one reviewed before the rebase, so no code changed. The commits are squashed into one per PR, which marks earlier review threads as outdated; they stay readable.Release flow. Cut a test release (
-test) from the feature branch and move the PR to draft. The ticket moves to validation. QA validates that release. Merge tomain. Cut the official release frommain, and the ticket moves to rollout.