| use crate::schema::*; |
| use diesel::*; |
| |
| #[test] |
| fn one_to_many_returns_query_source_for_association() { |
| let (connection, sean, tess, _) = conn_with_test_data(); |
| |
| let seans_posts = posts::table |
| .filter(posts::user_id.eq(sean.id)) |
| .load::<Post>(&connection) |
| .unwrap(); |
| let tess_posts = posts::table |
| .filter(posts::user_id.eq(tess.id)) |
| .load::<Post>(&connection) |
| .unwrap(); |
| |
| let found_posts: Vec<_> = Post::belonging_to(&sean).load(&connection).unwrap(); |
| assert_eq!(seans_posts, found_posts); |
| |
| let found_posts: Vec<_> = Post::belonging_to(&tess).load(&connection).unwrap(); |
| assert_eq!(tess_posts, found_posts); |
| } |
| |
| #[test] |
| fn eager_loading_associations_for_multiple_records() { |
| let (connection, sean, tess, _) = conn_with_test_data(); |
| |
| let users = vec![sean.clone(), tess.clone()]; |
| let posts = Post::belonging_to(&users) |
| .load::<Post>(&connection) |
| .unwrap() |
| .grouped_by(&users); |
| let users_and_posts = users.into_iter().zip(posts).collect::<Vec<_>>(); |
| |
| let seans_posts = Post::belonging_to(&sean).load(&connection).unwrap(); |
| let tess_posts = Post::belonging_to(&tess).load(&connection).unwrap(); |
| let expected_data = vec![(sean, seans_posts), (tess, tess_posts)]; |
| assert_eq!(expected_data, users_and_posts); |
| } |
| |
| #[cfg(not(feature = "mysql"))] // FIXME: Figure out how to handle tests that modify schema |
| mod eager_loading_with_string_keys { |
| use crate::schema::{connection, drop_table_cascade}; |
| use diesel::connection::SimpleConnection; |
| use diesel::*; |
| |
| table! { users { id -> Text, } } |
| table! { posts { id -> Text, user_id -> Text, } } |
| allow_tables_to_appear_in_same_query!(users, posts); |
| |
| #[derive(Queryable, Identifiable, Debug, PartialEq, Clone)] |
| pub struct User { |
| id: String, |
| } |
| |
| #[derive(Queryable, Identifiable, Debug, PartialEq, Clone, Associations)] |
| #[belongs_to(User)] |
| pub struct Post { |
| id: String, |
| user_id: String, |
| } |
| |
| #[test] |
| fn eager_loading_associations_for_multiple_records() { |
| let connection = connection(); |
| drop_table_cascade(&connection, "users"); |
| drop_table_cascade(&connection, "posts"); |
| drop_table_cascade(&connection, "fk_doesnt_reference_pk"); |
| connection |
| .batch_execute( |
| r#" |
| CREATE TABLE users (id TEXT PRIMARY KEY NOT NULL); |
| CREATE TABLE posts (id TEXT PRIMARY KEY NOT NULL, user_id TEXT NOT NULL); |
| INSERT INTO users (id) VALUES ('Sean'), ('Tess'); |
| INSERT INTO posts (id, user_id) VALUES ('Hello', 'Sean'), ('World', 'Sean'), ('Hello 2', 'Tess'); |
| "#, |
| ) |
| .unwrap(); |
| let sean = User { id: "Sean".into() }; |
| let tess = User { id: "Tess".into() }; |
| |
| let users = vec![sean.clone(), tess.clone()]; |
| let posts = Post::belonging_to(&users) |
| .load::<Post>(&connection) |
| .unwrap() |
| .grouped_by(&users); |
| let users_and_posts = users.into_iter().zip(posts).collect::<Vec<_>>(); |
| |
| let seans_posts = Post::belonging_to(&sean).load(&connection).unwrap(); |
| let tess_posts = Post::belonging_to(&tess).load(&connection).unwrap(); |
| let expected_data = vec![(sean, seans_posts), (tess, tess_posts)]; |
| assert_eq!(expected_data, users_and_posts); |
| } |
| } |
| |
| #[test] |
| fn grouping_associations_maintains_ordering() { |
| let (connection, sean, tess, _) = conn_with_test_data(); |
| |
| let users = vec![sean.clone(), tess.clone()]; |
| let posts = Post::belonging_to(&users) |
| .order(posts::title.desc()) |
| .load::<Post>(&connection) |
| .unwrap() |
| .grouped_by(&users); |
| let users_and_posts = users.into_iter().zip(posts).collect::<Vec<_>>(); |
| |
| let seans_posts = Post::belonging_to(&sean) |
| .order(posts::title.desc()) |
| .load(&connection) |
| .unwrap(); |
| let tess_posts = Post::belonging_to(&tess) |
| .order(posts::title.desc()) |
| .load(&connection) |
| .unwrap(); |
| let expected_data = vec![(sean.clone(), seans_posts), (tess.clone(), tess_posts)]; |
| assert_eq!(expected_data, users_and_posts); |
| |
| // Test when sorted manually |
| let users = vec![sean.clone(), tess.clone()]; |
| let mut posts = Post::belonging_to(&users) |
| .load::<Post>(&connection) |
| .unwrap(); |
| posts.sort_by(|a, b| b.title.cmp(&a.title)); |
| let posts = posts.grouped_by(&users); |
| let users_and_posts = users.into_iter().zip(posts).collect::<Vec<_>>(); |
| |
| assert_eq!(expected_data, users_and_posts); |
| } |
| |
| #[test] |
| fn associations_can_be_grouped_multiple_levels_deep() { |
| // I'm manually defining the data rather than loading from the database here, |
| // as it makes the tests *way* more readable if I omit setup here. This is |
| // the equivalent to. |
| // |
| // ```rust |
| // let users = users.load::<User>().unwrap(); |
| // let posts = Post::belonging_to(&users).load::<Post>().unwrap(); |
| // let comments = Comment::belonging_to(&users).load::<Comment>().unwrap(); |
| // ``` |
| let users = vec![User::new(1, "Sean"), User::new(2, "Tess")]; |
| let posts = vec![ |
| Post::new(1, 1, "Hello", None), |
| Post::new(2, 2, "World", None), |
| Post::new(3, 1, "Hello 2", None), |
| ]; |
| let comments = vec![ |
| Comment::new(1, 3, "LOL"), |
| Comment::new(2, 1, "UR dumb"), |
| Comment::new(3, 3, "Funny"), |
| ]; // Never read the comments |
| |
| let expected_data = vec![ |
| ( |
| users[0].clone(), |
| vec![ |
| (posts[0].clone(), vec![comments[1].clone()]), |
| ( |
| posts[2].clone(), |
| vec![comments[0].clone(), comments[2].clone()], |
| ), |
| ], |
| ), |
| (users[1].clone(), vec![(posts[1].clone(), vec![])]), |
| ]; |
| |
| let comments = comments.grouped_by(&posts); |
| let posts_and_comments = posts.into_iter().zip(comments).grouped_by(&users); |
| let data = users |
| .into_iter() |
| .zip(posts_and_comments) |
| .collect::<Vec<_>>(); |
| |
| assert_eq!(expected_data, data); |
| } |
| |
| #[test] |
| fn self_referencing_associations() { |
| #[derive(Insertable, Queryable, Associations, Identifiable, Debug, Clone, Copy, PartialEq)] |
| #[table_name = "trees"] |
| #[belongs_to(Tree, foreign_key = "parent_id")] |
| struct Tree { |
| id: i32, |
| parent_id: Option<i32>, |
| } |
| |
| let conn = connection(); |
| let test_data = vec![ |
| Tree { |
| id: 1, |
| parent_id: None, |
| }, |
| Tree { |
| id: 2, |
| parent_id: None, |
| }, |
| Tree { |
| id: 3, |
| parent_id: Some(1), |
| }, |
| Tree { |
| id: 4, |
| parent_id: Some(2), |
| }, |
| Tree { |
| id: 5, |
| parent_id: Some(1), |
| }, |
| ]; |
| insert_into(trees::table) |
| .values(&test_data) |
| .execute(&conn) |
| .unwrap(); |
| |
| let parents = trees::table |
| .filter(trees::parent_id.is_null()) |
| .load::<Tree>(&conn) |
| .unwrap(); |
| let children = Tree::belonging_to(&parents).load::<Tree>(&conn).unwrap(); |
| let children = children.grouped_by(&parents); |
| let data = parents.into_iter().zip(children).collect::<Vec<_>>(); |
| |
| let expected_data = vec![ |
| (test_data[0], vec![test_data[2], test_data[4]]), |
| (test_data[1], vec![test_data[3]]), |
| ]; |
| assert_eq!(expected_data, data); |
| } |
| |
| fn conn_with_test_data() -> (TestConnection, User, User, User) { |
| let connection = connection_with_sean_and_tess_in_users_table(); |
| insert_into(users::table) |
| .values(&NewUser::new("Jim", None)) |
| .execute(&connection) |
| .unwrap(); |
| |
| let sean = find_user_by_name("Sean", &connection); |
| let tess = find_user_by_name("Tess", &connection); |
| let jim = find_user_by_name("Jim", &connection); |
| let new_posts = vec![sean.new_post("Hello", None), sean.new_post("World", None)]; |
| insert_into(posts::table) |
| .values(&new_posts) |
| .execute(&connection) |
| .unwrap(); |
| let new_posts = vec![ |
| tess.new_post("Hello 2", None), |
| tess.new_post("World 2", None), |
| ]; |
| insert_into(posts::table) |
| .values(&new_posts) |
| .execute(&connection) |
| .unwrap(); |
| let new_posts = vec![jim.new_post("Hello 3", None), jim.new_post("World 3", None)]; |
| insert_into(posts::table) |
| .values(&new_posts) |
| .execute(&connection) |
| .unwrap(); |
| |
| (connection, sean, tess, jim) |
| } |
| |
| #[test] |
| #[cfg(not(feature = "mysql"))] // FIXME: Figure out how to handle tests that modify schema |
| fn custom_foreign_key() { |
| use diesel::connection::SimpleConnection; |
| use diesel::*; |
| |
| table! { |
| users1 { |
| id -> Integer, |
| name -> Text, |
| } |
| } |
| |
| table! { |
| posts1 { |
| id -> Integer, |
| belongs_to_user -> Integer, |
| title -> Text, |
| } |
| } |
| |
| allow_tables_to_appear_in_same_query!(users1, posts1); |
| |
| #[derive(Clone, Debug, PartialEq, Identifiable, Queryable)] |
| #[table_name = "users1"] |
| pub struct User { |
| id: i32, |
| name: String, |
| } |
| |
| #[derive(Clone, Debug, PartialEq, Associations, Identifiable, Queryable)] |
| #[belongs_to(User, foreign_key = "belongs_to_user")] |
| #[table_name = "posts1"] |
| pub struct Post { |
| id: i32, |
| belongs_to_user: i32, |
| title: String, |
| } |
| |
| joinable!(posts1 -> users1(belongs_to_user)); |
| let connection = connection(); |
| connection |
| .batch_execute( |
| r#" |
| CREATE TABLE users1 (id SERIAL PRIMARY KEY, |
| name TEXT NOT NULL); |
| CREATE TABLE posts1 (id SERIAL PRIMARY KEY, |
| belongs_to_user INTEGER NOT NULL, |
| title TEXT NOT NULL); |
| INSERT INTO users1 (id, name) VALUES (1, 'Sean'), (2, 'Tess'); |
| INSERT INTO posts1 (id, belongs_to_user, title) VALUES |
| (1, 1, 'Hello'), |
| (2, 2, 'World'), |
| (3, 1, 'Hello 2'); |
| "#, |
| ) |
| .unwrap(); |
| |
| let sean = User { |
| id: 1, |
| name: "Sean".into(), |
| }; |
| let tess = User { |
| id: 2, |
| name: "Tess".into(), |
| }; |
| let post1 = Post { |
| id: 1, |
| belongs_to_user: 1, |
| title: "Hello".into(), |
| }; |
| let post2 = Post { |
| id: 2, |
| belongs_to_user: 2, |
| title: "World".into(), |
| }; |
| let post3 = Post { |
| id: 3, |
| belongs_to_user: 1, |
| title: "Hello 2".into(), |
| }; |
| |
| assert_eq!( |
| Post::belonging_to(&sean).load(&connection), |
| Ok(vec![post1.clone(), post3.clone()]) |
| ); |
| |
| assert_eq!( |
| users1::table.inner_join(posts1::table).load(&connection), |
| Ok(vec![(sean.clone(), post1), (tess, post2), (sean, post3)]) |
| ); |
| } |