Skip to content

Read a Databricks catalog, and pin catalog rows as a shared fixture - #278

Draft
jat255 wants to merge 2 commits into
jat255/xp65-snowflake-readerfrom
jat255/xp65-databricks-reader
Draft

Read a Databricks catalog, and pin catalog rows as a shared fixture#278
jat255 wants to merge 2 commits into
jat255/xp65-snowflake-readerfrom
jat255/xp65-databricks-reader

Conversation

@jat255

@jat255 jat255 commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator

Fourth of the catalog import stack (kata xp65). Stacked on #277. Ports the catalog-import half of catalog-databricks.R, and moves both readers' row interpretation into a shared fixture.

Unity Catalog is read through system.information_schema, a plain query rather than a SHOW, with the information schema itself excluded so a listing describes the data rather than the catalog. The legacy hive_metastore is not in there at all and needs SHOW TABLES, which is why the listing forks on the catalog name and why selecting hive_metastore requires a schema: there is nothing to enumerate a whole legacy catalog from.

DESCRIBE TABLE does not report nullability, so it is read separately from system.information_schema.columns and joined by column name. hive_metastore has no information schema to ask, so its columns keep an unknown nullability rather than a guessed one. DESCRIBE TABLE also appends partition and detail sections after a row whose name starts with #; reading past that turns section headings into columns.

Metric views are out of scope with the rest of the semantic-model work (kata gcgj). They report as views here, and telling them apart needs the ODBC object type R queries, which only matters once metric views are supported.

Row interpretation is now a shared fixture

Which rows are relations, what kind each is, which comments count as prose, and where a DESCRIBE reply stops being columns are all user-observable, so they belong in tests/shared/ rather than being asserted in each language. catalog-rows.json carries the rows both warehouses actually return and what both implementations must make of them. Running the queries stays each language's own business; only their replies are a contract.

Booleans travel as strings, because a bare JSON true reads back as a logical in R and an absent one as NULL, which would let the two runners disagree about a fixture neither had changed.

R changes

R/ gains one behaviour-preserving refactor and no new behaviour. Snowflake's column shaping was inline in snowflake_describe_relation(), which also runs the query, so the contract it implements could not be reached without a live connection. It is now snowflake_describe_rows(), called by that same function with the same rows and returning the same data frame. Databricks already had databricks_columns_from_describe() split out, so it needed nothing.

In the test suite, two hand-written blocks are replaced by fixture runners: "Snowflake SHOW results retain native relation metadata" and "Databricks information schema retains native relation metadata". Every expectation they held is now a case in catalog-rows.json, none was dropped, and the Python suite asserts the same cases. The three comparison helpers live in a new helper-catalog-rows.R rather than in either test file, because a definition at the top of one test file is not visible to the other when files are run individually, which is how I found the runner failing.

Why it is safe: test-catalog-snowflake.R and test-catalog-databricks.R both pass. The fixture bites rather than passing vacuously, verified by changing one expected kind and watching each suite fail. Nothing outside those two files and the extracted function changed, so no other call site moves.

I did not run the full R suite. It hangs for about ten minutes until #268 lands, so I ran the two affected files only, which is the evidence behind the claim above.

pkg-r/tests/testthat/fixtures/shared/ is generated by scripts/sync-shared-fixtures.sh; skip it.

Verification

677 Python tests, ruff and pyrefly clean. The reader was confirmed to bite by removing the # metadata cutoff and by removing the hive_metastore fork.

@jat255
jat255 marked this pull request as draft September 4, 2026 06:10
@jat255 jat255 added this to the py-M2: data layer milestone Sep 4, 2026
@jat255 jat255 added needs-manual-review Agent-created work that needs a human review py Affects the Python implementation labels Sep 4, 2026
@jat255
jat255 force-pushed the jat255/xp65-databricks-reader branch from 3babd70 to dfed967 Compare September 5, 2026 04:58
@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown

Preview deployed to Connect (dogfood.team.pct.posit.it): https://dogfood.team.pct.posit.it/connect/#/apps/d7a36cae-8f27-448b-a478-61b81fbe3942/draft/368162

Deployed from commit 865cb4e.

@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown

Preview deployed to Connect (dogfood.team.pct.posit.it): https://dogfood.team.pct.posit.it/connect/#/apps/d7a36cae-8f27-448b-a478-61b81fbe3942/draft/367964

Deployed from commit dfed967.

@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown

Preview deployed to Connect (connect.staging.pct.posit.it): https://connect.staging.pct.posit.it/connect/#/apps/ad662e1b-5048-4acc-9ad7-f9478c92274e/draft/2638

Deployed from commit 865cb4e.

Fourth of the catalog import stack (kata xp65). Ports the catalog-import
half of catalog-databricks.R.

Unity Catalog is read through system.information_schema, which is a plain
query rather than a SHOW, and the information schema itself is excluded so a
listing describes the data rather than the catalog. The legacy
hive_metastore is not in there at all and needs SHOW TABLES, which is why
the listing forks on the catalog name and why selecting hive_metastore
requires a schema: there is nothing to enumerate a whole legacy catalog
from.

DESCRIBE TABLE does not report nullability, so it is read separately from
system.information_schema.columns and joined by column name. hive_metastore
has no information schema to ask, so its columns keep an unknown
nullability rather than a guessed one.

DESCRIBE TABLE also appends partition and detail sections after a row whose
name starts with `#`; reading past that turns section headings into columns.

Metric views are out of scope with the rest of the semantic-model work
(kata gcgj). They report as views here, and telling them apart needs the
ODBC object type R queries, which only matters once metric views are
supported. The comment in the reader says so.
Which rows are relations, what kind each is, which comments count as prose,
and where a DESCRIBE reply stops being columns are all user-observable, so
they belong in tests/shared/ rather than being asserted in each language.
catalog-rows.json carries the rows Snowflake and Databricks actually return
and what both implementations must make of them; R's hand-written row
assertions are replaced by runners over it.

Running the queries stays each language's own business. Only their replies
are a contract.

One R change beyond the runners: the Snowflake column shaping was inline in
snowflake_describe_relation(), which also runs the query, so the contract
was not reachable without a connection. It is now
snowflake_describe_rows(), called by the same function. Behaviour is
unchanged. Databricks already had databricks_columns_from_describe().

Booleans travel as strings, because a bare JSON true reads back as a logical
in R and an absent one as NULL, which would make the two runners disagree
about a fixture neither had changed.

Both runners were confirmed to bite by changing one expected kind and
watching each suite fail.

Found by roborev job 335.
@jat255
jat255 force-pushed the jat255/xp65-databricks-reader branch from dfed967 to 865cb4e Compare September 6, 2026 02:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-manual-review Agent-created work that needs a human review py Affects the Python implementation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant