blob: 92841365819b3f953054f29339e2af1c4315e94a [file] [log] [blame]
use super::schema::*;
use diesel::connection::SimpleConnection;
use diesel::*;
use crate::schema_dsl::*;
#[tokio::test(threaded_scheduler)]
async fn selecting_basic_data() {
use crate::schema::users::dsl::*;
let mut connection = async_connection().await;
sql_query("INSERT INTO users (name) VALUES ('Sean'), ('Tess')")
.execute_async(&mut connection)
.await
.unwrap();
let expected_data = vec![
("Sean".to_string(), None::<String>),
("Tess".to_string(), None::<String>),
];
let actual_data: Vec<_> = users
.select((name, hair_color))
.load_async(&mut connection)
.await
.unwrap();
assert_eq!(expected_data, actual_data);
}
#[test]
fn selecting_a_struct() {
use crate::schema::users::dsl::*;
let connection = connection();
connection
.execute("INSERT INTO users (name) VALUES ('Sean'), ('Tess')")
.unwrap();
let expected_users = vec![NewUser::new("Sean", None), NewUser::new("Tess", None)];
let actual_users: Vec<_> = users.select((name, hair_color)).load(&connection).unwrap();
assert_eq!(expected_users, actual_users);
}
#[test]
fn with_safe_select() {
use crate::schema::users::dsl::*;
let connection = connection();
connection
.execute("INSERT INTO users (name) VALUES ('Sean'), ('Tess')")
.unwrap();
let select_name = users.select(name);
let names: Vec<String> = select_name.load(&connection).unwrap();
assert_eq!(vec!["Sean".to_string(), "Tess".to_string()], names);
}
#[test]
fn with_select_sql() {
use diesel::dsl::sql;
let connection = connection();
connection
.execute("INSERT INTO users (name) VALUES ('Sean'), ('Tess')")
.unwrap();
let select_count = users::table.select(sql::<sql_types::BigInt>("COUNT(*)"));
let get_count = || select_count.clone().first::<i64>(&connection);
assert_eq!(Ok(2), get_count());
connection
.execute("INSERT INTO users (name) VALUES ('Jim')")
.unwrap();
assert_eq!(Ok(3), get_count());
}
#[test]
fn selecting_nullable_followed_by_non_null() {
use crate::schema::users::dsl::*;
let connection = connection();
connection
.execute("INSERT INTO users (name) VALUES ('Sean')")
.unwrap();
let source = users.select((hair_color, name));
let expected_data = vec![(None::<String>, "Sean".to_string())];
let data: Vec<_> = source.load(&connection).unwrap();
assert_eq!(expected_data, data);
}
#[test]
fn selecting_expression_with_bind_param() {
use crate::schema::users::dsl::*;
let connection = connection();
connection
.execute("INSERT INTO users (name) VALUES ('Sean'), ('Tess')")
.unwrap();
let source = users.select(name.eq("Sean".to_string()));
let expected_data = vec![true, false];
let actual_data = source.load::<bool>(&connection).unwrap();
assert_eq!(expected_data, actual_data);
}
table! {
select {
id -> Integer,
join -> Integer,
}
}
#[test]
#[cfg(not(feature = "mysql"))] // FIXME: Figure out how to handle tests that modify schema
fn selecting_columns_and_tables_with_reserved_names() {
use self::select::dsl::*;
let connection = connection();
create_table(
"select",
(
integer("id").primary_key().auto_increment(),
integer("join").not_null(),
),
)
.execute(&connection)
.unwrap();
connection
.execute("INSERT INTO \"select\" (\"join\") VALUES (1), (2), (3)")
.unwrap();
let expected_data = vec![(1, 1), (2, 2), (3, 3)];
let actual_data: Vec<(i32, i32)> = select.load(&connection).unwrap();
assert_eq!(expected_data, actual_data);
let expected_data = vec![1, 2, 3];
let actual_data: Vec<i32> = select.select(join).load(&connection).unwrap();
assert_eq!(expected_data, actual_data);
}
#[test]
#[cfg(not(feature = "mysql"))] // FIXME: Figure out how to handle tests that modify schema
fn selecting_columns_with_different_definition_order() {
let connection = connection();
drop_table_cascade(&connection, "users");
create_table(
"users",
(
integer("id").primary_key().auto_increment(),
string("hair_color"),
string("name").not_null(),
),
)
.execute(&connection)
.unwrap();
let expected_user = User::with_hair_color(1, "Sean", "black");
insert_into(users::table)
.values(&NewUser::new("Sean", Some("black")))
.execute(&connection)
.unwrap();
let user_from_select = users::table.first(&connection);
assert_eq!(Ok(&expected_user), user_from_select.as_ref());
}
#[test]
fn selection_using_subselect() {
use crate::schema::posts::dsl::*;
let connection = connection_with_sean_and_tess_in_users_table();
let ids: Vec<i32> = users::table.select(users::id).load(&connection).unwrap();
let query = format!(
"INSERT INTO posts (user_id, title) VALUES ({}, 'Hello'), ({}, 'World')",
ids[0], ids[1]
);
connection.execute(&query).unwrap();
let users = users::table
.filter(users::name.eq("Sean"))
.select(users::id);
let data: Vec<String> = posts
.select(title)
.filter(user_id.eq_any(users))
.load(&connection)
.unwrap();
assert_eq!(vec!["Hello".to_string()], data);
}
table! {
users_select_for_update_modifieres {
id -> Integer,
name -> Text,
hair_color -> Nullable<Text>,
}
}
table! {
users_select_for_update {
id -> Integer,
name -> Text,
hair_color -> Nullable<Text>,
}
}
table! {
users_select_for_no_key_update {
id -> Integer,
name -> Text,
hair_color -> Nullable<Text>,
}
}
table! {
users_fk_for_no_key_update {
id -> Integer,
users_fk -> Integer,
}
}
#[cfg(not(feature = "sqlite"))]
#[test]
fn select_for_update_locks_selected_rows() {
use self::users_select_for_update::dsl::*;
use std::mem::drop;
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
let conn_1 = connection_without_transaction();
conn_1
.execute("DROP TABLE IF EXISTS users_select_for_update")
.unwrap();
create_table(
"users_select_for_update",
(
integer("id").primary_key().auto_increment(),
string("name").not_null(),
string("hair_color"),
),
)
.execute(&conn_1)
.unwrap();
conn_1
.batch_execute(
"
-- MySQL locks the whole table without this index
CREATE UNIQUE INDEX users_select_for_update_name ON users_select_for_update (name);
INSERT INTO users_select_for_update (name) VALUES ('Sean'), ('Tess');
",
)
.unwrap();
conn_1.begin_test_transaction().unwrap();
let _sean = users_select_for_update
.for_update()
.filter(name.eq("Sean"))
.first::<User>(&conn_1)
.unwrap();
let (send, recv) = mpsc::channel();
let send2 = send.clone();
let _blocked_thread = thread::spawn(move || {
let conn_2 = connection();
update(users_select_for_update.filter(name.eq("Sean")))
.set(name.eq("Jim"))
.execute(&conn_2)
.unwrap();
send.send("Sean").unwrap();
});
let _unblocked_thread = thread::spawn(move || {
let conn_3 = connection();
update(users_select_for_update.filter(name.eq("Tess")))
.set(name.eq("Bob"))
.execute(&conn_3)
.unwrap();
send2.send("Tess").unwrap();
});
let timeout = Duration::from_secs(1);
let next_selected_name = recv.recv_timeout(timeout).unwrap();
// conn_3 will always complete before conn_2, as conn_2 is blocked
assert_eq!("Tess", next_selected_name);
let next_selected_name = recv.recv_timeout(timeout);
// conn_2 should still be blocked
assert!(next_selected_name.is_err());
drop(conn_1);
let next_selected_name = recv.recv_timeout(timeout).unwrap();
// Dropping conn_1 unblocks conn_2
assert_eq!("Sean", next_selected_name);
}
#[cfg(feature = "postgres")]
#[test]
fn select_for_update_modifiers() {
use self::users_select_for_update_modifieres::dsl::*;
// We need to actually commit some data for the
// test
let conn_1 = connection_without_transaction();
let conn_2 = connection();
let conn_3 = connection();
// Recreate the table
conn_1
.execute("DROP TABLE IF EXISTS users_select_for_update_modifieres")
.unwrap();
create_table(
"users_select_for_update_modifieres",
(
integer("id").primary_key().auto_increment(),
string("name").not_null(),
string("hair_color"),
),
)
.execute(&conn_1)
.unwrap();
// Add some test data
conn_1
.execute(
"
INSERT INTO users_select_for_update_modifieres (name)
VALUES ('Sean'), ('Tess')
",
)
.unwrap();
// Now both connections have begun a transaction
conn_1.begin_test_transaction().unwrap();
// Lock the "Sean" row
let _sean = users_select_for_update_modifieres
.order(name)
.for_update()
.first::<User>(&conn_1)
.unwrap();
// Try to access the "Sean" row with `NOWAIT`
conn_2.execute("SET STATEMENT_TIMEOUT TO 1000").unwrap();
let result = users_select_for_update_modifieres
.order(name)
.for_update()
.no_wait()
.first::<User>(&conn_2);
// Make sure we errored in the correct way (without timing out)
assert!(result.is_err());
if !format!("{:?}", result).contains("could not obtain lock on row") {
panic!("{:?}", result);
}
// Try to access the "Sean" row with `SKIP LOCKED`
let tess = users_select_for_update_modifieres
.order(name)
.for_update()
.skip_locked()
.first::<User>(&conn_3)
.unwrap();
// Make sure got back "Tess"
assert_eq!(tess.name, "Tess");
}
#[cfg(feature = "postgres")]
#[test]
fn select_for_no_key_update_modifiers() {
use self::users_fk_for_no_key_update::dsl::*;
use self::users_select_for_no_key_update::dsl::*;
// We need to actually commit some data for the
// test
let conn_1 = connection_without_transaction();
let conn_2 = connection();
let conn_3 = connection();
let conn_4 = connection();
// Recreate the table
conn_1
.execute("DROP TABLE IF EXISTS users_fk_for_no_key_update")
.unwrap();
conn_1
.execute("DROP TABLE IF EXISTS users_select_for_no_key_update")
.unwrap();
create_table(
"users_select_for_no_key_update",
(
integer("id").primary_key().auto_increment(),
string("name").not_null(),
string("hair_color"),
),
)
.execute(&conn_1)
.unwrap();
create_table(
"users_fk_for_no_key_update",
(
integer("id").primary_key().auto_increment(),
integer("users_fk").not_null(),
),
)
.execute(&conn_1)
.unwrap();
// Add a foreign key
conn_1
.execute(
"ALTER TABLE users_fk_for_no_key_update ADD CONSTRAINT users_fk \
FOREIGN KEY (users_fk) REFERENCES users_select_for_no_key_update(id)",
)
.unwrap();
// Add some test data
conn_1
.execute(
"INSERT INTO users_select_for_no_key_update (name) VALUES ('Sean'), ('Tess'), ('Will')",
)
.unwrap();
conn_1.begin_test_transaction().unwrap();
// Lock the "Sean" row, except the key
let _sean = users_select_for_no_key_update
.order(name)
.for_no_key_update()
.first::<User>(&conn_1)
.unwrap();
// Try to add an object referencing the "Sean" row
conn_2
.execute(
"INSERT INTO users_fk_for_no_key_update (users_fk) \
SELECT id FROM users_select_for_no_key_update where name='Sean'",
)
.unwrap();
// Check that it was successfully added
let expected_data = vec![(1, 1)];
let actual_data: Vec<(i32, i32)> = users_fk_for_no_key_update.load(&conn_2).unwrap();
assert_eq!(expected_data, actual_data);
// Try to access the "Sean" row with `for no key update` and `SKIP LOCKED`
let tess = users_select_for_no_key_update
.order(name)
.for_no_key_update()
.skip_locked()
.first::<User>(&conn_3)
.unwrap();
// Make sure got back "Tess"
assert_eq!(tess.name, "Tess");
// Lock the "Will" row completely
let will = users_select_for_no_key_update
.order(name)
.for_update()
.skip_locked()
.first::<User>(&conn_4)
.unwrap();
assert_eq!(will.name, "Will");
conn_2.execute("SET STATEMENT_TIMEOUT TO 1000").unwrap();
let result = conn_2.execute(
"INSERT INTO users_fk_for_no_key_update (users_fk) \
SELECT id FROM users_select_for_no_key_update where name='Will'",
);
// Times out instead of inserting row
assert!(result.is_err());
if !format!("{:?}", result).contains("canceling statement due to statement timeout") {
panic!("{:?}", result);
}
}
#[test]
fn select_can_be_called_on_query_that_is_valid_subselect_but_invalid_query() {
let connection = connection_with_sean_and_tess_in_users_table();
let sean = find_user_by_name("Sean", &connection);
let tess = find_user_by_name("Tess", &connection);
insert_into(posts::table)
.values(&vec![
tess.new_post("Tess", None),
sean.new_post("Hi", None),
])
.execute(&connection)
.unwrap();
let invalid_query_but_valid_subselect = posts::table
.filter(posts::title.eq(users::name))
.select(posts::user_id);
let users_with_post_using_name_as_title = users::table
.filter(users::id.eq_any(invalid_query_but_valid_subselect))
.load(&connection);
assert_eq!(Ok(vec![tess]), users_with_post_using_name_as_title);
}