pub use sz_rust_orm_facade::find_with_related::{
find_with_related_eager_sql, find_with_related_join, find_with_related_subquery,
inspect_relation, FindWithRelated, WithRelation as FindWithRelation,
};
use super::Relation;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum JoinMode {
Left,
Inner,
}
pub fn join_mode_str(mode: JoinMode) -> &'static str {
match mode {
JoinMode::Left => "LEFT",
JoinMode::Inner => "INNER",
}
}
pub fn is_one_to_one(relation: &Relation) -> bool {
matches!(relation, Relation::HasOne(_) | Relation::BelongsTo(_))
}
pub fn php_with_join_sql(
main_table: &str,
related_table: &str,
main_key: &str,
related_key: &str,
join_mode: JoinMode,
) -> String {
format!(
"SELECT main.* FROM {} main {} JOIN {} related ON main.{} = related.{}",
main_table,
join_mode_str(join_mode),
related_table,
main_key,
related_key
)
}
pub fn php_has_join_sql(
main_table: &str,
related_table: &str,
main_key: &str,
related_key: &str,
count_field: &str,
operator: &str,
count: i64,
) -> String {
format!(
"SELECT main.* FROM {} main INNER JOIN {} related ON main.{} = related.{} GROUP BY related.{} HAVING count({}) {} {}",
main_table,
related_table,
main_key,
related_key,
related_key,
count_field,
operator,
count
)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::relation::{BelongsTo, HasMany, HasOne};
#[test]
fn test_join_mode_str_left() {
assert_eq!(join_mode_str(JoinMode::Left), "LEFT");
}
#[test]
fn test_join_mode_str_inner() {
assert_eq!(join_mode_str(JoinMode::Inner), "INNER");
}
#[test]
fn test_join_mode_eq_and_copy() {
let mode1 = JoinMode::Left;
let mode2 = mode1; assert_eq!(mode1, mode2); assert_eq!(format!("{:?}", mode1), "Left"); }
#[test]
fn test_join_mode_default_is_left() {
let default_mode = JoinMode::Left;
assert_eq!(join_mode_str(default_mode), "LEFT");
}
#[test]
fn test_is_one_to_one_has_one() {
let rel = Relation::HasOne(HasOne {
foreign_key: "user_id".to_string(),
child_model: "profiles".to_string(),
child_pk: "id".to_string(),
});
assert!(is_one_to_one(&rel));
}
#[test]
fn test_is_one_to_one_belongs_to() {
let rel = Relation::BelongsTo(BelongsTo {
foreign_key: "user_id".to_string(),
parent_model: "users".to_string(),
parent_pk: "id".to_string(),
});
assert!(is_one_to_one(&rel));
}
#[test]
fn test_is_one_to_one_has_many_returns_false() {
let rel = Relation::HasMany(HasMany {
foreign_key: "user_id".to_string(),
child_model: "orders".to_string(),
child_pk: "id".to_string(),
});
assert!(!is_one_to_one(&rel));
}
#[test]
fn test_is_one_to_one_belongs_to_many_returns_false() {
let rel = Relation::BelongsToMany(crate::relation::BelongsToMany {
junction_table: "user_role".to_string(),
foreign_key: "user_id".to_string(),
other_key: "role_id".to_string(),
target_model: "roles".to_string(),
target_pk: "id".to_string(),
});
assert!(!is_one_to_one(&rel));
}
#[test]
fn test_php_with_join_sql_has_one_left_join() {
let sql = php_with_join_sql("users", "profiles", "id", "user_id", JoinMode::Left);
assert_eq!(
sql,
"SELECT main.* FROM users main LEFT JOIN profiles related ON main.id = related.user_id"
);
}
#[test]
fn test_php_with_join_sql_belongs_to_inner_join() {
let sql = php_with_join_sql("orders", "users", "user_id", "id", JoinMode::Inner);
assert_eq!(
sql,
"SELECT main.* FROM orders main INNER JOIN users related ON main.user_id = related.id"
);
}
#[test]
fn test_php_with_join_sql_has_one_default_left() {
let sql = php_with_join_sql("users", "profiles", "id", "user_id", JoinMode::Left);
assert!(sql.contains("LEFT JOIN"));
}
#[test]
fn test_php_with_join_sql_multi_word_tables() {
let sql = php_with_join_sql(
"order_items",
"product_details",
"id",
"order_item_id",
JoinMode::Left,
);
assert_eq!(
sql,
"SELECT main.* FROM order_items main LEFT JOIN product_details related ON main.id = related.order_item_id"
);
}
#[test]
fn test_php_with_join_sql_aligns_one_to_one_php_eagerly() {
let sql = php_with_join_sql("users", "profiles", "id", "user_id", JoinMode::Left);
assert!(sql.starts_with("SELECT main.* FROM"));
assert!(sql.contains(" LEFT JOIN "));
assert!(sql.contains(" ON main."));
assert!(sql.contains(" = related."));
}
#[test]
fn test_php_has_join_sql_default_operator_ge() {
let sql = php_has_join_sql("users", "orders", "id", "user_id", "*", ">=", 3);
assert_eq!(
sql,
"SELECT main.* FROM users main INNER JOIN orders related ON main.id = related.user_id GROUP BY related.user_id HAVING count(*) >= 3"
);
}
#[test]
fn test_php_has_join_sql_operator_gt() {
let sql = php_has_join_sql("users", "orders", "id", "user_id", "*", ">", 5);
assert!(sql.contains("HAVING count(*) > 5"));
}
#[test]
fn test_php_has_join_sql_operator_eq() {
let sql = php_has_join_sql("users", "orders", "id", "user_id", "*", "=", 1);
assert!(sql.contains("HAVING count(*) = 1"));
}
#[test]
fn test_php_has_join_sql_count_field_specific() {
let sql = php_has_join_sql("users", "orders", "id", "user_id", "orders.id", ">=", 2);
assert!(sql.contains("HAVING count(orders.id) >= 2"));
}
#[test]
fn test_php_has_join_sql_multi_word_tables() {
let sql = php_has_join_sql(
"order_items",
"product_details",
"id",
"order_item_id",
"*",
">=",
1,
);
assert!(sql.contains("FROM order_items main"));
assert!(sql.contains("INNER JOIN product_details related"));
assert!(sql.contains("ON main.id = related.order_item_id"));
assert!(sql.contains("GROUP BY related.order_item_id"));
}
#[test]
fn test_php_has_join_sql_aligns_php_has_many_has_method() {
let sql = php_has_join_sql("users", "orders", "id", "user_id", "*", ">=", 1);
assert!(sql.starts_with("SELECT main.* FROM"));
assert!(sql.contains(" INNER JOIN "));
assert!(sql.contains(" ON main."));
assert!(sql.contains(" GROUP BY related."));
assert!(sql.contains(" HAVING count("));
}
#[test]
fn test_php_has_join_sql_default_inner_join() {
let sql = php_has_join_sql("users", "orders", "id", "user_id", "*", ">=", 1);
assert!(sql.contains("INNER JOIN"));
assert!(!sql.contains("LEFT JOIN"));
}
#[test]
fn test_re_export_find_with_related_accessible() {
const _: () = {
type _AssertFindWithRelatedResolves<'a> = FindWithRelated<'a>;
};
}
#[test]
fn test_re_export_find_with_relation_accessible() {
const _: () = {
type _AssertFindWithRelationResolves<'a> = FindWithRelation<'a>;
};
}
#[test]
fn test_re_export_inspect_relation_callable() {
use std::collections::HashMap;
let mut relations: HashMap<&str, Relation> = HashMap::new();
relations.insert(
"orders",
Relation::HasMany(HasMany {
foreign_key: "user_id".to_string(),
child_model: "orders".to_string(),
child_pk: "id".to_string(),
}),
);
let result = inspect_relation(&relations, "orders");
assert!(result.is_some());
let (related_table, foreign_key, primary_key, is_many) = result.unwrap();
assert_eq!(related_table, "orders");
assert_eq!(foreign_key, "user_id");
assert_eq!(primary_key, "id");
assert!(is_many);
}
#[test]
fn test_re_export_inspect_relation_not_found() {
use std::collections::HashMap;
let relations: HashMap<&str, Relation> = HashMap::new();
let result = inspect_relation(&relations, "nonexistent");
assert!(result.is_none());
}
#[test]
fn test_re_export_inspect_relation_has_one() {
use std::collections::HashMap;
let mut relations: HashMap<&str, Relation> = HashMap::new();
relations.insert(
"profile",
Relation::HasOne(HasOne {
foreign_key: "user_id".to_string(),
child_model: "profiles".to_string(),
child_pk: "id".to_string(),
}),
);
let result = inspect_relation(&relations, "profile");
let (_, _, _, is_many) = result.unwrap();
assert!(!is_many);
}
#[test]
fn test_re_export_inspect_relation_belongs_to() {
use std::collections::HashMap;
let mut relations: HashMap<&str, Relation> = HashMap::new();
relations.insert(
"dept",
Relation::BelongsTo(BelongsTo {
foreign_key: "dept_id".to_string(),
parent_model: "depts".to_string(),
parent_pk: "id".to_string(),
}),
);
let result = inspect_relation(&relations, "dept");
let (related_table, foreign_key, primary_key, is_many) = result.unwrap();
assert_eq!(related_table, "depts");
assert_eq!(foreign_key, "dept_id");
assert_eq!(primary_key, "id");
assert!(!is_many);
}
#[test]
fn test_re_export_inspect_relation_belongs_to_many() {
use std::collections::HashMap;
let mut relations: HashMap<&str, Relation> = HashMap::new();
relations.insert(
"roles",
Relation::BelongsToMany(crate::relation::BelongsToMany {
junction_table: "user_role".to_string(),
foreign_key: "user_id".to_string(),
other_key: "role_id".to_string(),
target_model: "roles".to_string(),
target_pk: "id".to_string(),
}),
);
let result = inspect_relation(&relations, "roles");
let (related_table, _, _, is_many) = result.unwrap();
assert_eq!(related_table, "roles");
assert!(is_many);
}
#[test]
fn test_re_export_convenience_functions_callable() {
type _AssertJoinFn = for<'a> fn(
&'a dyn sz_orm_core::Dialect,
&'a str,
&'a str,
&'a str,
&'a str,
bool,
)
-> Result<FindWithRelated<'a>, sz_orm_core::DbError>;
type _AssertEagerSqlFn = fn(
&dyn sz_orm_core::Dialect,
&str,
&str,
&str,
Option<&str>,
) -> Result<(String, String), sz_orm_core::DbError>;
type _AssertSubqueryFn = fn(
&dyn sz_orm_core::Dialect,
&str,
&str,
&str,
&str,
Option<&str>,
) -> Result<String, sz_orm_core::DbError>;
const _: () = {
let _join: _AssertJoinFn = find_with_related_join;
let _eager_sql: _AssertEagerSqlFn = find_with_related_eager_sql;
let _subquery: _AssertSubqueryFn = find_with_related_subquery;
};
}
#[test]
fn test_r5_php_with_join_only_for_one_to_one() {
let has_many = Relation::HasMany(HasMany {
foreign_key: "user_id".to_string(),
child_model: "orders".to_string(),
child_pk: "id".to_string(),
});
assert!(!is_one_to_one(&has_many));
let has_one = Relation::HasOne(HasOne {
foreign_key: "user_id".to_string(),
child_model: "profiles".to_string(),
child_pk: "id".to_string(),
});
assert!(is_one_to_one(&has_one));
let belongs_to = Relation::BelongsTo(BelongsTo {
foreign_key: "user_id".to_string(),
parent_model: "users".to_string(),
parent_pk: "id".to_string(),
});
assert!(is_one_to_one(&belongs_to)); }
#[test]
fn test_r5_php_eagerly_join_on_has_one() {
let sql = php_with_join_sql("users", "profiles", "id", "user_id", JoinMode::Left);
assert_eq!(
sql,
"SELECT main.* FROM users main LEFT JOIN profiles related ON main.id = related.user_id"
);
}
#[test]
fn test_r5_php_eagerly_join_on_belongs_to() {
let sql = php_with_join_sql("orders", "users", "user_id", "id", JoinMode::Inner);
assert_eq!(
sql,
"SELECT main.* FROM orders main INNER JOIN users related ON main.user_id = related.id"
);
}
#[test]
fn test_r5_php_with_join_default_left() {
let sql_left = php_with_join_sql("users", "profiles", "id", "user_id", JoinMode::Left);
assert!(sql_left.contains("LEFT JOIN"));
}
#[test]
fn test_r5_php_has_default_inner_join() {
let sql = php_has_join_sql("users", "orders", "id", "user_id", "*", ">=", 1);
assert!(sql.contains("INNER JOIN"));
}
#[test]
fn test_r5_php_has_group_by_foreign_key() {
let sql = php_has_join_sql("users", "orders", "id", "user_id", "*", ">=", 1);
assert!(sql.contains("GROUP BY related.user_id"));
}
#[test]
fn test_r5_php_has_having_count() {
let sql = php_has_join_sql("users", "orders", "id", "user_id", "*", ">=", 3);
assert!(sql.contains("HAVING count(*) >= 3"));
}
#[test]
fn test_r5_php_has_count_field_star_default() {
let sql = php_has_join_sql("users", "orders", "id", "user_id", "*", ">=", 1);
assert!(sql.contains("count(*)"));
}
#[test]
fn test_r5_php_with_join_returns_no_result_for_non_one_to_one() {
let has_many_rel = Relation::HasMany(HasMany {
foreign_key: "user_id".to_string(),
child_model: "orders".to_string(),
child_pk: "id".to_string(),
});
if is_one_to_one(&has_many_rel) {
panic!("HasMany should not be OneToOne");
}
}
#[test]
fn test_integration_with_join_then_has_combined() {
let with_join_sql = php_with_join_sql("users", "profiles", "id", "user_id", JoinMode::Left);
assert!(with_join_sql.contains("LEFT JOIN profiles"));
let has_sql = php_has_join_sql("users", "orders", "id", "user_id", "*", ">=", 3);
assert!(has_sql.contains("INNER JOIN orders"));
assert!(has_sql.contains("HAVING count(*) >= 3"));
}
#[test]
fn test_integration_join_mode_round_trip() {
for mode in [JoinMode::Left, JoinMode::Inner] {
let mode_str = join_mode_str(mode);
let sql = php_with_join_sql("users", "profiles", "id", "user_id", mode);
assert!(sql.contains(&format!(" {} JOIN ", mode_str)));
}
}
#[test]
fn test_integration_inspect_relation_then_php_with_join_sql() {
use std::collections::HashMap;
let mut relations: HashMap<&str, Relation> = HashMap::new();
relations.insert(
"profile",
Relation::HasOne(HasOne {
foreign_key: "user_id".to_string(),
child_model: "profiles".to_string(),
child_pk: "id".to_string(),
}),
);
let inspect_result = inspect_relation(&relations, "profile").unwrap();
let (related_table, foreign_key, primary_key, is_many) = inspect_result;
assert!(!is_many);
let relation_ref = relations.get("profile").unwrap();
assert!(is_one_to_one(relation_ref));
let sql = php_with_join_sql(
"users",
related_table,
primary_key,
foreign_key,
JoinMode::Left,
);
assert_eq!(
sql,
"SELECT main.* FROM users main LEFT JOIN profiles related ON main.id = related.user_id"
);
}
#[test]
fn test_integration_inspect_relation_rejects_has_many_for_join() {
use std::collections::HashMap;
let mut relations: HashMap<&str, Relation> = HashMap::new();
relations.insert(
"orders",
Relation::HasMany(HasMany {
foreign_key: "user_id".to_string(),
child_model: "orders".to_string(),
child_pk: "id".to_string(),
}),
);
let inspect_result = inspect_relation(&relations, "orders").unwrap();
let (_, _, _, is_many) = inspect_result;
assert!(is_many);
let relation_ref = relations.get("orders").unwrap();
assert!(!is_one_to_one(relation_ref));
}
#[test]
fn test_integration_three_modes_comparison() {
use crate::relation::with::has_many_in_sql;
let in_sql = has_many_in_sql("orders", "user_id", &["1", "2", "3"]);
assert!(in_sql.contains("WHERE user_id IN (1, 2, 3)"));
let join_sql = php_with_join_sql("users", "profiles", "id", "user_id", JoinMode::Left);
assert!(join_sql.contains("LEFT JOIN"));
let has_sql = php_has_join_sql("users", "orders", "id", "user_id", "*", ">=", 1);
assert!(has_sql.contains("INNER JOIN"));
assert!(has_sql.contains("GROUP BY"));
assert!(has_sql.contains("HAVING count"));
}
}