Skip to content

Commit

Permalink
Bug 1876208 - API for dismissing suggestions
Browse files Browse the repository at this point in the history
Dismissed suggestion Url hashes are stored in the database and the
suggestions are not returned again in subsequent queries.

This currently works with most providers, but not Yelp or Weather.
  • Loading branch information
bendk committed Mar 14, 2024
1 parent a1b5ea4 commit f600a67
Show file tree
Hide file tree
Showing 13 changed files with 483 additions and 11 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

37 changes: 37 additions & 0 deletions DEPENDENCIES.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ the details of which are reproduced below.
* [MIT License: http-body](#mit-license-http-body)
* [MIT License: hyper](#mit-license-hyper)
* [MIT License: libsqlite3-sys, rusqlite](#mit-license-libsqlite3-sys-rusqlite)
* [MIT License: md-5](#mit-license-md-5)
* [MIT License: mime_guess](#mit-license-mime_guess)
* [MIT License: mio](#mit-license-mio)
* [MIT License: nom](#mit-license-nom)
Expand Down Expand Up @@ -1171,6 +1172,42 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
```
-------------
## MIT License: md-5

The following text applies to code linked from these dependencies:
[md-5](https://github.com/RustCrypto/hashes)

```
Copyright (c) 2006-2009 Graydon Hoare
Copyright (c) 2009-2013 Mozilla Foundation
Copyright (c) 2016 Artyom Pavlov
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
```
-------------
## MIT License: mime_guess
Expand Down
1 change: 1 addition & 0 deletions components/suggest/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ exclude = ["/android", "/ios"]
anyhow = "1.0"
chrono = "0.4"
interrupt-support = { path = "../support/interrupt" }
md-5 = "0.10"
once_cell = "1.5"
parking_lot = ">=0.11,<=0.12"
remote_settings = { path = "../remote_settings" }
Expand Down
20 changes: 20 additions & 0 deletions components/suggest/src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ impl<'a> SuggestDao<'a> {
WHERE
s.provider = :provider
AND k.keyword = :keyword
AND NOT EXISTS (SELECT 1 FROM dismissed_suggestions WHERE url=s.url)
"#,
named_params! {
":keyword": keyword_lowercased,
Expand Down Expand Up @@ -348,6 +349,7 @@ impl<'a> SuggestDao<'a> {
WHERE
s.provider = :provider
AND k.keyword = :keyword
AND NOT EXISTS (SELECT 1 FROM dismissed_suggestions WHERE url=s.url)
"#,
named_params! {
":keyword": keyword_lowercased,
Expand Down Expand Up @@ -430,6 +432,7 @@ impl<'a> SuggestDao<'a> {
k.keyword_prefix = :keyword_prefix
AND (k.keyword_suffix BETWEEN :keyword_suffix AND :keyword_suffix || x'FFFF')
AND s.provider = :provider
AND NOT EXISTS (SELECT 1 FROM dismissed_suggestions WHERE url=s.url)
GROUP BY
s.id
ORDER BY
Expand Down Expand Up @@ -529,6 +532,7 @@ impl<'a> SuggestDao<'a> {
k.keyword_prefix = :keyword_prefix
AND (k.keyword_suffix BETWEEN :keyword_suffix AND :keyword_suffix || x'FFFF')
AND s.provider = :provider
AND NOT EXISTS (SELECT 1 FROM dismissed_suggestions WHERE url=s.url)
GROUP BY
s.id,
k.confidence
Expand Down Expand Up @@ -1192,6 +1196,22 @@ impl<'a> SuggestDao<'a> {
Ok(())
}

pub fn insert_dismissal(&self, url: &str) -> Result<()> {
self.conn.execute(
"INSERT OR IGNORE INTO dismissed_suggestions(url)
VALUES(:url)",
named_params! {
":url": url,
},
)?;
Ok(())
}

pub fn clear_dismissals(&self) -> Result<()> {
self.conn.execute("DELETE FROM dismissed_suggestions", ())?;
Ok(())
}

/// Deletes all suggestions associated with a Remote Settings record from
/// the database.
pub fn drop_suggestions(&mut self, record_id: &SuggestRecordId) -> Result<()> {
Expand Down
2 changes: 1 addition & 1 deletion components/suggest/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ pub(crate) type Result<T> = std::result::Result<T, error::Error>;
pub type SuggestApiResult<T> = std::result::Result<T, error::SuggestApiError>;

/// A query for suggestions to show in the address bar.
#[derive(Debug, Default)]
#[derive(Clone, Debug, Default)]
pub struct SuggestionQuery {
pub keyword: String,
pub providers: Vec<SuggestionProvider>,
Expand Down
68 changes: 65 additions & 3 deletions components/suggest/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

use rusqlite::{Connection, Transaction};
use rusqlite::{functions::FunctionFlags, Connection, Transaction};
use sql_support::open_database::{self, ConnectionInitializer};

/// The current database schema version.
Expand All @@ -15,7 +15,7 @@ use sql_support::open_database::{self, ConnectionInitializer};
/// [`SuggestConnectionInitializer::upgrade_from`].
/// a. If suggestions should be re-ingested after the migration, call `clear_database()` inside
/// the migration.
pub const VERSION: u32 = 17;
pub const VERSION: u32 = 18;

/// The current Suggest database schema.
pub const SQL: &str = "
Expand Down Expand Up @@ -127,7 +127,7 @@ pub const SQL: &str = "
-- Just store the MD5 hash of the dismissed suggestion. The collision rate is low and the
-- impact of a collision is not showing a suggestion, which is not that bad.
CREATE TABLE dismissed_suggestions (
url_hash INTEGER PRIMARY KEY
url TEXT PRIMARY KEY
) WITHOUT ROWID;
";

Expand All @@ -148,6 +148,7 @@ impl ConnectionInitializer for SuggestConnectionInitializer {
PRAGMA foreign_keys = ON;
";
conn.execute_batch(initial_pragmas)?;
define_functions(conn)?;
sql_support::debug_tools::define_debug_functions(conn)?;

Ok(())
Expand Down Expand Up @@ -175,6 +176,17 @@ impl ConnectionInitializer for SuggestConnectionInitializer {
)?;
Ok(())
}
17 => {
tx.execute(
"
DROP TABLE dismissed_suggestions;
CREATE TABLE dismissed_suggestions (
url TEXT PRIMARY KEY
) WITHOUT ROWID;",
(),
)?;
Ok(())
}
_ => Err(open_database::Error::IncompatibleVersion(version)),
}
}
Expand All @@ -195,6 +207,56 @@ pub fn clear_database(db: &Connection) -> rusqlite::Result<()> {
)
}

fn define_functions(c: &Connection) -> rusqlite::Result<()> {
c.create_scalar_function(
"hash",
1,
FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
sql_fns::hash,
)?;
Ok(())
}

mod sql_fns {
use md5::{Digest, Md5};
use rusqlite::{functions::Context, types::ValueRef, Error, Result};

#[inline(never)]
pub fn hash(ctx: &Context<'_>) -> Result<Option<i64>> {
match ctx.len() {
1 => {
let raw = ctx.get_raw(0);
// This is a deterministic function, which means sqlite
// does certain optimizations which means hash() may be called
// with a null value even though the query prevents the null
// value from actually being used. As a special case, we return
// null when the input is NULL. We return NULL instead of zero
// because the hash columns are NOT NULL, so attempting to
// actually use the null should fail.
if raw == ValueRef::Null {
return Ok(None);
}
match raw.as_str() {
Ok(s) => {
// Hash using Md5, then return the first 64 bits since SQLite uses 64-bit
// integers.
let mut hasher = Md5::new();
hasher.update(s.as_bytes());
let hash_value = i128::from_le_bytes(hasher.finalize().into());
Ok(Some((hash_value >> 64) as i64))
}
Err(e) => Err(Error::UserFunctionError(
format!("Bad arg to 'hash': {e}").into(),
)),
}
}
n => Err(Error::UserFunctionError(
format!("`hash` expects 1 argument, got {}.", n).into(),
)),
}
}
}

#[cfg(test)]
mod test {
use super::*;
Expand Down
Loading

0 comments on commit f600a67

Please sign in to comment.