Skip to content

Commit 59de709

Browse files
TimelordUKclaude
andcommitted
feat(nvim): Add smart column expansion and distinct values analysis
## Smart Star Expansion (\sE) - Execute queries with LIMIT 1 to get actual schema from CTEs/subqueries - Support both array-of-objects and object-with-columns JSON formats - Auto-insert column hint comments for Ctrl+N completion - Fallback to static file hints when query execution fails - Configuration: smart_expansion.enabled, auto_insert_column_hints ## Distinct Values Analysis (\srD) - New --distinct-column CLI flag for cardinality analysis - Automatically detects and preserves WEB CTE context - Extracts CTE definition and appends GROUP BY aggregation - Displays top 100 distinct values with counts in floating window - Works with any query context (CTEs, subqueries, files) ## Implementation - Rust: CTE-aware query rewriting with parenthesis depth tracking - Nvim: Simplified to call CLI and parse CSV output - Uses vim.schedule() for proper event context handling 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 4716d45 commit 59de709

10 files changed

Lines changed: 1311 additions & 188 deletions

File tree

docs/NVIM_SMART_COLUMN_COMPLETION.md

Lines changed: 441 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
# Smart Column Expansion and Completion Features
2+
3+
## New Features
4+
5+
### 1. Smart * Expansion (`\sE`)
6+
7+
The `\sE` keybinding now intelligently expands `SELECT *` by **executing the query** to get the actual column schema, instead of relying on static data file hints.
8+
9+
#### What's New
10+
11+
**Before:**
12+
- Only worked with `-- #! data_file.csv` hints
13+
- Couldn't handle CTEs, subqueries, or joins
14+
- Limited to direct table access
15+
16+
**After:**
17+
- Works with CTEs, subqueries, joins, and complex queries
18+
- Executes `LIMIT 0` version of your query to get schema
19+
- Falls back to static schema if execution fails
20+
- Can be toggled on/off in config
21+
22+
#### Usage
23+
24+
1. Write a query with `SELECT *`:
25+
```sql
26+
WITH sales AS (
27+
SELECT * FROM data WHERE amount > 100
28+
)
29+
SELECT * FROM sales
30+
```
31+
32+
2. Put cursor on the `SELECT *` line you want to expand
33+
34+
3. Press `\sE` (or `:SqlCliExpandStar`)
35+
36+
4. The plugin:
37+
- Captures the full query context (including the CTE)
38+
- Executes it with `LIMIT 0` to get column names
39+
- Replaces `*` with actual columns
40+
- Adds a column hint comment for completion (see below)
41+
42+
#### Result
43+
44+
```sql
45+
-- Columns: order_id, customer_id, amount, date, region
46+
47+
WITH sales AS (
48+
SELECT * FROM data WHERE amount > 100
49+
)
50+
SELECT
51+
order_id
52+
, customer_id
53+
, amount
54+
, date
55+
, region
56+
FROM sales
57+
```
58+
59+
### 2. Auto Column Hint Comments
60+
61+
After expanding `*`, a special comment is automatically inserted at the top of your buffer:
62+
63+
```sql
64+
-- Columns: order_id, customer_id, amount, date, region
65+
```
66+
67+
#### Why This Matters
68+
69+
Neovim's default word completion (`Ctrl+N`) looks for words in the **current buffer**. Since query results are in a different buffer, column names weren't available for completion.
70+
71+
The hint comment solves this by adding column names to your SQL buffer!
72+
73+
#### Usage
74+
75+
1. After expanding *, the hint comment is added automatically
76+
77+
2. Start typing a column name and press `Ctrl+N`:
78+
```sql
79+
SELECT ord<Ctrl+N>
80+
```
81+
82+
3. Neovim suggests: `order_id` (from the hint comment!)
83+
84+
#### Configuration
85+
86+
```lua
87+
require('sql-cli').setup({
88+
smart_expansion = {
89+
enabled = true, -- Enable smart expansion
90+
auto_insert_column_hints = true, -- Auto-add hint comments
91+
auto_sync_column_hints = true, -- Sync after query execution
92+
fallback_to_static = true, -- Fall back if query fails
93+
}
94+
})
95+
```
96+
97+
### 3. Column Hint Syncing
98+
99+
When you execute a query and get results, the plugin can automatically update the column hint comment with the result columns.
100+
101+
This keeps your completion suggestions in sync with your actual query results!
102+
103+
#### Configuration
104+
105+
Set `auto_sync_column_hints = true` in config (enabled by default).
106+
107+
### 4. Distinct Values Display (`\srD`)
108+
109+
This feature already existed but may not have been easily discoverable.
110+
111+
#### Usage
112+
113+
1. Put cursor on a column name in your query
114+
2. Press `\srD`
115+
3. A floating window shows distinct values and their counts
116+
117+
#### Example
118+
119+
Cursor on `region`:
120+
121+
```
122+
╔═══ Distinct values for 'region' ═══╗
123+
║ Total distinct values: 4 ║
124+
║ ║
125+
║ Value Count ║
126+
║ ───────────────────────── ║
127+
║ East 1,234 ║
128+
║ West 1,156 ║
129+
║ North 892 ║
130+
║ South 743 ║
131+
╚═════════════════════════════════════╝
132+
```
133+
134+
## Configuration
135+
136+
### Full Config Example
137+
138+
```lua
139+
require('sql-cli').setup({
140+
-- Smart expansion features
141+
smart_expansion = {
142+
enabled = true, -- Use smart expansion (query execution)
143+
auto_insert_column_hints = true, -- Add column hints for completion
144+
auto_sync_column_hints = true, -- Sync hints after query execution
145+
fallback_to_static = true, -- Fall back to --schema-json if query fails
146+
},
147+
148+
-- Keymaps
149+
keymaps = {
150+
expand_star = '<leader>sE', -- Smart * expansion
151+
-- ... other keymaps
152+
}
153+
})
154+
```
155+
156+
### Disable Smart Expansion
157+
158+
To use the old static expansion:
159+
160+
```lua
161+
smart_expansion = {
162+
enabled = false, -- Use old expand_star_columns behavior
163+
}
164+
```
165+
166+
### Disable Column Hints
167+
168+
To expand * without adding hint comments:
169+
170+
```lua
171+
smart_expansion = {
172+
enabled = true,
173+
auto_insert_column_hints = false, -- Don't add hints
174+
}
175+
```
176+
177+
## Keybindings Summary
178+
179+
| Key | Description |
180+
|-----------|--------------------------------------|
181+
| `\sE` | Smart SELECT * expansion |
182+
| `\srD` | Show distinct values for column |
183+
| `Ctrl+N` | Autocomplete (use with column hints) |
184+
185+
## Technical Details
186+
187+
### How Smart Expansion Works
188+
189+
1. **Query Capture**: Finds the complete query context (including CTEs, subqueries)
190+
2. **Preview Execution**: Runs query with `LIMIT 0` to get schema
191+
3. **Column Extraction**: Parses JSON result to get column names
192+
4. **Replacement**: Replaces `*` with formatted column list
193+
5. **Hint Addition**: Adds comment for completion (if enabled)
194+
195+
### Query Context Detection
196+
197+
The plugin looks backward/forward to find query boundaries:
198+
- Stops at `GO` statements
199+
- Captures `WITH` clauses at the start
200+
- Includes everything up to the next `GO`
201+
202+
This means it correctly handles:
203+
- Multiple CTEs
204+
- Nested subqueries
205+
- JOIN operations
206+
- Complex WHERE clauses
207+
208+
### Fallback Behavior
209+
210+
If smart expansion fails (query error, syntax issue):
211+
1. Shows error message
212+
2. Falls back to static `--schema-json` if `fallback_to_static = true`
213+
3. Uses data file hint or opened CSV as before
214+
215+
## Testing
216+
217+
See `test_smart_expansion.sql` for examples:
218+
219+
```bash
220+
# Open in neovim
221+
nvim test_smart_expansion.sql
222+
223+
# Try \sE on each SELECT * line
224+
# Try \srD on column names
225+
# Try Ctrl+N after expansion to test completion
226+
```
227+
228+
## Troubleshooting
229+
230+
### "Could not determine query context"
231+
232+
- Make sure your cursor is on or near a `SELECT *` line
233+
- Check that the query is properly formatted (not in middle of CTE)
234+
235+
### "Query execution failed"
236+
237+
- Query may have syntax errors
238+
- Data file might not be set (use `:SqlCliSetData`)
239+
- Try the query manually first with `:SqlCliExecute`
240+
241+
### Column hints not appearing
242+
243+
- Check `smart_expansion.auto_insert_column_hints = true`
244+
- Verify expansion actually ran (check for notification)
245+
- Look for `-- Columns:` comment at top of buffer
246+
247+
### Completion not working
248+
249+
- Press `Ctrl+N` after typing partial column name
250+
- Make sure column hint comment exists in buffer
251+
- Neovim's completion looks at **current buffer** only
252+
253+
## Future Enhancements
254+
255+
Possible improvements:
256+
- LSP integration for true cross-buffer completion
257+
- Column hints from results buffer without expansion
258+
- Intelligent column ordering (frequently used first)
259+
- Type hints in completion (String, Integer, etc.)
260+
- Schema caching to avoid repeated executions
261+
262+
## Related Documentation
263+
264+
- Main plugin README: `README.md`
265+
- Keybindings: `KEYBINDINGS.md`
266+
- Design doc: `../docs/NVIM_SMART_COLUMN_COMPLETION.md`

