Table labels - #551
Draft
teunbrand wants to merge 5 commits into
Draft
Table labels#551teunbrand wants to merge 5 commits into
teunbrand wants to merge 5 commits into
Conversation
TableCell (execute/table.rs) positions each column label and data value on an inclusive row/column grid, tagged ColumnLabel/Body (TableCellKind, naming borrowed from R's gt) so a writer can tell them apart without relying on position. ResolvedTable now holds Vec<TableCell> instead of a DataFrame; nrow()/ncol() are computed from cells rather than stored, so there's one source of truth for the table's shape. Writer::write_table drops its Table parameter entirely: Table's only field (source) is already consumed before cells exist, so there's nothing left for a writer to use it for. HtmlWriter, ggsql-cli and ggsql-jupyter are updated to match.
create_column_labels and create_body each build their half of the layout independently, both numbering rows from 0 — neither knows where it sits relative to the other. compose_cells is a pure function of the two (no DataFrame/SQL knowledge), computing the row offset from the column labels' actual extent and shifting the body via TableCell::offset_rows/offset_cols rather than each caller touching top/bottom or left/right separately. This is what lets more intermediate composers (header, footer, ...) join compose_cells later as Table grows, and keeps cells_from_dataframe useful as a place to test offset arithmetic in isolation from any real query — added layout_tests covering create_column_labels, create_body, and compose_cells (including a label section spanning more than one row, to pin down that the offset comes from the labels' real extent rather than an assumed single row) using the df! macro, no duckdb feature or Reader needed.
tabulate_statement now accepts an optional label_clause (reused unchanged from VISUALISE), so TABULATE FROM sales LABEL id => 'ID' parses. Table.labels: Labels (not Option<Labels> — an empty Labels already means "no overrides", so there's no third state an Option would distinguish, and every consumer's logic is the same either way). build_tabulate_statement now mirrors build_visualise_statement's structure: a process_tab_clause function parallels process_viz_clause, ready to grow an arm per future TABULATE clause (FACET/SCALE). execute::table::create_column_labels resolves each column's content against Table.labels with three outcomes: a column absent from labels keeps its name, an explicit LABEL col => NULL empties the cell, and LABEL col => 'text' overrides it. Verified end-to-end via a real ggsql exec --writer html invocation, plus a unit test covering all three outcomes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Groundwork ahead of column spanners, the feature expected to make cell overlap actually reachable: validate_overlaps walks each cell's grid footprint into a position set and errors on the first collision. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
create_table_columns is now the one place a TABULATE LABEL clause gets resolved; create_column_labels and create_body both build cells off its order instead of the DataFrame's raw column order, so a future spanner- driven column rearrangement reaches cell positions without either function changing. create_body also now looks columns up by name and fetches each column's array once (outside the row loop) rather than once per cell. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
teunbrand
added this pull request to stack #552
September 17, 2026 09:29
Draft
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This is a PR stacking on top of #550.
The user-facing change is that we can now use the
LABELclause in tabulate.However, the real meat of this PR is the intermediate representation of table cells, which is groundwork for future features.
The idea is that writers only receive a vector of
TableCells, which contains all the relevant information for rendering the table.Currently, these track the cells position in the table, the kind of cell (only body cell / column label now), the content (label) and in the future they'll also have formatting/styling information to be processed by the writer.
The
HtmlWriteris made to have a surface-level understanding ofTableCells, currently only considering top-left positions for placing them. Row- and column spans are planned once we develop features that generate them.