Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
f57107b
Table plumbing (#550)
teunbrand Sep 22, 2026
af74bff
Table labels (#551)
teunbrand Sep 22, 2026
ae021e5
Table spans (#553)
teunbrand Sep 22, 2026
ed5de72
Table format (#554)
teunbrand Sep 22, 2026
30c0731
Table titelature (#563)
teunbrand Sep 24, 2026
f6d1277
Table stub (#568)
teunbrand Sep 29, 2026
21a2198
Finishing touches for tables (#571)
teunbrand Oct 1, 2026
0e4f0af
Merge branch 'main' into table-feature
teunbrand Oct 1, 2026
f486002
Rename `ACROSS` to `OVER`
teunbrand Oct 2, 2026
f4b2613
propagate doc suggestions
teunbrand Oct 2, 2026
c4ff215
Apply batched suggestions from code review
teunbrand Oct 2, 2026
2e292b3
apply more suggestions
teunbrand Oct 5, 2026
7d42ca8
move out params into top-level file
teunbrand Oct 5, 2026
d6512b5
move labels to top level
teunbrand Oct 5, 2026
e77ca41
Update doc/vendor/SKILL.md
teunbrand Oct 5, 2026
4664d25
code formatting
teunbrand Oct 5, 2026
48ea2ea
move table cell types out of execute into table::layout
teunbrand Oct 8, 2026
8b81de4
move TableColumn and TableRow into table::layout
teunbrand Oct 8, 2026
f92e235
move cell/section assembly out of execute into table::layout
teunbrand Oct 9, 2026
a47d085
move spanner code into the table module
teunbrand Oct 9, 2026
d8a9d9f
move extract_heading_labels and move_stub_columns into table::layout
teunbrand Oct 9, 2026
6887ea7
subsume Section into TableLayout, bundling columns with rows and cells
teunbrand Oct 9, 2026
383d817
route ResolvedTable and Writer::write_table through TableLayout
teunbrand Oct 9, 2026
9a24ebe
move row partitioning and header-order validation into TableLayout
teunbrand Oct 9, 2026
ead06e4
document TableLayout in the export list, API reference and changelog
teunbrand Oct 9, 2026
e31f233
move slot grid construction into TableLayout::slots_by_row
teunbrand Oct 9, 2026
755a453
bundle TableCell kind and classes into named constructors
teunbrand Oct 9, 2026
22c7441
Merge remote-tracking branch 'origin/main' into table-feature
teunbrand Oct 9, 2026
7cd0b12
Route TABULATE derived-table wraps through sql::select_from
teunbrand Oct 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
## [Unreleased]

### Added

- New `TABULATE` clause renders a query result as a table instead of a
plot. Tables are rendered via new `html` writer. `TABULATE` supports the
following clauses:
- `LABEL` sets (sub)titles, captions and overrides the display of column
labels.
- `SPAN` groups columns under a shared header cell (spanner).
- `FORMAT` configures how a column's cells are displayed.
- `Writer::write_table` renders a resolved table, taking the new public
`TableLayout` — the table's cells bundled with their column and row
metadata.

### Changed

- The reader/dialect layer was refactored around a central dialect registry with
Expand All @@ -16,9 +29,6 @@
the data carried microsecond timestamps (#569).
- Extent queries are now merged and ordered portably, fixing schema resolution
on MySQL and ClickHouse (#569).

### Fixed

- Fixed a parser bug that interpreted comment characters inside string literals
as initializing a comment (#555).
- Fixed a bug in stat_aggregate prevented transposed layers from properly
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ For details — module layout, traits, where extension points live — see [`src

## Building

**Prerequisite: `tree-sitter-cli`.** Any Rust build regenerates the parser from `grammar.js` via `tree-sitter-ggsql`'s build script, which runs `tree-sitter generate` and **fails if `tree-sitter-cli` is not on `PATH`**. Install it once with `npm install -g tree-sitter-cli`. To build against the committed `tree-sitter-ggsql/src/parser.c` without the CLI (e.g. if you're not touching the grammar), set `GGSQL_SKIP_GENERATE=1`.
**Prerequisite: `tree-sitter-cli`.** Any Rust build regenerates the parser from `grammar.js` via `tree-sitter-ggsql`'s build script, which runs `tree-sitter generate` and **fails if `tree-sitter-cli` is not on `PATH`**. Install it once with `npm install -g tree-sitter-cli`. To build against a pre-generated `tree-sitter-ggsql/src/parser.c` without the CLI (e.g. if you're not touching the grammar), set `GGSQL_SKIP_GENERATE=1`.

```sh
# Rust workspace (default members: tree-sitter-ggsql, src, ggsql-cli, ggsql-jupyter)
Expand Down
21 changes: 20 additions & 1 deletion doc/_quarto.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,15 @@ website:
href: syntax/clause/project.qmd
- text: "`LABEL`"
href: syntax/clause/label.qmd
- text: "---"
- text: "`TABULATE`"
href: syntax/clause/tabulate.qmd
- text: "`LABEL`"
href: syntax/clause/label-tabulate.qmd
- text: "`SPAN`"
href: syntax/clause/span.qmd
- text: "`FORMAT`"
href: syntax/clause/format.qmd
- text: Gallery
href: gallery/index.qmd
- href: faq.qmd
Expand Down Expand Up @@ -92,7 +101,7 @@ website:
contents:
- text: Overview
href: syntax/index.qmd
- section: "Main clauses"
- section: "Visualise clauses"
contents:
- text: "`VISUALISE`"
href: syntax/clause/visualise.qmd
Expand All @@ -108,6 +117,16 @@ website:
href: syntax/clause/project.qmd
- text: "`LABEL`"
href: syntax/clause/label.qmd
- section: "Tabulate clauses"
contents:
- text: "`TABULATE`"
href: syntax/clause/tabulate.qmd
- text: "`LABEL`"
href: syntax/clause/label-tabulate.qmd
- text: "`SPAN`"
href: syntax/clause/span.qmd
- text: "`FORMAT`"
href: syntax/clause/format.qmd
- section: Layers
contents:
- section: Types
Expand Down
142 changes: 131 additions & 11 deletions doc/ggsql.xml
Original file line number Diff line number Diff line change
Expand Up @@ -87,17 +87,8 @@
<item>DESC</item>
</list>

<!-- ggsql Main Clause Keywords (triggers context switch) -->
<list name="clause_keywords">
<item>DRAW</item>
<item>PLACE</item>
<item>SCALE</item>
<item>PROJECT</item>
<item>FACET</item>
<item>LABEL</item>
<item>VISUALISE</item>
<item>VISUALIZE</item>
</list>
<!-- Clause keywords are matched with inline WordDetect rules that
switch context; there is no shared clause_keywords list. -->

<!-- ggsql Sub-keywords -->
<list name="viz_subkeywords">
Expand All @@ -110,6 +101,8 @@
<item>RENAMING</item>
<item>TO</item>
<item>VIA</item>
<item>OVER</item>
Comment thread
teunbrand marked this conversation as resolved.
<item>UNDER</item>
</list>

<!-- Scale Type Modifiers (before aesthetic name in SCALE) -->
Expand Down Expand Up @@ -397,6 +390,7 @@
<WordDetect attribute="Keyword" context="ProjectClause" String="PROJECT" insensitive="true"/>
<WordDetect attribute="Keyword" context="FacetClause" String="FACET" insensitive="true"/>
<WordDetect attribute="Keyword" context="LabelClause" String="LABEL" insensitive="true"/>
<WordDetect attribute="Keyword" context="TabulateClause" String="TABULATE" insensitive="true"/>

<!-- Fat arrow operator -->
<StringDetect attribute="Operator" context="#stay" String="=&gt;"/>
Expand Down Expand Up @@ -444,6 +438,7 @@
<WordDetect attribute="Keyword" context="ProjectClause" String="PROJECT" insensitive="true"/>
<WordDetect attribute="Keyword" context="FacetClause" String="FACET" insensitive="true"/>
<WordDetect attribute="Keyword" context="LabelClause" String="LABEL" insensitive="true"/>
<WordDetect attribute="Keyword" context="TabulateClause" String="TABULATE" insensitive="true"/>
<WordDetect attribute="Keyword" context="Normal" String="VISUALISE" insensitive="true"/>
<WordDetect attribute="Keyword" context="Normal" String="VISUALIZE" insensitive="true"/>
<WordDetect attribute="Keyword" context="Normal" String="SELECT" insensitive="true"/>
Expand Down Expand Up @@ -484,6 +479,7 @@
<WordDetect attribute="Keyword" context="ProjectClause" String="PROJECT" insensitive="true"/>
<WordDetect attribute="Keyword" context="FacetClause" String="FACET" insensitive="true"/>
<WordDetect attribute="Keyword" context="LabelClause" String="LABEL" insensitive="true"/>
<WordDetect attribute="Keyword" context="TabulateClause" String="TABULATE" insensitive="true"/>
<WordDetect attribute="Keyword" context="Normal" String="VISUALISE" insensitive="true"/>
<WordDetect attribute="Keyword" context="Normal" String="VISUALIZE" insensitive="true"/>
<WordDetect attribute="Keyword" context="Normal" String="SELECT" insensitive="true"/>
Expand Down Expand Up @@ -523,6 +519,7 @@
<WordDetect attribute="Keyword" context="ProjectClause" String="PROJECT" insensitive="true"/>
<WordDetect attribute="Keyword" context="FacetClause" String="FACET" insensitive="true"/>
<WordDetect attribute="Keyword" context="LabelClause" String="LABEL" insensitive="true"/>
<WordDetect attribute="Keyword" context="TabulateClause" String="TABULATE" insensitive="true"/>
<WordDetect attribute="Keyword" context="Normal" String="VISUALISE" insensitive="true"/>
<WordDetect attribute="Keyword" context="Normal" String="VISUALIZE" insensitive="true"/>
<WordDetect attribute="Keyword" context="Normal" String="SELECT" insensitive="true"/>
Expand Down Expand Up @@ -569,6 +566,7 @@
<WordDetect attribute="Keyword" context="ProjectClause" String="PROJECT" insensitive="true"/>
<WordDetect attribute="Keyword" context="FacetClause" String="FACET" insensitive="true"/>
<WordDetect attribute="Keyword" context="LabelClause" String="LABEL" insensitive="true"/>
<WordDetect attribute="Keyword" context="TabulateClause" String="TABULATE" insensitive="true"/>
<WordDetect attribute="Keyword" context="Normal" String="VISUALISE" insensitive="true"/>
<WordDetect attribute="Keyword" context="Normal" String="VISUALIZE" insensitive="true"/>
<WordDetect attribute="Keyword" context="Normal" String="SELECT" insensitive="true"/>
Expand Down Expand Up @@ -612,6 +610,7 @@
<WordDetect attribute="Keyword" context="ProjectClause" String="PROJECT" insensitive="true"/>
<WordDetect attribute="Keyword" context="FacetClause" String="FACET" insensitive="true"/>
<WordDetect attribute="Keyword" context="LabelClause" String="LABEL" insensitive="true"/>
<WordDetect attribute="Keyword" context="TabulateClause" String="TABULATE" insensitive="true"/>
<WordDetect attribute="Keyword" context="Normal" String="VISUALISE" insensitive="true"/>
<WordDetect attribute="Keyword" context="Normal" String="VISUALIZE" insensitive="true"/>
<WordDetect attribute="Keyword" context="Normal" String="SELECT" insensitive="true"/>
Expand Down Expand Up @@ -654,6 +653,9 @@
<WordDetect attribute="Keyword" context="ProjectClause" String="PROJECT" insensitive="true"/>
<WordDetect attribute="Keyword" context="FacetClause" String="FACET" insensitive="true"/>
<WordDetect attribute="Keyword" context="LabelClause" String="LABEL" insensitive="true"/>
<WordDetect attribute="Keyword" context="TabulateClause" String="TABULATE" insensitive="true"/>
<WordDetect attribute="Keyword" context="SpanClause" String="SPAN" insensitive="true"/>
<WordDetect attribute="Keyword" context="FormatClause" String="FORMAT" insensitive="true"/>
<WordDetect attribute="Keyword" context="Normal" String="VISUALISE" insensitive="true"/>
<WordDetect attribute="Keyword" context="Normal" String="VISUALIZE" insensitive="true"/>
<WordDetect attribute="Keyword" context="Normal" String="SELECT" insensitive="true"/>
Expand All @@ -676,6 +678,123 @@
<DetectChar char="," attribute="Symbol" context="#stay"/>
</context>

<!-- TABULATE clause context -->
<context name="TabulateClause" attribute="Normal Text" lineEndContext="#stay">
<Detect2Chars char="-" char1="-" attribute="Comment" context="Comment"/>
<Detect2Chars char="/" char1="*" attribute="Comment" context="CommentMulti" beginRegion="comment"/>
<DetectChar char="'" attribute="String" context="StringSingle"/>
<DetectChar char="&quot;" attribute="String" context="StringDouble"/>
<RegExpr attribute="Number" context="#stay" String="-?[0-9]+\.?[0-9]*([eE][+-]?[0-9]+)?"/>

<!-- Exit to other clause contexts -->
<WordDetect attribute="Keyword" context="LabelClause" String="LABEL" insensitive="true"/>
<WordDetect attribute="Keyword" context="TabulateClause" String="TABULATE" insensitive="true"/>
<WordDetect attribute="Keyword" context="SpanClause" String="SPAN" insensitive="true"/>
<WordDetect attribute="Keyword" context="FormatClause" String="FORMAT" insensitive="true"/>
<WordDetect attribute="Keyword" context="Normal" String="VISUALISE" insensitive="true"/>
<WordDetect attribute="Keyword" context="Normal" String="VISUALIZE" insensitive="true"/>
<WordDetect attribute="Keyword" context="Normal" String="SELECT" insensitive="true"/>
<WordDetect attribute="Keyword" context="#stay" String="FROM" insensitive="true"/>
<WordDetect attribute="Keyword" context="Normal" String="WHERE" insensitive="true"/>
<WordDetect attribute="Keyword" context="Normal" String="WITH" insensitive="true"/>

<!-- AS for column renames (value AS name) -->
<WordDetect attribute="Keyword" context="#stay" String="AS" insensitive="true"/>

<!-- Wildcard column selection -->
<DetectChar char="*" attribute="Operator" context="#stay"/>

<StringDetect attribute="Operator" context="#stay" String="=&gt;"/>
<AnyChar attribute="Operator" context="#stay" String="=!&lt;&gt;+-*/%"/>
<DetectChar char="(" attribute="Symbol" context="#stay"/>
<DetectChar char=")" attribute="Symbol" context="#stay"/>
<DetectChar char="[" attribute="Symbol" context="#stay"/>
<DetectChar char="]" attribute="Symbol" context="#stay"/>
<DetectChar char="," attribute="Symbol" context="#stay"/>
</context>

<!-- SPAN clause context -->
<context name="SpanClause" attribute="Normal Text" lineEndContext="#stay">
<Detect2Chars char="-" char1="-" attribute="Comment" context="Comment"/>
<Detect2Chars char="/" char1="*" attribute="Comment" context="CommentMulti" beginRegion="comment"/>
<DetectChar char="'" attribute="String" context="StringSingle"/>
<DetectChar char="&quot;" attribute="String" context="StringDouble"/>
<RegExpr attribute="Number" context="#stay" String="-?[0-9]+\.?[0-9]*([eE][+-]?[0-9]+)?"/>

<!-- Exit to other clause contexts -->
<WordDetect attribute="Keyword" context="LabelClause" String="LABEL" insensitive="true"/>
<WordDetect attribute="Keyword" context="TabulateClause" String="TABULATE" insensitive="true"/>
<WordDetect attribute="Keyword" context="SpanClause" String="SPAN" insensitive="true"/>
<WordDetect attribute="Keyword" context="FormatClause" String="FORMAT" insensitive="true"/>
<WordDetect attribute="Keyword" context="Normal" String="VISUALISE" insensitive="true"/>
<WordDetect attribute="Keyword" context="Normal" String="VISUALIZE" insensitive="true"/>
<WordDetect attribute="Keyword" context="Normal" String="SELECT" insensitive="true"/>
<WordDetect attribute="Keyword" context="#stay" String="FROM" insensitive="true"/>
<WordDetect attribute="Keyword" context="Normal" String="WHERE" insensitive="true"/>
<WordDetect attribute="Keyword" context="Normal" String="WITH" insensitive="true"/>

<!-- OVER/UNDER and SETTING (only SETTING supported here, not FILTER/PARTITION/ORDER) -->
<WordDetect attribute="Keyword" context="#stay" String="OVER" insensitive="true"/>
<WordDetect attribute="Keyword" context="#stay" String="UNDER" insensitive="true"/>
<WordDetect attribute="Keyword" context="#stay" String="SETTING" insensitive="true"/>

<!-- SPAN's own SETTING keys -->
<WordDetect attribute="Attribute" context="#stay" String="gather" insensitive="true"/>
<WordDetect attribute="Attribute" context="#stay" String="level" insensitive="true"/>

<StringDetect attribute="Operator" context="#stay" String="=&gt;"/>
<AnyChar attribute="Operator" context="#stay" String="=!&lt;&gt;+-*/%"/>
<DetectChar char="(" attribute="Symbol" context="#stay"/>
<DetectChar char=")" attribute="Symbol" context="#stay"/>
<DetectChar char="[" attribute="Symbol" context="#stay"/>
<DetectChar char="]" attribute="Symbol" context="#stay"/>
<DetectChar char="," attribute="Symbol" context="#stay"/>
</context>

<!-- FORMAT clause context -->
<context name="FormatClause" attribute="Normal Text" lineEndContext="#stay">
<Detect2Chars char="-" char1="-" attribute="Comment" context="Comment"/>
<Detect2Chars char="/" char1="*" attribute="Comment" context="CommentMulti" beginRegion="comment"/>
<DetectChar char="'" attribute="String" context="StringSingle"/>
<DetectChar char="&quot;" attribute="String" context="StringDouble"/>
<RegExpr attribute="Number" context="#stay" String="-?[0-9]+\.?[0-9]*([eE][+-]?[0-9]+)?"/>

<!-- Exit to other clause contexts -->
<WordDetect attribute="Keyword" context="LabelClause" String="LABEL" insensitive="true"/>
<WordDetect attribute="Keyword" context="TabulateClause" String="TABULATE" insensitive="true"/>
<WordDetect attribute="Keyword" context="SpanClause" String="SPAN" insensitive="true"/>
<WordDetect attribute="Keyword" context="FormatClause" String="FORMAT" insensitive="true"/>
<WordDetect attribute="Keyword" context="Normal" String="VISUALISE" insensitive="true"/>
<WordDetect attribute="Keyword" context="Normal" String="VISUALIZE" insensitive="true"/>
<WordDetect attribute="Keyword" context="Normal" String="SELECT" insensitive="true"/>
<WordDetect attribute="Keyword" context="#stay" String="FROM" insensitive="true"/>
<WordDetect attribute="Keyword" context="Normal" String="WHERE" insensitive="true"/>
<WordDetect attribute="Keyword" context="Normal" String="WITH" insensitive="true"/>

<!-- SETTING and RENAMING (only these two supported here) -->
<WordDetect attribute="Keyword" context="#stay" String="SETTING" insensitive="true"/>
<WordDetect attribute="Keyword" context="#stay" String="RENAMING" insensitive="true"/>

<!-- FORMAT target identifiers (default BODY) -->
<WordDetect attribute="Keyword" context="#stay" String="BODY" insensitive="true"/>
<WordDetect attribute="Keyword" context="#stay" String="STUB" insensitive="true"/>

<!-- FORMAT's own SETTING keys -->
<WordDetect attribute="Attribute" context="#stay" String="hjust" insensitive="true"/>
<WordDetect attribute="Attribute" context="#stay" String="width" insensitive="true"/>

<!-- Wildcard for RENAMING -->
<DetectChar char="*" attribute="Operator" context="#stay"/>

<StringDetect attribute="Operator" context="#stay" String="=&gt;"/>
<AnyChar attribute="Operator" context="#stay" String="=!&lt;&gt;+-*/%"/>
<DetectChar char="(" attribute="Symbol" context="#stay"/>
<DetectChar char=")" attribute="Symbol" context="#stay"/>
<DetectChar char="[" attribute="Symbol" context="#stay"/>
<DetectChar char="]" attribute="Symbol" context="#stay"/>
<DetectChar char="," attribute="Symbol" context="#stay"/>
</context>

<!-- VISUALISE clause context -->
<context name="VisualiseClause" attribute="Normal Text" lineEndContext="#stay">
<Detect2Chars char="-" char1="-" attribute="Comment" context="Comment"/>
Expand All @@ -691,6 +810,7 @@
<WordDetect attribute="Keyword" context="ProjectClause" String="PROJECT" insensitive="true"/>
<WordDetect attribute="Keyword" context="FacetClause" String="FACET" insensitive="true"/>
<WordDetect attribute="Keyword" context="LabelClause" String="LABEL" insensitive="true"/>
<WordDetect attribute="Keyword" context="TabulateClause" String="TABULATE" insensitive="true"/>
<WordDetect attribute="Keyword" context="VisualiseClause" String="VISUALISE" insensitive="true"/>
<WordDetect attribute="Keyword" context="VisualiseClause" String="VISUALIZE" insensitive="true"/>
<WordDetect attribute="Keyword" context="Normal" String="SELECT" insensitive="true"/>
Expand Down
Loading
Loading