Skip to content

feat(history): add typed inherent methods and JSON filter to SqliteBackedHistory#1112

Open
eitsupi wants to merge 8 commits into
nushell:mainfrom
eitsupi:feat/typed-history-extra-info
Open

feat(history): add typed inherent methods and JSON filter to SqliteBackedHistory#1112
eitsupi wants to merge 8 commits into
nushell:mainfrom
eitsupi:feat/typed-history-extra-info

Conversation

@eitsupi

@eitsupi eitsupi commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

Summary

Three typed inherent methods are added to SqliteBackedHistory (save_with_extra, load_with_extra, search_with_extra) for round-tripping more_info without type erasure. History::save now delegates to save_with_extra, so both paths share the same implementation. A new JsonFilterValue enum and SearchFilter::more_info_json field enable type-precise filtering by JSON path at the SQL level. The History trait is unchanged.

Motivation

#1011 made HistoryItem generic over ExtraInfo and exposed HistoryItemExtraInfo publicly. However, the History trait still uses the type-erased HistoryItem<IgnoreAllExtraInfo>, so any more_info written through the trait is silently discarded on the next read. The generics are in place, but there is no usable end-to-end path to preserve custom metadata.

Downstream crates that want to persist typed metadata in more_info necessarily hold SqliteBackedHistory directly, since FileBackedHistory has no more_info support at all. Typed inherent methods on SqliteBackedHistory model this capability boundary precisely, without changing the History trait.

@eitsupi eitsupi force-pushed the feat/typed-history-extra-info branch from 79faf98 to a04be41 Compare June 28, 2026 10:16
eitsupi and others added 6 commits June 28, 2026 10:17
Add `save_with_extra`, `load_with_extra`, and `search_with_extra` as
typed inherent methods on `SqliteBackedHistory`. These methods allow
downstream crates to round-trip custom `more_info` types through the
SQLite backend without going through the `History` trait.

Also add `more_info_json` to `SearchFilter` for SQL-level JSON filtering
using SQLite's `json_extract()`. This avoids fetching all rows and
filtering in Rust when only a subset matching a JSON path condition is
needed. Boolean JSON values compare as integers (`"1"` = true, `"0"` =
false) because SQLite's `json_extract` maps JSON booleans to integers.

The `History` trait itself remains unchanged (non-breaking).

Motivation: downstream crates that hold `SqliteBackedHistory` directly
(rather than `Box<dyn History>`) can now store and query typed metadata
alongside history entries. Crates using `FileBackedHistory` or
`Box<dyn History>` are unaffected.
…r booleans

History::count() and History::search() now clear more_info_json before
calling construct_query, matching the documented contract that non-typed
trait methods do not evaluate JSON filters.

For more_info_json filter values of "true" or "false", use SQLite's
json_type() instead of CAST(json_extract(...) AS TEXT) to match JSON
boolean values exactly. This prevents false matches against integer 1/0
and string "true"/"false". Other filter values continue to use CAST.
…alue enum

Add `JsonFilterValue` enum with `Null`, `Bool`, `Integer`, `Real`, and `Text`
variants so that `SearchFilter::more_info_json` filters are type-precise.
Each variant generates SQL using `json_type()` to prevent collisions between
JSON booleans, integers, and strings (e.g. `Bool(true)` no longer matches
integer `1` or string `"true"`).

Export `JsonFilterValue` from the crate root. Add collision tests covering
every scalar type combination.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add json_filter_value_real_vs_integer to verify Real(f) does not collide
with Integer values sharing the same numeric representation, and
json_filter_value_null_vs_missing_path to verify Null matches JSON null
at a path but not SQL-NULL more_info or an absent path.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
json_filter_value_real_vs_integer: add an integer row at $.score to verify
Real(1.0) excludes it (json_type 'integer' != 'real') while Integer(1)
correctly matches it but not the real rows.

json_filter_value_null_vs_missing_path: add a row whose non-null JSON
has no $.val key (WithoutVal struct) to verify JsonFilterValue::Null
excludes it alongside the SQL-NULL more_info row.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@eitsupi eitsupi force-pushed the feat/typed-history-extra-info branch from a04be41 to 6f3efef Compare June 28, 2026 10:17
eitsupi and others added 2 commits June 28, 2026 10:25
Keep BoxedNamedParams as &'static str keys (unchanged from upstream) to
avoid adding .to_string() to every existing params.push call. Dynamic
JSON filter parameters (":json_path_N" etc.) are collected in a separate
OwnedNamedParams vec and merged at the end of construct_query.

Also remove the spurious Vec<&str> type annotation on `wheres`.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
History::save now delegates to save_with_extra rather than a private
save_impl, removing the unnecessary indirection layer.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@eitsupi eitsupi marked this pull request as ready for review June 28, 2026 10:33
@fdncred

fdncred commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

cool. i remember adding more_info thinking someday someone would use it. what are you using it for?

@eitsupi

eitsupi commented Jun 28, 2026

Copy link
Copy Markdown
Contributor Author

what are you using it for?

My priorities were distinguishing between special meta-commands available within the REPL and recording the REPL's output.
https://github.com/eitsupi/arf/blob/d95cd3f875fd495909034050e3b1884e9291bde1/crates/arf-console/src/history/search.rs#L96-L100

Furthermore, I imagine this would be useful for this tool with REPL that can connect to various databases, such as for distinguishing between different databases.
https://github.com/columnar-tech/databow

@fdncred

fdncred commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

cool. thanks for sharing.

@eitsupi

eitsupi commented Jun 30, 2026

Copy link
Copy Markdown
Contributor Author

My first use case: eitsupi/arf#236

@fdncred

fdncred commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

@kronberger-droid I'd probably wait on this one too until after the release unless you feel strongly about landing it now.

@kronberger-droid

kronberger-droid commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator

Yeah agreed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants