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
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@
- fix `count_by_origin` and `count_by_form_action_origin` with punicode origins

### Places
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assuming this may conflict with #7101, so need to wait for that PR to merge.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved here: 60f5b82

- `places::storage::history_metadata::get_most_recent_search_entries()` was added to fetch the most recent search entries in history metadata. ([#7104](https://github.com/mozilla/application-services/pull/7104))
- `places::storage::history_metadata::delete_all_metadata_for_search()` was added to delete the search terms in history metadata. ([#7101](https://github.com/mozilla/application-services/pull/7101))


# v146.0 (_2025-11-10_)

## ✨ What's New ✨
Expand Down
8 changes: 8 additions & 0 deletions components/places/src/ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,14 @@ impl PlacesConnection {
self.with_conn(|conn| history_metadata::get_most_recent(conn, limit))
}

#[handle_error(crate::Error)]
pub fn get_most_recent_search_entries_in_history_metadata(
&self,
limit: i32,
) -> ApiResult<Vec<HistoryMetadata>> {
self.with_conn(|conn| history_metadata::get_most_recent_search_entries(conn, limit))
}

#[handle_error(crate::Error)]
pub fn query_history_metadata(
&self,
Expand Down
3 changes: 3 additions & 0 deletions components/places/src/places.udl
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ interface PlacesConnection {
[Throws=PlacesApiError]
sequence<HistoryMetadata> get_most_recent_history_metadata(i32 limit);

[Throws=PlacesApiError]
sequence<HistoryMetadata> get_most_recent_search_entries_in_history_metadata(i32 limit);

[Throws=PlacesApiError]
sequence<SearchResult> query_autocomplete(string search, i32 limit);

Expand Down
228 changes: 228 additions & 0 deletions components/places/src/storage/history_metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,13 @@ lazy_static! {
LIMIT :limit",
common_select_sql = COMMON_METADATA_SELECT
);
static ref SEARCH_QUERY_SQL: String = format!(
"{common_select_sql}
WHERE search_term NOT NULL
ORDER BY updated_at DESC
LIMIT :limit",
common_select_sql = COMMON_METADATA_SELECT
);
static ref QUERY_SQL: String = format!(
"{common_select_sql}
WHERE
Expand Down Expand Up @@ -507,6 +514,20 @@ pub fn get_most_recent(db: &PlacesDb, limit: i32) -> Result<Vec<HistoryMetadata>
)
}

// Returns the most recent history metadata entries where search term is not null (newest first),
// limited by `limit`.
//
// Internally this uses [`SEARCH_QUERY_SQL`], ordered by descending `updated_at`.
pub fn get_most_recent_search_entries(db: &PlacesDb, limit: i32) -> Result<Vec<HistoryMetadata>> {
db.query_rows_and_then_cached(
SEARCH_QUERY_SQL.as_str(),
rusqlite::named_params! {
":limit": limit,
},
HistoryMetadata::from_row,
)
}

pub fn get_highlights(
db: &PlacesDb,
weights: HistoryHighlightWeights,
Expand Down Expand Up @@ -1486,6 +1507,213 @@ mod tests {
assert_eq!(most_recents[2].url, "https://example.com/1");
}

#[test]
fn test_get_most_recent_search_entries_empty() {
let conn = PlacesDb::open_in_memory(ConnectionType::ReadWrite).expect("memory db");
let rows = get_most_recent_search_entries(&conn, 5).expect("query ok");
assert!(rows.is_empty());
}

#[test]
fn test_get_most_recent_search_entries_with_limits_and_same_observation() {
let conn = PlacesDb::open_in_memory(ConnectionType::ReadWrite).expect("memory db");

note_observation!(&conn,
url "http://mozilla.org/1/",
view_time None,
search_term Some("search_term_1"),
document_type None,
referrer_url None,
title None
);

bump_clock();

note_observation!(&conn,
url "http://mozilla.org/1/",
view_time None,
search_term Some("search_term_1"),
document_type None,
referrer_url None,
title None
);

bump_clock();

note_observation!(&conn,
url "http://mozilla.org/1/",
view_time None,
search_term Some("search_term_1"),
document_type None,
referrer_url None,
title None
);

// Limiting to 1 should return the most recent entry where search is not null.
let most_recents1 = get_most_recent_search_entries(&conn, 1).expect("query ok");
assert_eq!(most_recents1.len(), 1);
assert_eq!(most_recents1[0].url, "http://mozilla.org/1/");

// Limiting to 3 should also return one entry, since we only have one unique URL.
let most_recents2 = get_most_recent_search_entries(&conn, 3).expect("query ok");
assert_eq!(most_recents2.len(), 1);
assert_eq!(most_recents2[0].url, "http://mozilla.org/1/");

// Limiting to 10 should also return one entry, since we only have one unique URL.
let most_recents3 = get_most_recent_search_entries(&conn, 10).expect("query ok");
assert_eq!(most_recents3.len(), 1);
assert_eq!(most_recents3[0].url, "http://mozilla.org/1/");
}

#[test]
fn test_get_most_recent_search_entries_with_limits_and_different_observations() {
let conn = PlacesDb::open_in_memory(ConnectionType::ReadWrite).expect("memory db");

note_observation!(&conn,
url "http://mozilla.org/1/",
view_time None,
search_term Some("search_term_1"),
document_type None,
referrer_url None,
title None
);

bump_clock();

note_observation!(&conn,
url "http://mozilla.org/2/",
view_time Some(20),
search_term None,
document_type Some(DocumentType::Regular),
referrer_url None,
title None
);

bump_clock();

note_observation!(&conn,
url "http://mozilla.org/3/",
view_time None,
search_term Some("search_term_2"),
document_type None,
referrer_url None,
title None
);

bump_clock();

note_observation!(&conn,
url "http://mozilla.org/4/",
view_time None,
search_term Some("search_term_3"),
document_type None,
referrer_url None,
title None
);

// Limiting to 1 should return the most recent entry where search is not null.
let most_recents1 = get_most_recent_search_entries(&conn, 1).expect("query ok");
assert_eq!(most_recents1.len(), 1);
assert_eq!(most_recents1[0].url, "http://mozilla.org/4/");

// Limiting to 2 should return the two most recent entries.
let most_recents2 = get_most_recent_search_entries(&conn, 2).expect("query ok");
assert_eq!(most_recents2.len(), 2);
assert_eq!(most_recents2[0].url, "http://mozilla.org/4/");
assert_eq!(most_recents2[1].url, "http://mozilla.org/3/");

// Limiting to 10 should return all three entries, in the correct order.
let most_recents3 = get_most_recent_search_entries(&conn, 10).expect("query ok");
assert_eq!(most_recents3.len(), 3);
assert_eq!(most_recents3[0].url, "http://mozilla.org/4/");
assert_eq!(most_recents3[1].url, "http://mozilla.org/3/");
assert_eq!(most_recents3[2].url, "http://mozilla.org/1/");
}

#[test]
fn test_get_most_recent_search_entries_with_negative_limit_with_same_observation() {
let conn = PlacesDb::open_in_memory(ConnectionType::ReadWrite).expect("memory db");

note_observation!(&conn,
url "http://mozilla.org/1/",
view_time None,
search_term Some("search_term_1"),
document_type None,
referrer_url None,
title None
);

bump_clock();

note_observation!(&conn,
url "http://mozilla.org/1/",
view_time None,
search_term Some("search_term_1"),
document_type None,
referrer_url None,
title None
);

bump_clock();

note_observation!(&conn,
url "http://mozilla.org/1/",
view_time None,
search_term Some("search_term_1"),
document_type None,
referrer_url None,
title None
);

// Limiting to -1 should return all entries properly ordered.
let most_recents = get_most_recent_search_entries(&conn, -1).expect("query ok");
assert_eq!(most_recents.len(), 1);
assert_eq!(most_recents[0].url, "http://mozilla.org/1/");
}

#[test]
fn test_get_most_recent_search_entries_with_negative_limit_with_different_observations() {
let conn = PlacesDb::open_in_memory(ConnectionType::ReadWrite).expect("memory db");

note_observation!(&conn,
url "http://mozilla.org/1/",
view_time None,
search_term Some("search_term_1"),
document_type None,
referrer_url None,
title None
);

bump_clock();

note_observation!(&conn,
url "http://mozilla.org/2/",
view_time None,
search_term Some("search_term_2"),
document_type None,
referrer_url None,
title None
);

bump_clock();

note_observation!(&conn,
url "http://mozilla.org/3/",
view_time None,
search_term Some("search_term_3"),
document_type None,
referrer_url None,
title None
);

// Limiting to -1 should return all entries properly ordered.
let most_recents = get_most_recent_search_entries(&conn, -1).expect("query ok");
assert_eq!(most_recents.len(), 3);
assert_eq!(most_recents[0].url, "http://mozilla.org/3/");
assert_eq!(most_recents[1].url, "http://mozilla.org/2/");
assert_eq!(most_recents[2].url, "http://mozilla.org/1/");
}

#[test]
fn test_get_highlights() {
let conn = PlacesDb::open_in_memory(ConnectionType::ReadWrite).expect("memory db");
Expand Down