nvim-plugin/examples/large_dataset_config.lua

Lines changed: 33 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -3,46 +3,46 @@
33
-- may appear later in the data (e.g., FIX file selectors with 3k+ rows)
44

55
require('sql-cli').setup({
6-
command = "sql-cli", -- Or full path like "/home/user/sql-cli/target/release/sql-cli"
6+
command = "sql-cli", -- Or full path like "/home/user/sql-cli/target/release/sql-cli"
77

8-
-- Split configuration
9-
split = {
10-
direction = "vertical",
11-
size = 0.5,
12-
},
8+
-- Split configuration
9+
split = {
10+
direction = "vertical",
11+
size = 0.5,
12+
},
1313

14-
-- Output format
15-
output_format = "table",
14+
-- Output format
15+
output_format = "table",
1616

17-
-- Table output settings optimized for large datasets
18-
table_output = {
19-
-- Set to 0 for unlimited column width (no truncation)
20-
-- Or set to a higher value like 150 if you want some limit
21-
max_col_width = 0,
17+
-- Table output settings optimized for large datasets
18+
table_output = {
19+
-- Set to 0 for unlimited column width (no truncation)
20+
-- Or set to a higher value like 150 if you want some limit
21+
max_col_width = 0,
2222

23-
-- Set to 0 to scan ALL rows for column width calculation
24-
-- This ensures wide values appearing late in the dataset are not truncated
25-
-- Note: This may be slower for very large datasets (10k+ rows)
26-
col_sample_rows = 0,
23+
-- Set to 0 to scan ALL rows for column width calculation
24+
-- This ensures wide values appearing late in the dataset are not truncated
25+
-- Note: This may be slower for very large datasets (10k+ rows)
26+
col_sample_rows = 0,
2727

28-
-- Hide columns that are entirely NULL/empty
29-
-- Useful for sparse datasets (e.g., spot trades with empty option columns)
30-
auto_hide_empty = true,
31-
},
28+
-- Hide columns that are entirely NULL/empty
29+
-- Useful for sparse datasets (e.g., spot trades with empty option columns)
30+
auto_hide_empty = true,
31+
},
3232

33-
-- Auto-detect features
34-
auto_detect = {
35-
csv_files = true,
36-
data_hints = true,
37-
},
33+
-- Auto-detect features
34+
auto_detect = {
35+
csv_files = true,
36+
data_hints = true,
37+
},
3838

39-
-- Output settings
40-
output = {
41-
focus_on_run = false,
42-
clear_on_run = true,
43-
wrap = false,
44-
number = false,
45-
},
39+
-- Output settings
40+
output = {
41+
focus_on_run = false,
42+
clear_on_run = true,
43+
wrap = false,
44+
number = false,
45+
},
4646
})
4747

