gabotorresruiz opened a new pull request, #45150:
URL: https://github.com/apache/superset/pull/45150

   ### SUMMARY
   
   Virtual datasets over BigQuery tables with STRUCT (RECORD) columns only 
register the top-level columns.
   The STRUCT shows up as a single untyped `RECORD` column, its cells render as 
JSON strings, and its fields cannot be used as dimensions, filters or time 
columns.
   A physical dataset over the same table gets one dotted column per field 
(`calendar.day`, `calendar.client.week_number`, ...).
   
   #### Root cause
   
   - Physical datasets get their columns from the SQLAlchemy inspector, and 
`sqlalchemy-bigquery` flattens non-repeated STRUCT fields into dotted columns.
   - Virtual datasets get their columns from `get_columns_description`, which 
builds them from the DB-API `cursor.description` of a metadata probe.
   - The BigQuery DB-API builds `cursor.description` from the top-level schema 
fields only, so the nested fields never reach Superset.
   
   #### Fix
   
   - New `BaseEngineSpec.expand_nested_columns(cursor, columns)` hook, called 
by `get_columns_description` on the probe's columns.
     The default returns the columns unchanged, so every other engine is 
untouched.
   - `BigQueryEngineSpec` implements it by reading the result schema the DB-API 
cursor keeps for its last `execute()`, and appends one dotted column per 
non-repeated STRUCT field right after its parent.
     This is the same set and order of names the inspector returns for physical 
tables.
   - Repeated STRUCT columns (`ARRAY<STRUCT<...>>`) are listed but not 
expanded, because `a.b` is not selectable on them, again matching the inspector.
   - A selected column that already has a member's name (an alias) is kept and 
the member is skipped, so there are no duplicate names.
   - Types, `type_generic` and `is_dttm` of the new columns come from the 
engine spec's column specs, so a nested `DATE` field is a temporal column.
   
   #### Calculated columns named like a STRUCT field
   
   Until now the way to use a STRUCT field on a virtual dataset was a 
calculated column named like the field, for example `calendar.day` with 
expression `UPPER(calendar.day)`.
   `fetch_metadata` matches source columns to existing columns by name, so 
without care the first refresh after this change would turn such a calculated 
column into a plain column and drop its expression.
   
   - The expanded columns carry a `nested_field` flag on `ResultSetColumnType`.
   - `fetch_metadata` skips a flagged column whose name belongs to an existing 
calculated column, so the calculated column keeps its expression and no 
duplicate is created.
   - Any other source column still replaces a calculated column of the same 
name, as before (covered by 
`test_fetch_metadata_source_column_replaces_calculated_column`).
   - The flag is never persisted: `TableColumn` is built from explicit fields, 
and the frontend sync builds new columns from explicit fields too.
   
   #### Design notes
   
   - Expansion is unconditional for BigQuery because the BigQuery inspector 
already flattens STRUCT fields unconditionally for physical datasets.
     Trino's ROW expansion stays where it is (inspector path, opt in through 
`expand_rows`); Trino could implement the same hook for virtual datasets in a 
follow-up.
   - Existing virtual datasets are not migrated.
     They gain the new columns the next time their columns are synced or 
refreshed; nothing is removed or retyped.
   
   ### BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF
   
   #### Before
   
   Virtual dataset over `SELECT * FROM struct_repro`: 4 columns, `calendar` is 
an untyped `RECORD`.
   
   <!-- before-02-virtual-columns.png -->
   
   Explore: `calendar` cells are JSON strings and no `calendar.*` column can be 
picked.
   
   <!-- before-04-explore-calendar-json.png -->
   
   #### After
   
   "Sync columns from source" adds the 6 STRUCT fields and keeps the original 4 
columns.
   
   <!-- after-01-virtual-sync-added.png -->
   
   `calendar.day` as a dimension with a filter on `calendar.client.week_number`.
   
   <!-- after-03-explore-calendar-day-dimension.png -->
   
   A calculated column `calendar.day` = `UPPER(calendar.day)` keeps its 
expression after a sync.
   
   <!-- after-06-calculated-column-coexists.png -->
   
   ### TESTING INSTRUCTIONS
   
   #### Unit tests
   
   ```bash
   pytest tests/unit_tests/db_engine_specs/test_bigquery.py \
     tests/unit_tests/db_engine_specs/test_base.py \
     tests/unit_tests/connectors/sqla/utils_test.py \
     tests/unit_tests/connectors/sqla/models_test.py
   ```
   
   #### Manual
   
   1. In a BigQuery project (or the `goccy/bigquery-emulator`), create a table 
with a nested STRUCT:
   
      ```sql
      CREATE TABLE my_dataset.struct_repro AS
      SELECT
        d AS date,
        STRUCT(
          EXTRACT(DAY FROM d) AS day_num_of_month,
          FORMAT_DATE('%A', d) AS day,
          STRUCT(
            EXTRACT(WEEK FROM d) AS week_number,
            DATE_TRUNC(d, WEEK) AS week_start_date
          ) AS client
        ) AS calendar,
        'USD' AS currency,
        1 AS event_count
      FROM UNNEST(GENERATE_DATE_ARRAY('2025-01-01', '2025-12-31')) AS d
      ```
   
   2. In SQL Lab run `SELECT * FROM my_dataset.struct_repro` and save it as a 
dataset.
   3. Edit the dataset and open Columns.
      Expect `calendar.day_num_of_month`, `calendar.day`, `calendar.client`, 
`calendar.client.week_number` and `calendar.client.week_start_date` next to the 
top-level columns, with `calendar.client.week_start_date` marked temporal.
   4. Build a Table chart with `calendar.day` as a dimension and a filter on 
`calendar.client.week_number`.
      View query shows `` `calendar`.`day` `` and `` 
`calendar`.`client`.`week_number` ``.
   5. Build a line chart with `calendar.client.week_start_date` on the x axis 
and a weekly time grain.
   6. On a virtual dataset created before this change, add a calculated column 
`calendar.day` with expression `UPPER(calendar.day)`, then run "Sync columns 
from source" and Save, and also call `PUT /api/v1/dataset/<id>/refresh`.
      The calculated column keeps its expression, the other STRUCT fields are 
added, and there is a single `calendar.day`.
   7. Regression checks: a physical dataset over the same table, SQL Lab 
results, and a virtual dataset on another engine are unchanged.
   
   ### ADDITIONAL INFORMATION
   
   - [ ] Has associated issue:
   - [ ] Required feature flags:
   - [ ] Changes UI
   - [ ] Includes DB Migration (follow approval process in 
[SIP-59](https://github.com/apache/superset/issues/13351))
     - [ ] Migration is atomic, supports rollback & is backwards-compatible
     - [ ] Confirm DB migration upgrade and downgrade tested
     - [ ] Runtime estimates and downtime expectations provided
   - [ ] Introduces new feature or API
   - [ ] Removes existing feature or API
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to