Skip to content

Commit d33bf2e

Browse files
committed
fix: ai fixes
1 parent 25636f9 commit d33bf2e

File tree

4 files changed

+90
-3
lines changed

4 files changed

+90
-3
lines changed

CLAUDE.md

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Project Overview
6+
7+
Textplot is a DuckDB extension that provides text-based data visualization functions. It's a C++ extension built using the DuckDB extension template and CI tools, creating ASCII/Unicode charts directly from SQL queries.
8+
9+
## Build Commands
10+
11+
```bash
12+
# Build release version
13+
make release
14+
15+
# Build debug version
16+
make debug
17+
18+
# Run tests (requires build first)
19+
make test # runs against release build
20+
make test_debug # runs against debug build
21+
22+
# Format code
23+
make format
24+
25+
# Clean build artifacts
26+
make clean
27+
```
28+
29+
The build uses CMake under the hood. Build outputs go to `build/release/` or `build/debug/`.
30+
31+
## Architecture
32+
33+
### Extension Entry Point
34+
- `src/textplot_extension.cpp` - Registers all scalar functions with DuckDB via `LoadInternal()`
35+
36+
### SQL Functions
37+
Each visualization function has a corresponding implementation file:
38+
39+
| Function | Files | Purpose |
40+
|----------|-------|---------|
41+
| `tp_bar()` | `textplot_bar.cpp/.hpp` | Horizontal bar charts with thresholds and colors |
42+
| `tp_density()` | `textplot_density.cpp/.hpp` | Density plots/histograms from arrays |
43+
| `tp_sparkline()` | `textplot_sparkline.cpp/.hpp` | Compact trend lines with multiple modes |
44+
| `tp_qr()` | `textplot_qr.cpp/.hpp` | QR code generation |
45+
46+
### Pattern
47+
Each function follows the DuckDB scalar function pattern:
48+
- `Textplot*Bind()` - Validates and binds arguments at plan time
49+
- `Textplot*()` - Executes the function at runtime
50+
- Functions accept named parameters (e.g., `width := 20`, `style := 'ascii'`)
51+
52+
### Directory Structure
53+
- `src/` - C++ source files
54+
- `src/include/` - Header files
55+
- `test/sql/` - SQLLogicTest files (`.test` extension)
56+
- `duckdb/` - DuckDB submodule (git submodule)
57+
- `extension-ci-tools/` - Build system submodule
58+
59+
## Testing
60+
61+
Tests use DuckDB's SQLLogicTest format in `test/sql/`. See https://duckdb.org/dev/sqllogictest/intro.html for syntax.
62+
63+
## Dependencies
64+
65+
This extension uses vcpkg for dependency management. The `vcpkg.json` in the project root defines dependencies, and `extension_config.cmake` configures the extension build.

src/textplot_bar.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,13 @@ void TextplotBar(DataChunk &args, ExpressionState &state, Vector &result) {
234234
const auto &bind_data = func_expr.bind_info->Cast<TextplotBarBindData>();
235235

236236
UnaryExecutor::Execute<double, string_t>(value_vector, result, args.size(), [&](double value) {
237-
const auto proportion = std::clamp((value - bind_data.min) / (bind_data.max - bind_data.min), 0.0, 1.0);
237+
double proportion;
238+
if (bind_data.max == bind_data.min) {
239+
// Avoid division by zero: if value equals min/max, show full bar; otherwise empty
240+
proportion = (value >= bind_data.min) ? 1.0 : 0.0;
241+
} else {
242+
proportion = std::clamp((value - bind_data.min) / (bind_data.max - bind_data.min), 0.0, 1.0);
243+
}
238244
const auto filled_blocks = static_cast<int64_t>(std::round(bind_data.width * proportion));
239245

240246
string bar;

src/textplot_density.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,8 +187,11 @@ void TextplotDensity(DataChunk &args, ExpressionState &state, Vector &result) {
187187
// Count values in each bin
188188
for (const double val : data_items) {
189189
auto binIndex = static_cast<int>((val - minVal) / binWidth);
190+
// Clamp to valid range to handle floating point edge cases
191+
if (binIndex < 0)
192+
binIndex = 0;
190193
if (binIndex >= bind_data.width)
191-
binIndex = bind_data.width - 1; // Handle edge case
194+
binIndex = bind_data.width - 1;
192195
bins[binIndex]++;
193196
}
194197

@@ -208,6 +211,9 @@ void TextplotDensity(DataChunk &args, ExpressionState &state, Vector &result) {
208211
int markerPos = -1;
209212
if (!std::isnan(markerValue) && markerValue >= minVal && markerValue <= maxVal) {
210213
markerPos = static_cast<int>((markerValue - minVal) / binWidth);
214+
// Clamp to valid range to handle floating point edge cases
215+
if (markerPos < 0)
216+
markerPos = 0;
211217
if (markerPos >= bind_data.width)
212218
markerPos = bind_data.width - 1;
213219
}

src/textplot_sparkline.cpp

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,11 @@ std::string generateAbsoluteSparkline(const double *data, int size, int width,
115115

116116
if (max_val == min_val) {
117117
int mid_idx = characters.size() / 2;
118-
return std::string(width, characters[mid_idx][0]);
118+
std::string result;
119+
for (int i = 0; i < width; i++) {
120+
result += characters[mid_idx];
121+
}
122+
return result;
119123
}
120124

121125
std::string result;
@@ -125,10 +129,16 @@ std::string generateAbsoluteSparkline(const double *data, int size, int width,
125129
for (int i = 0; i < width; i++) {
126130
int start_idx = static_cast<int>(i * data_per_char);
127131
int end_idx = static_cast<int>((i + 1) * data_per_char);
132+
// Clamp indices to valid range
133+
if (start_idx >= size)
134+
start_idx = size - 1;
128135
if (end_idx > size)
129136
end_idx = size;
130137
if (start_idx >= end_idx)
131138
end_idx = start_idx + 1;
139+
// Final safety check: ensure we don't exceed array bounds
140+
if (end_idx > size)
141+
end_idx = size;
132142

133143
double sum = 0.0;
134144
for (int j = start_idx; j < end_idx; j++) {

0 commit comments

Comments
 (0)