4848
-- Example use cases:

nvim-plugin/lua/sql-cli/config.lua

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,14 @@ M.defaults = {
4646
data_hints = true, -- Auto-detect -- #!data: hints in SQL files
4747
},
4848

49+
-- Smart expansion features
50+
smart_expansion = {
51+
enabled = true, -- Enable smart * expansion (executes query to get columns)
52+
auto_insert_column_hints = true, -- Auto-insert column hint comments for completion
53+
auto_sync_column_hints = true, -- Sync columns from results buffer to query buffer
54+
fallback_to_static = true, -- Fall back to static schema if query execution fails
55+
},
56+
4957
-- Table navigation settings
5058
table_navigation = {
5159
enabled_by_default = false, -- Don't auto-enable table navigation (requires manual toggle)

nvim-plugin/lua/sql-cli/init.lua

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -188,8 +188,8 @@ function M.create_commands()
188188
end, { desc = "Open query results in new buffer" })
189189

190190
vim.api.nvim_create_user_command("SqlCliExpandStar", function()
191-
results.expand_star_columns(M.config, M.state)
192-
end, { desc = "Expand SELECT * to column names" })
191+
results.expand_star_smart(M.config, M.state)
192+
end, { desc = "Expand SELECT * to column names (smart)" })
193193

194194
vim.api.nvim_create_user_command("SqlCliToggleTableNav", function()
195195
table_nav.toggle_navigation(nil, M.config)
@@ -482,8 +482,8 @@ function M.setup_keymaps()
482482

483483
if keymaps.expand_star then
484484
vim.keymap.set("n", keymaps.expand_star, function()
485-
results.expand_star_columns(M.config, M.state)
486-
end, { desc = "Expand SELECT *", silent = true })
485+
results.expand_star_smart(M.config, M.state)
486+
end, { desc = "Expand SELECT * (smart)", silent = true })
487487

488488
vim.keymap.set("v", keymaps.expand_star, function()
489489
results.expand_star_visual(M.config, M.state)

0 commit comments

Comments
 (0)