Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 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
6687e10
minor cosmetic polishes
teunbrand Oct 27, 2026
d9c3cd9
Multiple `FORMAT`s merge instead of last-one-wins (#572)
teunbrand Nov 2, 2026
1679ab1
Merge remote-tracking branch 'origin/main' into table-feature
teunbrand Nov 2, 2026
6f65622
use new label method
teunbrand Nov 2, 2026
d8319a6
dedup spanner columns
teunbrand Nov 2, 2026
609ae14
Guard against `SETTING level => null` panic
teunbrand Nov 2, 2026
456aa84
fix git shennanigans with test folder
teunbrand Nov 2, 2026
b3c7eab
Add note for reserved word
teunbrand Nov 2, 2026
653009c
restructure `SelectionItem`
teunbrand Nov 3, 2026
278eace
change default writer mode to inline
teunbrand Nov 3, 2026
913bb8a
wasm emits tables
teunbrand Nov 3, 2026
0bd08dc
add caption-side: bottom
teunbrand Nov 3, 2026
8450128
Use counter for anonymous span ids
teunbrand Nov 3, 2026
1e2900b
rework unit number constraint
teunbrand Nov 3, 2026
f3aff89
improve formatting
teunbrand Nov 3, 2026
89c7858
Harden TableLayout cell/row invariants
teunbrand Nov 3, 2026
12dfb95
Address FORMAT review comments
teunbrand Nov 3, 2026
93a6785
Validate only the resolved first spec
teunbrand Nov 3, 2026
6287e0a
Fix CLI html writer surface, add Writer::render_plot
teunbrand Nov 3, 2026
2faed7f
Drop the text/plain placeholder from Jupyter table output
teunbrand Nov 3, 2026
09e09df
improve accessibility features
teunbrand Nov 4, 2026
ecabdde
Apply batched suggestions from code review
teunbrand Nov 4, 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
2 changes: 1 addition & 1 deletion AGENTS.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
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@

### 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.
- The Positron extension now registers a ggsql data importer, so dragging a
csv/tsv/parquet/json file into Positron offers to generate the ggsql code
that loads it into a table, including any filters and sorts shown in the
Expand All @@ -11,6 +21,9 @@

### Changed

- **Breaking**: `TABULATE` is now a reserved word after `VISUALISE`/`TABULATE`,
like `VISUALISE`. Reference a column named `tabulate` there quoted
(`'tabulate'`); the SQL part of a query is unaffected.
- The reader/dialect layer was refactored around a central dialect registry with
per-database dialect implementations (#569). As part of this, some
previously-public items changed: the `CacheBackend` trait moved to a
Expand All @@ -19,6 +32,8 @@
- Scale resolution now detects and converts temporal-unit mismatches and errors
on unrepresentable ranges instead of producing silently wrong scales (#569).
- `Plot.labels` field is no longer `Option<Labels>` but plain `Labels`.
- The CLI now always prints query warnings (e.g. extra `VISUALISE`/`TABULATE`
statements being ignored) to stderr, not only with `-v`.
- Replaced `CLAUDE.md` files with `AGENTS.md` files.

### Fixed
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
151 changes: 140 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.
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,132 @@
<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"/>

<!-- Constants -->
<keyword attribute="Constant" context="#stay" String="constants"/>

<!-- 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"/>

<!-- Constants (SPAN NULL OVER ... for anonymous spanners) -->
<keyword attribute="Constant" context="#stay" String="constants"/>

<!-- 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"/>

<!-- Constants -->
<keyword attribute="Constant" context="#stay" String="constants"/>

<!-- 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 +819,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