use rustrails_support::{database, runtime};
use sea_orm::{ConnectionTrait, Schema};
use crate::base::test_support::{TestUser, seed_users, test_user};
use crate::{OrderDirection, Relation};
fn run_batch_test(seed: bool, test: impl FnOnce() + Send + 'static) {
std::thread::spawn(move || {
let _rt = runtime::init_runtime();
database::establish("sqlite::memory:").expect("sqlite in-memory connection should succeed");
runtime::block_on(async {
let db = database::db();
let schema = Schema::new(db.get_database_backend());
db.execute(&schema.create_table_from_entity(test_user::Entity))
.await
.expect("test_users table should be created");
if seed {
seed_users(&db).await;
}
});
test();
})
.join()
.unwrap();
}
fn run_seeded_batch_test(test: impl FnOnce() + Send + 'static) {
run_batch_test(true, test);
}
fn find_each_names(relation: Relation<TestUser>, batch_size: u64) -> Vec<String> {
let mut names = Vec::new();
relation
.find_each_sync(batch_size, |user| names.push(user.name))
.expect("find_each should succeed");
names
}
fn find_batch_names(relation: Relation<TestUser>, batch_size: u64) -> Vec<Vec<String>> {
let mut batches = Vec::new();
relation
.find_in_batches_sync(batch_size, |users| {
batches.push(users.into_iter().map(|user| user.name).collect());
})
.expect("find_in_batches should succeed");
batches
}
use rustrails_support::ignored_rails_tests;
#[test]
fn test_each_should_execute_one_query_per_batch() {
run_seeded_batch_test(|| {
let names = find_each_names(
Relation::<TestUser>::new().order("id", OrderDirection::Asc),
2,
);
assert_eq!(names, vec!["Alice", "Bob", "Carol"]);
});
}
#[test]
fn test_each_should_not_return_query_chain_and_execute_only_one_query() {
run_seeded_batch_test(|| {
let mut ids = Vec::new();
let result = Relation::<TestUser>::new()
.order("id", OrderDirection::Asc)
.find_each_sync(2, |user| ids.push(user.id.expect("seeded users have ids")));
assert!(result.is_ok());
assert_eq!(ids, vec![1, 2, 3]);
});
}
ignored_rails_tests!(
"Rails-specific: Enumerator contracts are not implemented for batch APIs";
test_each_should_return_an_enumerator_if_no_block_is_present,
test_each_should_return_a_sized_enumerator,
test_each_enumerator_should_execute_one_query_per_batch,
test_find_in_batches_should_return_an_enumerator,
test_find_in_batches_should_return_a_sized_enumerator
);
ignored_rails_tests!(
"Rails-specific: ActiveRecord select/id safety checks are not implemented for batch APIs";
test_each_should_raise_if_select_is_set_without_id
);
#[test]
fn test_each_should_execute_if_id_is_in_select() {
run_seeded_batch_test(|| {
let mut ids = Vec::new();
Relation::<TestUser>::new()
.select(&["id", "name"])
.order("id", OrderDirection::Asc)
.find_each_sync(2, |user| {
ids.push(user.id.expect("selected id should be present"))
})
.expect("find_each should succeed");
assert_eq!(ids, vec![1, 2, 3]);
});
}
ignored_rails_tests!(
"Rails-specific: order-scope warnings and logger integration are not implemented";
test_warn_if_order_scope_is_set
);
#[test]
fn test_logger_not_required() {
run_seeded_batch_test(|| {
let batches = find_batch_names(
Relation::<TestUser>::new().order("id", OrderDirection::Asc),
2,
);
assert_eq!(batches.len(), 2);
});
}
#[test]
fn test_find_in_batches_should_return_batches() {
run_seeded_batch_test(|| {
let batches = find_batch_names(
Relation::<TestUser>::new().order("id", OrderDirection::Asc),
2,
);
assert_eq!(batches, vec![vec!["Alice", "Bob"], vec!["Carol"]]);
});
}
ignored_rails_tests!(
"Rails-specific: start/finish cursor options are not implemented for find_in_batches";
test_find_in_batches_should_start_from_the_start_option,
test_find_in_batches_should_end_at_the_finish_option
);
ignored_rails_tests!(
"Rails-specific: lazy enumerator query execution is not implemented for batch APIs";
test_find_in_batches_shouldnt_execute_query_unless_needed
);
ignored_rails_tests!(
"Rails-specific: SQL batch-order quoting checks are not implemented";
test_find_in_batches_should_quote_batch_order,
test_find_in_batches_should_quote_batch_order_with_desc_order
);
ignored_rails_tests!(
"Rails-specific: invalid-order validation differs because rustrails-record uses typed ordering";
test_each_should_raise_if_order_is_invalid,
test_in_batches_without_block_should_raise_if_order_is_invalid
);
#[test]
fn test_find_in_batches_should_not_use_records_after_yielding_them_in_case_original_array_is_modified()
{
run_seeded_batch_test(|| {
let mut seen = Vec::new();
Relation::<TestUser>::new()
.order("id", OrderDirection::Asc)
.find_in_batches_sync(2, |mut batch| {
let original_names = batch
.iter()
.map(|user| user.name.clone())
.collect::<Vec<_>>();
batch.clear();
seen.push(original_names);
})
.expect("find_in_batches should succeed");
assert_eq!(seen, vec![vec!["Alice", "Bob"], vec!["Carol"]]);
});
}
ignored_rails_tests!(
"Rails-specific: default-scope order handling configuration is not implemented";
test_find_in_batches_should_ignore_the_order_default_scope,
test_find_in_batches_should_error_on_ignore_the_order,
test_find_in_batches_should_not_error_if_config_overridden,
test_find_in_batches_should_error_on_config_specified_to_error,
test_find_in_batches_should_not_error_by_default,
test_find_in_batches_should_not_ignore_the_default_scope_if_it_is_other_then_order
);
ignored_rails_tests!(
"Rails-specific: custom primary-key cursor batching is not implemented";
test_find_in_batches_should_use_any_column_as_primary_key,
test_find_in_batches_should_use_any_column_as_primary_key_when_start_is_not_specified
);
ignored_rails_tests!(
"Rails-specific: Relation::in_batches is not implemented in rustrails-record";
test_in_batches_should_not_execute_any_query,
test_in_batches_should_error_on_ignore_the_order,
test_in_batches_has_attribute_readers,
test_in_batches_should_yield_relation_if_block_given,
test_in_batches_should_be_enumerable_if_no_block_given,
test_in_batches_each_record_should_yield_record_if_block_is_given,
test_in_batches_each_record_should_return_enumerator_if_no_block_given,
test_in_batches_each_record_should_be_ordered_by_id,
test_in_batches_update_all_affect_all_records,
test_in_batches_update_all_returns_rows_affected,
test_in_batches_update_all_returns_zero_when_no_batches,
test_in_batches_touch_all_affect_all_records,
test_in_batches_touch_all_returns_rows_affected,
test_in_batches_touch_all_returns_zero_when_no_batches,
test_in_batches_delete_all_should_not_delete_records_in_other_batches,
test_in_batches_delete_all_returns_rows_affected,
test_in_batches_delete_all_returns_zero_when_no_batches,
test_in_batches_destroy_all_should_not_destroy_records_in_other_batches,
test_in_batches_destroy_all_returns_rows_affected,
test_in_batches_destroy_all_returns_zero_when_no_batches,
test_in_batches_should_not_be_loaded,
test_in_batches_should_be_loaded,
test_in_batches_if_not_loaded_executes_more_queries,
test_in_batches_when_loaded_runs_no_queries,
test_in_batches_when_loaded_runs_no_queries_with_order_argument,
test_in_batches_when_loaded_runs_no_queries_with_start_and_end_arguments,
test_in_batches_when_loaded_runs_no_queries_with_start_and_end_arguments_and_reverse_order,
test_in_batches_when_loaded_can_return_an_enum,
test_in_batches_when_loaded_runs_no_queries_when_batching_over_cpk_model,
test_in_batches_when_loaded_iterates_using_custom_column,
test_in_batches_should_return_relations,
test_in_batches_should_start_from_the_start_option,
test_in_batches_should_end_at_the_finish_option,
test_in_batches_executes_range_queries_when_unconstrained,
test_in_batches_executes_in_queries_when_unconstrained_and_opted_out_of_ranges,
test_in_batches_executes_in_queries_when_constrained,
test_in_batches_executes_range_queries_when_constrained_and_opted_in_into_ranges,
test_in_batches_no_subqueries_for_whole_tables_batching,
test_in_batches_shouldnt_execute_query_unless_needed,
test_in_batches_should_unscope_cursor_after_pluck,
test_in_batches_loaded_should_unscope_cursor_after_pluck,
test_in_batches_should_quote_batch_order,
test_in_batches_should_quote_batch_order_with_desc_order,
test_in_batches_enumerator_should_quote_batch_order_with_desc_order,
test_in_batches_enumerator_each_record_should_quote_batch_order_with_desc_order,
test_in_batches_should_not_use_records_after_yielding_them_in_case_original_array_is_modified,
test_in_batches_should_not_ignore_default_scope_without_order_statements,
test_in_batches_should_use_any_column_as_primary_key,
test_in_batches_should_use_any_column_as_primary_key_when_start_is_not_specified,
test_in_batches_should_return_an_enumerator,
test_in_batches_relations_should_not_overlap_with_each_other,
test_in_batches_relations_with_condition_should_not_overlap_with_each_other,
test_in_batches_relations_update_all_should_not_affect_matching_records_in_other_batches,
test_in_batches_with_custom_columns_raises_when_start_missing_items,
test_in_batches_with_custom_columns_raises_when_finish_missing_items,
test_in_batches_with_custom_columns_raises_when_non_unique_columns,
test_in_batches_iterating_using_custom_columns
);
fn run_empty_batch_test(test: impl FnOnce() + Send + 'static) {
run_batch_test(false, test);
}
#[test]
fn test_find_each_should_honor_limit_if_passed_a_block() {
run_seeded_batch_test(|| {
let names = find_each_names(
Relation::<TestUser>::new()
.order("id", OrderDirection::Asc)
.limit(2),
3,
);
assert_eq!(names, vec!["Alice", "Bob"]);
});
}
#[test]
fn test_find_each_should_honor_offset_if_passed_a_block() {
run_seeded_batch_test(|| {
let names = find_each_names(
Relation::<TestUser>::new()
.order("id", OrderDirection::Asc)
.offset(1),
2,
);
assert_eq!(names, vec!["Bob", "Carol"]);
});
}
#[test]
fn test_find_each_should_honor_limit_and_offset_if_passed_a_block() {
run_seeded_batch_test(|| {
let names = find_each_names(
Relation::<TestUser>::new()
.order("id", OrderDirection::Asc)
.offset(1)
.limit(1),
2,
);
assert_eq!(names, vec!["Bob"]);
});
}
#[test]
fn test_find_each_should_return_no_rows_when_limit_is_zero() {
run_seeded_batch_test(|| {
let names = find_each_names(
Relation::<TestUser>::new()
.order("id", OrderDirection::Asc)
.limit(0),
2,
);
assert!(names.is_empty());
});
}
#[test]
fn test_find_each_should_return_no_rows_when_offset_is_past_end() {
run_seeded_batch_test(|| {
let names = find_each_names(
Relation::<TestUser>::new()
.order("id", OrderDirection::Asc)
.offset(10),
2,
);
assert!(names.is_empty());
});
}
#[test]
fn test_find_each_should_respect_desc_order() {
run_seeded_batch_test(|| {
let names = find_each_names(
Relation::<TestUser>::new().order("id", OrderDirection::Desc),
2,
);
assert_eq!(names, vec!["Carol", "Bob", "Alice"]);
});
}
#[test]
fn test_find_each_should_raise_when_batch_size_is_zero() {
run_seeded_batch_test(|| {
let error = Relation::<TestUser>::new()
.order("id", OrderDirection::Asc)
.find_each_sync(0, |_| ())
.expect_err("zero batch size should be rejected");
assert!(
matches!(error, crate::RecordError::Invalid(message) if message.contains("batch size"))
);
});
}
#[test]
fn test_find_in_batches_should_honor_limit_if_passed_a_block() {
run_seeded_batch_test(|| {
let batches = find_batch_names(
Relation::<TestUser>::new()
.order("id", OrderDirection::Asc)
.limit(2),
3,
);
assert_eq!(batches, vec![vec!["Alice", "Bob"]]);
});
}
#[test]
fn test_find_in_batches_should_honor_offset_if_passed_a_block() {
run_seeded_batch_test(|| {
let batches = find_batch_names(
Relation::<TestUser>::new()
.order("id", OrderDirection::Asc)
.offset(1),
2,
);
assert_eq!(batches, vec![vec!["Bob", "Carol"]]);
});
}
#[test]
fn test_find_in_batches_should_honor_limit_and_offset_if_passed_a_block() {
run_seeded_batch_test(|| {
let batches = find_batch_names(
Relation::<TestUser>::new()
.order("id", OrderDirection::Asc)
.offset(1)
.limit(2),
1,
);
assert_eq!(batches, vec![vec!["Bob"], vec!["Carol"]]);
});
}
#[test]
fn test_find_in_batches_should_return_no_batches_when_limit_is_zero() {
run_seeded_batch_test(|| {
let batches = find_batch_names(
Relation::<TestUser>::new()
.order("id", OrderDirection::Asc)
.limit(0),
2,
);
assert!(batches.is_empty());
});
}
#[test]
fn test_find_in_batches_should_return_no_batches_when_offset_is_past_end() {
run_seeded_batch_test(|| {
let batches = find_batch_names(
Relation::<TestUser>::new()
.order("id", OrderDirection::Asc)
.offset(10),
2,
);
assert!(batches.is_empty());
});
}
#[test]
fn test_find_in_batches_should_respect_desc_order() {
run_seeded_batch_test(|| {
let batches = find_batch_names(
Relation::<TestUser>::new().order("id", OrderDirection::Desc),
2,
);
assert_eq!(batches, vec![vec!["Carol", "Bob"], vec!["Alice"]]);
});
}
#[test]
fn test_find_in_batches_should_error_when_batch_size_is_zero() {
run_seeded_batch_test(|| {
let error = Relation::<TestUser>::new()
.order("id", OrderDirection::Asc)
.find_in_batches_sync(0, |_| ())
.expect_err("zero batch size should be rejected");
assert!(
matches!(error, crate::RecordError::Invalid(message) if message.contains("batch size"))
);
});
}
#[test]
fn test_find_in_batches_should_return_one_batch_when_batch_size_exceeds_limit() {
run_seeded_batch_test(|| {
let batches = find_batch_names(
Relation::<TestUser>::new()
.order("id", OrderDirection::Asc)
.limit(1),
5,
);
assert_eq!(batches, vec![vec!["Alice"]]);
});
}
#[test]
fn test_find_each_should_yield_no_rows_for_empty_relations() {
run_empty_batch_test(|| {
let names = find_each_names(
Relation::<TestUser>::new().order("id", OrderDirection::Asc),
2,
);
assert!(names.is_empty());
});
}
#[test]
fn test_find_in_batches_should_yield_no_batches_for_empty_relations() {
run_empty_batch_test(|| {
let batches = find_batch_names(
Relation::<TestUser>::new().order("id", OrderDirection::Asc),
2,
);
assert!(batches.is_empty());
});
}
ignored_rails_tests!(
"Rails-specific: query-cache instrumentation and table alias support are not exposed by rustrails-record batch APIs";
test_find_each_respects_table_alias,
test_find_each_bypasses_the_query_cache_for_its_own_queries,
test_find_each_does_not_disable_the_query_cache_inside_the_given_block,
test_find_in_batches_bypasses_the_query_cache_for_its_own_queries,
test_find_in_batches_does_not_disable_the_query_cache_inside_the_given_block
);
ignored_rails_tests!(
"Rails-specific: composite primary-key batch traversal is not implemented for TestUser";
test_find_each_iterates_over_composite_primary_key,
test_find_each_with_multiple_column_ordering_and_using_composite_primary_key,
test_find_in_batches_with_composite_primary_key
);