Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose sqlite's sqlite3_create_collation() #2564

Merged
merged 16 commits into from
Apr 15, 2021
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: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ for Rust libraries in [RFC #1105](https://github.com/rust-lang/rfcs/blob/master/

* Added the error position for PostgreSQL errors

* Added ability to create custom collation functions in SQLite.

### Removed

* All previously deprecated items have been removed.
Expand Down Expand Up @@ -203,6 +205,8 @@ for Rust libraries in [RFC #1105](https://github.com/rust-lang/rfcs/blob/master/

* Queries containing a `distinct on` clause check now on compile time that a compatible order clause was set.

* Implementations of custom SQLite SQL functions now check for panics

### Deprecated

* `diesel_(prefix|postfix|infix)_operator!` have been deprecated. These macros
Expand Down
4 changes: 2 additions & 2 deletions diesel/src/sqlite/connection/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ pub fn register<ArgsSqlType, RetSqlType, Args, Ret, F>(
mut f: F,
) -> QueryResult<()>
where
F: FnMut(&RawConnection, Args) -> Ret + Send + 'static,
F: FnMut(&RawConnection, Args) -> Ret + std::panic::UnwindSafe + Send + 'static,
Args: FromSqlRow<ArgsSqlType, Sqlite> + StaticallySizedRow<ArgsSqlType, Sqlite>,
Ret: ToSql<RetSqlType, Sqlite>,
Sqlite: HasSqlType<RetSqlType>,
Expand Down Expand Up @@ -45,7 +45,7 @@ pub fn register_aggregate<ArgsSqlType, RetSqlType, Args, Ret, A>(
fn_name: &str,
) -> QueryResult<()>
where
A: SqliteAggregateFunction<Args, Output = Ret> + 'static + Send,
A: SqliteAggregateFunction<Args, Output = Ret> + 'static + Send + std::panic::UnwindSafe,
Args: FromSqlRow<ArgsSqlType, Sqlite> + StaticallySizedRow<ArgsSqlType, Sqlite>,
Ret: ToSql<RetSqlType, Sqlite>,
Sqlite: HasSqlType<RetSqlType>,
Expand Down
118 changes: 116 additions & 2 deletions diesel/src/sqlite/connection/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ impl SqliteConnection {
mut f: F,
) -> QueryResult<()>
where
F: FnMut(Args) -> Ret + Send + 'static,
F: FnMut(Args) -> Ret + std::panic::UnwindSafe + Send + 'static,
Args: FromSqlRow<ArgsSqlType, Sqlite> + StaticallySizedRow<ArgsSqlType, Sqlite>,
Ret: ToSql<RetSqlType, Sqlite>,
Sqlite: HasSqlType<RetSqlType>,
Expand All @@ -242,14 +242,57 @@ impl SqliteConnection {
fn_name: &str,
) -> QueryResult<()>
where
A: SqliteAggregateFunction<Args, Output = Ret> + 'static + Send,
A: SqliteAggregateFunction<Args, Output = Ret> + 'static + Send + std::panic::UnwindSafe,
Args: FromSqlRow<ArgsSqlType, Sqlite> + StaticallySizedRow<ArgsSqlType, Sqlite>,
Ret: ToSql<RetSqlType, Sqlite>,
Sqlite: HasSqlType<RetSqlType>,
{
functions::register_aggregate::<_, _, _, _, A>(&self.raw_connection, fn_name)
}

/// Register a collation function.
///
/// `collation` must always return the same answer given the same inputs.
/// If `collation` panics and unwinds the stack, the process is aborted, since it is used
/// across a C FFI boundary, which cannot be unwound across and there is no way to
/// signal failures via the SQLite interface in this case..
///
/// If the name is already registered it will be overwritten.
///
/// This method will return an error if registering the function fails, either due to an
/// out-of-memory situation or because a collation with that name already exists and is
/// currently being used in parallel by a query.
///
/// The collation needs to be specified when creating a table:
/// `CREATE TABLE my_table ( str TEXT COLLATE MY_COLLATION )`,
/// where `MY_COLLATION` corresponds to name passed as `collation_name`.
///
/// # Example
///
/// ```rust
/// # include!("../../doctest_setup.rs");
/// #
/// # fn main() {
/// # run_test().unwrap();
/// # }
/// #
/// # fn run_test() -> QueryResult<()> {
/// # let conn = SqliteConnection::establish(":memory:").unwrap();
/// // sqlite NOCASE only works for ASCII characters,
/// // this collation allows handling UTF-8 (barring locale differences)
/// conn.register_collation("RUSTNOCASE", |rhs, lhs| {
/// rhs.to_lowercase().cmp(&lhs.to_lowercase())
/// })
/// # }
/// ```
pub fn register_collation<F>(&self, collation_name: &str, collation: F) -> QueryResult<()>
where
F: Fn(&str, &str) -> std::cmp::Ordering + Send + 'static + std::panic::UnwindSafe,
{
self.raw_connection
.register_collation_function(collation_name, collation)
}

fn register_diesel_sql_functions(&self) -> QueryResult<()> {
use crate::sql_types::{Integer, Text};

Expand Down Expand Up @@ -522,4 +565,75 @@ mod tests {
.unwrap();
assert_eq!(Some(3), result);
}

table! {
my_collation_example {
id -> Integer,
value -> Text,
}
}

#[test]
fn register_collation_function() {
use self::my_collation_example::dsl::*;

let connection = SqliteConnection::establish(":memory:").unwrap();

connection
.register_collation("RUSTNOCASE", |rhs, lhs| {
rhs.to_lowercase().cmp(&lhs.to_lowercase())
})
.unwrap();

connection
.execute(
"CREATE TABLE my_collation_example (id integer primary key autoincrement, value text collate RUSTNOCASE)",
)
.unwrap();
connection
.execute("INSERT INTO my_collation_example (value) VALUES ('foo'), ('FOo'), ('f00')")
.unwrap();

let result = my_collation_example
.filter(value.eq("foo"))
.select(value)
.load::<String>(&connection);
assert_eq!(
Ok(&["foo".to_owned(), "FOo".to_owned()][..]),
result.as_ref().map(|vec| vec.as_ref())
);

let result = my_collation_example
.filter(value.eq("FOO"))
.select(value)
.load::<String>(&connection);
assert_eq!(
Ok(&["foo".to_owned(), "FOo".to_owned()][..]),
result.as_ref().map(|vec| vec.as_ref())
);

let result = my_collation_example
.filter(value.eq("f00"))
.select(value)
.load::<String>(&connection);
assert_eq!(
Ok(&["f00".to_owned()][..]),
result.as_ref().map(|vec| vec.as_ref())
);

let result = my_collation_example
.filter(value.eq("F00"))
.select(value)
.load::<String>(&connection);
assert_eq!(
Ok(&["f00".to_owned()][..]),
result.as_ref().map(|vec| vec.as_ref())
);

let result = my_collation_example
.filter(value.eq("oof"))
.select(value)
.load::<String>(&connection);
assert_eq!(Ok(&[][..]), result.as_ref().map(|vec| vec.as_ref()));
}
}
Loading