Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 1 addition & 3 deletions .github/workflows/release-please.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,7 @@ permissions:

jobs:
release-please:
# Temporarily disabled until post-daemon/two-db-model work is complete.
# Re-enable by removing the `false &&` prefix below.
if: false && github.repository == 'tableau/hyper-api-rust'
if: github.repository == 'tableau/hyper-api-rust'
runs-on: ubuntu-latest
steps:
- uses: googleapis/release-please-action@v5
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ A **pure-Rust** implementation of the Hyper database API, using the PostgreSQL
wire protocol with Hyper-specific extensions. Create, read, and manipulate Hyper
database files (`.hyper`) without any C library dependencies.

> **Project Status — 0.1.x, AI-Engineered**
> **Project Status — 0.2.x, AI-Engineered**
>
> This crate was vibe-engineered with heavy use of AI coding assistants. The
> **0.1.x** line may still undergo large breaking changes; the public API
> won't settle until the 0.2.0 release.
> **0.2.x** line may still undergo large breaking changes; the public API
> won't settle until the 1.0.0 release.
>
> Contributors and reviewers should, at a minimum, run an **AI code reviewer**
> over any changes, following the conventions, layering rules, and patterns
Expand Down
29 changes: 29 additions & 0 deletions hyperdb-mcp/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,35 @@ and this project adheres to [Semantic Versioning](https://semver.org/).

### Fixed

- **Chart x-axis tick label thinning.** Long categorical line/scatter
charts (e.g. a 90-point hourly TIMESTAMP series) used to render with
only ONE visible x-axis label. The old logic blanked individual
labels at non-step indices, but `plotters` picks its own tick
*positions* on the float axis and rounds them to integer indices —
so almost none of the chosen ticks landed on a kept index, and the
formatter returned empty strings for the rest. The chart layer now
computes a target tick count from chart width and label sizes and
passes that count to `plotters` via `.x_labels(N)`; every drawn
tick carries its real label. Same `+00:00` suffix stripping for
shared TIMESTAMPTZ offsets is preserved (now isolated in
`strip_shared_tz_suffix`).
- **Line / scatter charts over `DATE`, `TIMESTAMP`, and `TIMESTAMPTZ`
columns now use a proportional time axis** instead of the previous
categorical-with-evenly-spaced-ticks behavior. Real-world time gaps
between data points are now reflected in the chart's x-axis: a
series at `2026-05-01 08:00`, `2026-05-01 12:30`, `2026-05-02 06:15`
shows the 4.5-hour and 17.75-hour gaps proportionally instead of
flattening every interval to the same width. Tick labels are
formatted via `chrono` in a form that matches the input kind:
`%Y-%m-%d` for DATE, `%Y-%m-%d %H:%M:%S` for TIMESTAMP, and
`%Y-%m-%d %H:%M:%S%:z` for TIMESTAMPTZ (the offset captured from the
first row, so a uniformly-`+05:30` series reports IST throughout).
Set `x_as_category: true` to opt out and force the previous
categorical layout (e.g. for charts where evenly-spaced bins are
more readable than proportional gaps). TEXT x columns continue to
render categorically as before. Bar charts are unaffected — they
remain categorical regardless of x type, which matches reader
expectations for grouped data.
- **Watcher recovery after hyperd restart.** The watcher's connection
pool now auto-rebuilds when a per-file ingest hits a connection-lost
error (typically after the daemon restarts hyperd). Each ingest gets
Expand Down
2 changes: 2 additions & 0 deletions hyperdb-mcp/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# hyperdb-mcp

> **Note:** This crate was vibe-engineered with heavy use of AI coding assistants. The 0.1.x line may still undergo large breaking changes; the public API won't settle until the 1.0.0 release.

An MCP (Model Context Protocol) server that turns the Hyper columnar database into an instant SQL analytics engine. Data flows in from other MCP plugins or files, lands in Hyper automatically, and becomes queryable with SQL — no setup, no schema files, no database management.

Built on the pure-Rust [`hyperdb-api`](../hyperdb-api/) crate for maximum performance: 22M+ rows/sec inserts, 18M+ rows/sec queries, constant memory for billion-row results.
Expand Down
29 changes: 21 additions & 8 deletions hyperdb-mcp/src/attach.rs
Original file line number Diff line number Diff line change
Expand Up @@ -727,6 +727,14 @@ pub fn validate_output_path(path: &str, kind: &str) -> Result<PathBuf, McpError>
format!("{kind} path '{path}' has no file-name component"),
)
})?;
if !parent.exists() {
std::fs::create_dir_all(parent).map_err(|e| {
McpError::new(
ErrorCode::InternalError,
format!("Failed to create parent directory for {kind} path '{path}': {e}"),
)
})?;
}
let canonical_parent = std::fs::canonicalize(parent).map_err(|e| {
McpError::new(
ErrorCode::FileNotFound,
Expand Down Expand Up @@ -924,14 +932,19 @@ mod tests {
}

#[test]
fn validate_output_path_rejects_missing_parent() {
// Build a platform-portable absolute path with a missing parent.
// Hardcoded "/definitely/..." paths are not absolute on Windows.
let missing_parent = std::env::temp_dir()
.join("hyper_mcp_validate_output_missing_parent_99999")
.join("out.csv");
let err = validate_output_path(missing_parent.to_str().unwrap(), "export").unwrap_err();
assert_eq!(err.code, ErrorCode::FileNotFound);
fn validate_output_path_creates_missing_parent() {
let parent = std::env::temp_dir().join("hyper_mcp_validate_output_missing_parent_99999");
// Clean up from any prior run.
let _ = std::fs::remove_dir_all(&parent);
assert!(!parent.exists());

let target = parent.join("out.csv");
let canonical =
validate_output_path(target.to_str().unwrap(), "export").expect("should create parent");
assert!(canonical.is_absolute());
assert!(parent.exists(), "parent directory should have been created");
// Clean up.
let _ = std::fs::remove_dir_all(&parent);
}

#[test]
Expand Down
Loading
Loading