diff --git a/examples/basic/Readme.md b/examples/basic/Readme.md index d1a55c88..b355e250 100644 --- a/examples/basic/Readme.md +++ b/examples/basic/Readme.md @@ -5,19 +5,9 @@ Prepare: Setup a test database and configure the connection string in `main.rs`. Run `bakery.sql` to setup the test table and data. -Running: +Running: `cargo run` ```sh -cargo run -``` - -All about selects: - -```sh -SqlxMySqlPoolConnection - -===== ===== - find all cakes: SELECT `cake`.`id`, `cake`.`name` FROM `cake` Model { id: 1, name: "New York Cheese" } @@ -46,16 +36,6 @@ Model { id: 9, name: "Pineapple", cake_id: None } ===== ===== -find cakes and fruits: SELECT `cake`.`id` AS `A_id`, `cake`.`name` AS `A_name`, `fruit`.`id` AS `B_id`, `fruit`.`name` AS `B_name`, `fruit`.`cake_id` AS `B_cake_id` FROM `cake` LEFT JOIN `fruit` ON `cake`.`id` = `fruit`.`cake_id` - -(Model { id: 1, name: "New York Cheese" }, Some(Model { id: 1, name: "Blueberry", cake_id: Some(1) })) - -(Model { id: 1, name: "New York Cheese" }, Some(Model { id: 2, name: "Rasberry", cake_id: Some(1) })) - -(Model { id: 2, name: "Chocolate Forest" }, Some(Model { id: 3, name: "Strawberry", cake_id: Some(2) })) - -===== ===== - find one by primary key: SELECT `cake`.`id`, `cake`.`name` FROM `cake` WHERE `cake`.`id` = 1 LIMIT 1 Model { id: 1, name: "New York Cheese" } @@ -72,6 +52,33 @@ Model { id: 2, name: "Rasberry", cake_id: Some(1) } ===== ===== +find fruits and cakes: SELECT `fruit`.`id` AS `A_id`, `fruit`.`name` AS `A_name`, `fruit`.`cake_id` AS `A_cake_id`, `cake`.`id` AS `B_id`, `cake`.`name` AS `B_name` FROM `fruit` LEFT JOIN `cake` ON `fruit`.`cake_id` = `cake`.`id` +with loader: +SELECT `fruit`.`id`, `fruit`.`name`, `fruit`.`cake_id` FROM `fruit` +SELECT `cake`.`id`, `cake`.`name` FROM `cake` WHERE `cake`.`id` IN (1, 1, 2, NULL, NULL, NULL, NULL, NULL, NULL) + +(Model { id: 1, name: "Blueberry", cake_id: Some(1) }, Some(Model { id: 1, name: "New York Cheese" })) +(Model { id: 2, name: "Rasberry", cake_id: Some(1) }, Some(Model { id: 1, name: "New York Cheese" })) +(Model { id: 3, name: "Strawberry", cake_id: Some(2) }, Some(Model { id: 2, name: "Chocolate Forest" })) +(Model { id: 4, name: "Apple", cake_id: None }, None) +(Model { id: 5, name: "Banana", cake_id: None }, None) +(Model { id: 6, name: "Cherry", cake_id: None }, None) +(Model { id: 7, name: "Lemon", cake_id: None }, None) +(Model { id: 8, name: "Orange", cake_id: None }, None) +(Model { id: 9, name: "Pineapple", cake_id: None }, None) +===== ===== + +find cakes with fruits: SELECT `cake`.`id` AS `A_id`, `cake`.`name` AS `A_name`, `fruit`.`id` AS `B_id`, `fruit`.`name` AS `B_name`, `fruit`.`cake_id` AS `B_cake_id` FROM `cake` LEFT JOIN `fruit` ON `cake`.`id` = `fruit`.`cake_id` ORDER BY `cake`.`id` ASC +with loader: +SELECT `cake`.`id`, `cake`.`name` FROM `cake` +SELECT `fruit`.`id`, `fruit`.`name`, `fruit`.`cake_id` FROM `fruit` WHERE `fruit`.`cake_id` IN (1, 2) + +(Model { id: 1, name: "New York Cheese" }, [Model { id: 1, name: "Blueberry", cake_id: Some(1) }, Model { id: 2, name: "Rasberry", cake_id: Some(1) }]) + +(Model { id: 2, name: "Chocolate Forest" }, [Model { id: 3, name: "Strawberry", cake_id: Some(2) }]) + +===== ===== + count fruits by cake: SELECT `cake`.`name`, COUNT(`fruit`.`id`) AS `num_of_fruits` FROM `cake` LEFT JOIN `fruit` ON `cake`.`id` = `fruit`.`cake_id` GROUP BY `cake`.`name` SelectResult { name: "New York Cheese", num_of_fruits: 2 } @@ -81,6 +88,10 @@ SelectResult { name: "Chocolate Forest", num_of_fruits: 1 } ===== ===== find cakes and fillings: SELECT `cake`.`id` AS `A_id`, `cake`.`name` AS `A_name`, `filling`.`id` AS `B_id`, `filling`.`name` AS `B_name` FROM `cake` LEFT JOIN `cake_filling` ON `cake`.`id` = `cake_filling`.`cake_id` LEFT JOIN `filling` ON `cake_filling`.`filling_id` = `filling`.`id` ORDER BY `cake`.`id` ASC +with loader: +SELECT `cake`.`id`, `cake`.`name` FROM `cake` +SELECT `cake_filling`.`cake_id`, `cake_filling`.`filling_id` FROM `cake_filling` WHERE `cake_filling`.`cake_id` IN (1, 2) +SELECT `filling`.`id`, `filling`.`name` FROM `filling` WHERE `filling`.`id` IN (1, 2, 2, 3) (Model { id: 1, name: "New York Cheese" }, [Model { id: 1, name: "Vanilla" }, Model { id: 2, name: "Lemon" }]) @@ -100,42 +111,96 @@ Model { id: 1, name: "New York Cheese" } Model { id: 2, name: "Chocolate Forest" } -``` +===== ===== -All about operations: +find all cakes paginated: +SELECT `cake`.`id`, `cake`.`name` FROM `cake` LIMIT 3 OFFSET 0 +Model { id: 1, name: "New York Cheese" } +Model { id: 2, name: "Chocolate Forest" } +SELECT `cake`.`id`, `cake`.`name` FROM `cake` LIMIT 3 OFFSET 3 + +find all fruits paginated: +SELECT `fruit`.`id`, `fruit`.`name`, `fruit`.`cake_id` FROM `fruit` LIMIT 3 OFFSET 0 +Model { id: 1, name: "Blueberry", cake_id: Some(1) } +Model { id: 2, name: "Rasberry", cake_id: Some(1) } +Model { id: 3, name: "Strawberry", cake_id: Some(2) } +SELECT `fruit`.`id`, `fruit`.`name`, `fruit`.`cake_id` FROM `fruit` LIMIT 3 OFFSET 3 +Model { id: 4, name: "Apple", cake_id: None } +Model { id: 5, name: "Banana", cake_id: None } +Model { id: 6, name: "Cherry", cake_id: None } +SELECT `fruit`.`id`, `fruit`.`name`, `fruit`.`cake_id` FROM `fruit` LIMIT 3 OFFSET 6 +Model { id: 7, name: "Lemon", cake_id: None } +Model { id: 8, name: "Orange", cake_id: None } +Model { id: 9, name: "Pineapple", cake_id: None } +SELECT `fruit`.`id`, `fruit`.`name`, `fruit`.`cake_id` FROM `fruit` LIMIT 3 OFFSET 9 + +find all fruits with stream: +SELECT `fruit`.`id`, `fruit`.`name`, `fruit`.`cake_id` FROM `fruit` LIMIT 3 OFFSET 0 +Model { id: 1, name: "Blueberry", cake_id: Some(1) } +Model { id: 2, name: "Rasberry", cake_id: Some(1) } +Model { id: 3, name: "Strawberry", cake_id: Some(2) } +SELECT `fruit`.`id`, `fruit`.`name`, `fruit`.`cake_id` FROM `fruit` LIMIT 3 OFFSET 3 +Model { id: 4, name: "Apple", cake_id: None } +Model { id: 5, name: "Banana", cake_id: None } +Model { id: 6, name: "Cherry", cake_id: None } +SELECT `fruit`.`id`, `fruit`.`name`, `fruit`.`cake_id` FROM `fruit` LIMIT 3 OFFSET 6 +Model { id: 7, name: "Lemon", cake_id: None } +Model { id: 8, name: "Orange", cake_id: None } +Model { id: 9, name: "Pineapple", cake_id: None } +SELECT `fruit`.`id`, `fruit`.`name`, `fruit`.`cake_id` FROM `fruit` LIMIT 3 OFFSET 9 + +find all fruits in json with stream: +SELECT `fruit`.`id`, `fruit`.`name`, `fruit`.`cake_id` FROM `fruit` LIMIT 3 OFFSET 0 +Object {"cake_id": Number(1), "id": Number(1), "name": String("Blueberry")} +Object {"cake_id": Number(1), "id": Number(2), "name": String("Rasberry")} +Object {"cake_id": Number(2), "id": Number(3), "name": String("Strawberry")} +SELECT `fruit`.`id`, `fruit`.`name`, `fruit`.`cake_id` FROM `fruit` LIMIT 3 OFFSET 3 +Object {"cake_id": Null, "id": Number(4), "name": String("Apple")} +Object {"cake_id": Null, "id": Number(5), "name": String("Banana")} +Object {"cake_id": Null, "id": Number(6), "name": String("Cherry")} +SELECT `fruit`.`id`, `fruit`.`name`, `fruit`.`cake_id` FROM `fruit` LIMIT 3 OFFSET 6 +Object {"cake_id": Null, "id": Number(7), "name": String("Lemon")} +Object {"cake_id": Null, "id": Number(8), "name": String("Orange")} +Object {"cake_id": Null, "id": Number(9), "name": String("Pineapple")} +SELECT `fruit`.`id`, `fruit`.`name`, `fruit`.`cake_id` FROM `fruit` LIMIT 3 OFFSET 9 +===== ===== + +fruits first page: +SELECT `fruit`.`id`, `fruit`.`name`, `fruit`.`cake_id` FROM `fruit` LIMIT 3 OFFSET 0 +Model { id: 1, name: "Blueberry", cake_id: Some(1) } +Model { id: 2, name: "Rasberry", cake_id: Some(1) } +Model { id: 3, name: "Strawberry", cake_id: Some(2) } +===== ===== + +fruits number of page: +SELECT COUNT(*) AS num_items FROM (SELECT `fruit`.`id`, `fruit`.`name`, `fruit`.`cake_id` FROM `fruit`) AS `sub_query` +3 +===== ===== -```sh INSERT INTO `fruit` (`name`) VALUES ('pear') - -Inserted: InsertResult { last_insert_id: 21 } - -SELECT `fruit`.`id`, `fruit`.`name`, `fruit`.`cake_id` FROM `fruit` WHERE `fruit`.`id` = 21 LIMIT 1 - -Pear: Some(Model { id: 21, name: "pear", cake_id: None }) - -UPDATE `fruit` SET `name` = 'Sweet pear' WHERE `fruit`.`id` = 21 - -Updated: ActiveModel { id: ActiveValue { value: 21, state: Unchanged }, name: ActiveValue { value: "Sweet pear", state: Set }, cake_id: ActiveValue { value: None, state: Unchanged } } - -===== ===== - -INSERT INTO `fruit` (`name`) VALUES ('banana') -SELECT `fruit`.`id`, `fruit`.`name`, `fruit`.`cake_id` FROM `fruit` WHERE `fruit`.`id` = 22 LIMIT 1 - -Inserted: ActiveModel { id: ActiveValue { value: 22, state: Unchanged }, name: ActiveValue { value: "banana", state: Unchanged }, cake_id: ActiveValue { value: None, state: Unchanged } } - -UPDATE `fruit` SET `name` = 'banana banana' WHERE `fruit`.`id` = 22 - -Updated: ActiveModel { id: ActiveValue { value: 22, state: Unchanged }, name: ActiveValue { value: "banana banana", state: Set }, cake_id: ActiveValue { value: None, state: Unchanged } } - -DELETE FROM `fruit` WHERE `fruit`.`id` = 22 - +Inserted: last_insert_id = 64 +SELECT `fruit`.`id`, `fruit`.`name`, `fruit`.`cake_id` FROM `fruit` WHERE `fruit`.`id` = 64 LIMIT 1 +Pear: Some(Model { id: 64, name: "pear", cake_id: None }) +UPDATE `fruit` SET `name` = 'Sweet pear' WHERE `fruit`.`id` = 64 +SELECT `fruit`.`id`, `fruit`.`name`, `fruit`.`cake_id` FROM `fruit` WHERE `fruit`.`id` = 64 LIMIT 1 +Updated: Model { id: 64, name: "Sweet pear", cake_id: None } +DELETE FROM `fruit` WHERE `fruit`.`id` = 64 Deleted: DeleteResult { rows_affected: 1 } - ===== ===== -INSERT INTO `fruit` (`name`) VALUES ('pineapple') -SELECT `fruit`.`id`, `fruit`.`name`, `fruit`.`cake_id` FROM `fruit` WHERE `fruit`.`id` = 23 LIMIT 1 +INSERT INTO `fruit` (`name`) VALUES ('Banana') +SELECT `fruit`.`id`, `fruit`.`name`, `fruit`.`cake_id` FROM `fruit` WHERE `fruit`.`id` = 65 LIMIT 1 +Inserted: ActiveModel { id: Unchanged(65), name: Unchanged("Banana"), cake_id: Unchanged(None) } +UPDATE `fruit` SET `name` = 'Banana Mongo' WHERE `fruit`.`id` = 65 +SELECT `fruit`.`id`, `fruit`.`name`, `fruit`.`cake_id` FROM `fruit` WHERE `fruit`.`id` = 65 LIMIT 1 +Updated: ActiveModel { id: Unchanged(65), name: Unchanged("Banana Mongo"), cake_id: Unchanged(None) } +DELETE FROM `fruit` WHERE `fruit`.`id` = 65 +Deleted: DeleteResult { rows_affected: 1 } +===== ===== -Saved: ActiveModel { id: ActiveValue { value: 23, state: Unchanged }, name: ActiveValue { value: "pineapple", state: Unchanged } } +INSERT INTO `fruit` (`name`) VALUES ('Pineapple') +SELECT `fruit`.`id`, `fruit`.`name`, `fruit`.`cake_id` FROM `fruit` WHERE `fruit`.`id` = 66 LIMIT 1 +Saved: ActiveModel { id: Unchanged(66), name: Unchanged("Pineapple"), cake_id: Unchanged(None) } +DELETE FROM `fruit` WHERE `fruit`.`id` = 66 +Deleted: DeleteResult { rows_affected: 1 } ``` \ No newline at end of file diff --git a/examples/basic/src/operation.rs b/examples/basic/src/operation.rs index 55558e65..ceae382a 100644 --- a/examples/basic/src/operation.rs +++ b/examples/basic/src/operation.rs @@ -22,21 +22,22 @@ pub async fn insert_and_update(db: &DbConn) -> Result<(), DbErr> { }; let res = Fruit::insert(pear).exec(db).await?; - println!(); - println!("Inserted: last_insert_id = {}\n", res.last_insert_id); + println!("Inserted: last_insert_id = {}", res.last_insert_id); let pear: Option = Fruit::find_by_id(res.last_insert_id).one(db).await?; - println!(); - println!("Pear: {pear:?}\n"); + println!("Pear: {pear:?}"); let mut pear: fruit::ActiveModel = pear.unwrap().into(); pear.name = Set("Sweet pear".to_owned()); let pear: fruit::Model = pear.update(db).await?; - println!(); - println!("Updated: {pear:?}\n"); + println!("Updated: {pear:?}"); + + let result = pear.delete(db).await?; + + println!("Deleted: {result:?}"); Ok(()) } @@ -48,20 +49,17 @@ pub async fn save_active_model(db: &DbConn) -> Result<(), DbErr> { }; let mut banana: fruit::ActiveModel = banana.save(db).await?; - println!(); - println!("Inserted: {banana:?}\n"); + println!("Inserted: {banana:?}"); banana.name = Set("Banana Mongo".to_owned()); let banana: fruit::ActiveModel = banana.save(db).await?; - println!(); - println!("Updated: {banana:?}\n"); + println!("Updated: {banana:?}"); let result = banana.delete(db).await?; - println!(); - println!("Deleted: {result:?}\n"); + println!("Deleted: {result:?}"); Ok(()) } @@ -84,8 +82,11 @@ async fn save_custom_active_model(db: &DbConn) -> Result<(), DbErr> { let pineapple = pineapple.save(db).await?; - println!(); - println!("Saved: {pineapple:?}\n"); + println!("Saved: {pineapple:?}"); + + let result = pineapple.delete(db).await?; + + println!("Deleted: {result:?}"); Ok(()) } diff --git a/examples/basic/src/select.rs b/examples/basic/src/select.rs index 611226a2..bb1354fb 100644 --- a/examples/basic/src/select.rs +++ b/examples/basic/src/select.rs @@ -6,18 +6,18 @@ pub async fn all_about_select(db: &DbConn) -> Result<(), DbErr> { println!("===== =====\n"); - find_together(db).await?; - - println!("===== =====\n"); - - find_many(db).await?; - - println!("===== =====\n"); - find_one(db).await?; println!("===== =====\n"); + find_one_to_one(db).await?; + + println!("===== =====\n"); + + find_one_to_many(db).await?; + + println!("===== =====\n"); + count_fruits_by_cake(db).await?; println!("===== =====\n"); @@ -67,21 +67,29 @@ async fn find_all(db: &DbConn) -> Result<(), DbErr> { Ok(()) } -async fn find_together(db: &DbConn) -> Result<(), DbErr> { - print!("find cakes and fruits: "); +async fn find_one_to_one(db: &DbConn) -> Result<(), DbErr> { + print!("find fruits and cakes: "); - let both: Vec<(cake::Model, Option)> = - Cake::find().find_also_related(Fruit).all(db).await?; + let fruits_and_cakes: Vec<(fruit::Model, Option)> = + Fruit::find().find_also_related(Cake).all(db).await?; + + println!("with loader: "); + let fruits: Vec = Fruit::find().all(db).await?; + let cakes: Vec> = fruits.load_one(Cake, db).await?; println!(); - for bb in both.iter() { - println!("{bb:?}\n"); + for (left, right) in fruits_and_cakes + .into_iter() + .zip(fruits.into_iter().zip(cakes.into_iter())) + { + println!("{left:?}"); + assert_eq!(left, right); } Ok(()) } -async fn find_many(db: &DbConn) -> Result<(), DbErr> { +async fn find_one_to_many(db: &DbConn) -> Result<(), DbErr> { print!("find cakes with fruits: "); let cakes_with_fruits: Vec<(cake::Model, Vec)> = Cake::find() @@ -89,9 +97,9 @@ async fn find_many(db: &DbConn) -> Result<(), DbErr> { .all(db) .await?; - // equivalent; but with a different API + println!("with loader: "); let cakes: Vec = Cake::find().all(db).await?; - let fruits: Vec> = cakes.load_many(fruit::Entity, db).await?; + let fruits: Vec> = cakes.load_many(Fruit, db).await?; println!(); for (left, right) in cakes_with_fruits @@ -173,11 +181,10 @@ async fn find_many_to_many(db: &DbConn) -> Result<(), DbErr> { let cakes_with_fillings: Vec<(cake::Model, Vec)> = Cake::find().find_with_related(Filling).all(db).await?; - // equivalent; but with a different API + println!("with loader: "); let cakes: Vec = Cake::find().all(db).await?; - let fillings: Vec> = cakes - .load_many_to_many(filling::Entity, cake_filling::Entity, db) - .await?; + let fillings: Vec> = + cakes.load_many_to_many(Filling, CakeFilling, db).await?; println!(); for (left, right) in cakes_with_fillings @@ -187,7 +194,6 @@ async fn find_many_to_many(db: &DbConn) -> Result<(), DbErr> { println!("{left:?}\n"); assert_eq!(left, right); } - println!(); print!("find fillings for cheese cake: "); @@ -288,8 +294,8 @@ async fn find_all_stream(db: &DbConn) -> Result<(), DbErr> { use futures::TryStreamExt; use std::time::Duration; - println!("find all cakes: "); - let mut cake_paginator = cake::Entity::find().paginate(db, 2); + println!("find all cakes paginated: "); + let mut cake_paginator = cake::Entity::find().paginate(db, 3); while let Some(cake_res) = cake_paginator.fetch_and_next().await? { for cake in cake_res { println!("{cake:?}"); @@ -297,8 +303,8 @@ async fn find_all_stream(db: &DbConn) -> Result<(), DbErr> { } println!(); - println!("find all fruits: "); - let mut fruit_paginator = fruit::Entity::find().paginate(db, 2); + println!("find all fruits paginated: "); + let mut fruit_paginator = fruit::Entity::find().paginate(db, 3); while let Some(fruit_res) = fruit_paginator.fetch_and_next().await? { for fruit in fruit_res { println!("{fruit:?}"); @@ -307,7 +313,7 @@ async fn find_all_stream(db: &DbConn) -> Result<(), DbErr> { println!(); println!("find all fruits with stream: "); - let mut fruit_stream = fruit::Entity::find().paginate(db, 2).into_stream(); + let mut fruit_stream = fruit::Entity::find().paginate(db, 3).into_stream(); while let Some(fruits) = fruit_stream.try_next().await? { for fruit in fruits { println!("{fruit:?}"); @@ -319,7 +325,7 @@ async fn find_all_stream(db: &DbConn) -> Result<(), DbErr> { println!("find all fruits in json with stream: "); let mut json_stream = fruit::Entity::find() .into_json() - .paginate(db, 2) + .paginate(db, 3) .into_stream(); while let Some(jsons) = json_stream.try_next().await? { for json in jsons { @@ -333,7 +339,7 @@ async fn find_all_stream(db: &DbConn) -> Result<(), DbErr> { async fn find_first_page(db: &DbConn) -> Result<(), DbErr> { println!("fruits first page: "); - let page = fruit::Entity::find().paginate(db, 2).fetch_page(0).await?; + let page = fruit::Entity::find().paginate(db, 3).fetch_page(0).await?; for fruit in page { println!("{fruit:?}"); } @@ -343,7 +349,7 @@ async fn find_first_page(db: &DbConn) -> Result<(), DbErr> { async fn find_num_pages(db: &DbConn) -> Result<(), DbErr> { println!("fruits number of page: "); - let num_pages = fruit::Entity::find().paginate(db, 2).num_pages().await?; + let num_pages = fruit::Entity::find().paginate(db, 3).num_pages().await?; println!("{num_pages:?}"); Ok(()) diff --git a/src/query/loader.rs b/src/query/loader.rs index 90e5e35b..53420b01 100644 --- a/src/query/loader.rs +++ b/src/query/loader.rs @@ -387,17 +387,19 @@ where } fn prepare_condition(table: &TableRef, col: &Identity, keys: &[ValueTuple]) -> Condition { + // TODO when value is hashable, retain only unique values + let keys = keys.to_owned(); match col { Identity::Unary(column_a) => { let column_a = table_column(table, column_a); - Condition::all().add(Expr::col(column_a).is_in(keys.iter().cloned().flatten())) + Condition::all().add(Expr::col(column_a).is_in(keys.into_iter().flatten())) } Identity::Binary(column_a, column_b) => Condition::all().add( Expr::tuple([ SimpleExpr::Column(table_column(table, column_a)), SimpleExpr::Column(table_column(table, column_b)), ]) - .in_tuples(keys.iter().cloned()), + .in_tuples(keys), ), Identity::Ternary(column_a, column_b, column_c) => Condition::all().add( Expr::tuple([ @@ -405,7 +407,7 @@ fn prepare_condition(table: &TableRef, col: &Identity, keys: &[ValueTuple]) -> C SimpleExpr::Column(table_column(table, column_b)), SimpleExpr::Column(table_column(table, column_c)), ]) - .in_tuples(keys.iter().cloned()), + .in_tuples(keys), ), } }