diff options
Diffstat (limited to '')
| -rw-r--r-- | test/join_test.erl | 273 |
1 files changed, 0 insertions, 273 deletions
diff --git a/test/join_test.erl b/test/join_test.erl deleted file mode 100644 index de62127..0000000 --- a/test/join_test.erl +++ /dev/null @@ -1,273 +0,0 @@ --module(join_test). --include_lib("eunit/include/eunit.hrl"). --include("operations.hrl"). - -%%% Join Operators Test Suite -%%% -%%% Tests for relational join operations: -%%% - Equijoin (natural join on attribute equality) -%%% - Theta-join (join with arbitrary predicate) - -join_test_() -> - {foreach, - fun setup/0, - fun cleanup/1, - [ - fun test_equijoin_simple/1, - fun test_equijoin_multiple_matches/1, - fun test_equijoin_no_matches/1, - fun test_equijoin_with_conflicts/1, - fun test_theta_join_greater_than/1, - fun test_theta_join_complex_predicate/1, - fun test_join_with_infinite_relation/1, - fun test_join_materialize_pipeline/1 - ]}. - -%%% Setup and Cleanup - -setup() -> - operations:setup(), - DB = operations:create_database(test_db), - - % Create employees relation - {DB1, _} = operations:create_relation(DB, employees, #{ - id => integer, - name => string, - dept_id => integer, - age => integer - }), - - {DB2, _} = operations:create_tuple(DB1, employees, #{id => 1, name => "Alice", dept_id => 10, age => 30}), - {DB3, _} = operations:create_tuple(DB2, employees, #{id => 2, name => "Bob", dept_id => 20, age => 35}), - {DB4, _} = operations:create_tuple(DB3, employees, #{id => 3, name => "Carol", dept_id => 10, age => 28}), - {DB5, _} = operations:create_tuple(DB4, employees, #{id => 4, name => "Dave", dept_id => 30, age => 42}), - - % Create departments relation - {DB6, _} = operations:create_relation(DB5, departments, #{ - dept_id => integer, - dept_name => string, - budget => integer - }), - - {DB7, _} = operations:create_tuple(DB6, departments, #{dept_id => 10, dept_name => "Engineering", budget => 100000}), - {DB8, _} = operations:create_tuple(DB7, departments, #{dept_id => 20, dept_name => "Sales", budget => 80000}), - {DB9, _} = operations:create_tuple(DB8, departments, #{dept_id => 30, dept_name => "Marketing", budget => 90000}), - - DB9. - -cleanup(_DB) -> - ok. - -%%% Equijoin Tests - -test_equijoin_simple(DB) -> - % Join employees with departments on dept_id - EmpIter = operations:get_tuples_iterator(DB, employees, #{}), - DeptIter = operations:get_tuples_iterator(DB, departments, #{}), - - JoinIter = operations:equijoin_iterator(EmpIter, DeptIter, dept_id), - Results = operations:collect_all(JoinIter), - - % Should get 4 joined tuples (one for each employee) - [ - ?_assertEqual(4, length(Results)), - % Check Alice joined with Engineering - ?_assert(lists:any(fun(T) -> - maps:get(name, T, undefined) =:= "Alice" andalso - maps:get(dept_name, T, undefined) =:= "Engineering" - end, Results)), - % Check Bob joined with Sales - ?_assert(lists:any(fun(T) -> - maps:get(name, T, undefined) =:= "Bob" andalso - maps:get(dept_name, T, undefined) =:= "Sales" - end, Results)), - % All results should have both employee and department attributes - ?_assert(lists:all(fun(T) -> - maps:is_key(name, T) andalso - maps:is_key(dept_name, T) andalso - maps:is_key(dept_id, T) - end, Results)) - ]. - -test_equijoin_multiple_matches(DB) -> - % Multiple employees in same department should produce multiple results - EmpIter = operations:get_tuples_iterator(DB, employees, #{}), - DeptIter = operations:get_tuples_iterator(DB, departments, #{}), - - JoinIter = operations:equijoin_iterator(EmpIter, DeptIter, dept_id), - Results = operations:collect_all(JoinIter), - - % Engineering dept (10) has 2 employees (Alice and Carol) - EngineeringResults = lists:filter(fun(T) -> - maps:get(dept_name, T, undefined) =:= "Engineering" - end, Results), - - [ - ?_assertEqual(2, length(EngineeringResults)), - ?_assert(lists:any(fun(T) -> maps:get(name, T) =:= "Alice" end, EngineeringResults)), - ?_assert(lists:any(fun(T) -> maps:get(name, T) =:= "Carol" end, EngineeringResults)) - ]. - -test_equijoin_no_matches(DB) -> - % Create a department with no employees - {DB1, _} = operations:create_tuple(DB, departments, #{dept_id => 40, dept_name => "HR", budget => 70000}), - - % Filter to only dept_id=40 which has no employees - DeptIter = operations:get_tuples_iterator(DB1, departments, #{}), - FilteredDept = operations:select_iterator(DeptIter, fun(D) -> maps:get(dept_id, D) =:= 40 end), - - EmpIter = operations:get_tuples_iterator(DB1, employees, #{}), - - JoinIter = operations:equijoin_iterator(EmpIter, FilteredDept, dept_id), - Results = operations:collect_all(JoinIter), - - % No employees in HR, so no results - [ - ?_assertEqual(0, length(Results)) - ]. - -test_equijoin_with_conflicts(DB) -> - % Create relations with conflicting attribute names - {DB1, _} = operations:create_relation(DB, table_a, #{ - id => integer, - value => integer - }), - {DB2, _} = operations:create_tuple(DB1, table_a, #{id => 1, value => 100}), - - {DB3, _} = operations:create_relation(DB2, table_b, #{ - id => integer, - value => integer % Same name as table_a.value - }), - {DB4, _} = operations:create_tuple(DB3, table_b, #{id => 1, value => 200}), - - AIter = operations:get_tuples_iterator(DB4, table_a, #{}), - BIter = operations:get_tuples_iterator(DB4, table_b, #{}), - - JoinIter = operations:equijoin_iterator(AIter, BIter, id), - Results = operations:collect_all(JoinIter), - - % Should have one result with both values - % Right side 'value' should be prefixed as 'right_value' - [ - ?_assertEqual(1, length(Results)), - ?_assertMatch([#{id := 1, value := 100, right_value := 200}], Results) - ]. - -%%% Theta-Join Tests - -test_theta_join_greater_than(DB) -> - % Join where employee.age > department.budget/1000 - % (contrived example for testing) - - EmpIter = operations:get_tuples_iterator(DB, employees, #{}), - DeptIter = operations:get_tuples_iterator(DB, departments, #{}), - - Predicate = fun(Emp, Dept) -> - Age = maps:get(age, Emp), - Budget = maps:get(budget, Dept), - Age > (Budget div 2000) % Age > Budget/2000 - end, - - JoinIter = operations:theta_join_iterator(EmpIter, DeptIter, Predicate), - Results = operations:collect_all(JoinIter), - - % Should get some results based on predicate - [ - ?_assert(length(Results) > 0), - % Verify predicate holds for all results - ?_assert(lists:all(fun(T) -> - Age = maps:get(age, T), - Budget = maps:get(budget, T), - Age > (Budget div 2000) - end, Results)) - ]. - -test_theta_join_complex_predicate(DB) -> - % Join with multiple conditions - EmpIter = operations:get_tuples_iterator(DB, employees, #{}), - DeptIter = operations:get_tuples_iterator(DB, departments, #{}), - - % Join where employee is in department AND age > 30 - Predicate = fun(Emp, Dept) -> - maps:get(dept_id, Emp) =:= maps:get(dept_id, Dept) andalso - maps:get(age, Emp) > 30 - end, - - JoinIter = operations:theta_join_iterator(EmpIter, DeptIter, Predicate), - Results = operations:collect_all(JoinIter), - - % Should get Bob (35) and Dave (42), not Alice (30) or Carol (28) - [ - ?_assertEqual(2, length(Results)), - ?_assert(lists:all(fun(T) -> maps:get(age, T) > 30 end, Results)), - ?_assert(lists:any(fun(T) -> maps:get(name, T) =:= "Bob" end, Results)), - ?_assert(lists:any(fun(T) -> maps:get(name, T) =:= "Dave" end, Results)) - ]. - -%%% Integration Tests - -test_join_with_infinite_relation(DB) -> - % Integers are built-in, no need to create them - - % Get small set of integers - IntIter = operations:get_tuples_iterator(DB, integer, #{value => {range, 1, 5}}), - - % Get employees - EmpIter = operations:get_tuples_iterator(DB, employees, #{}), - - % Join where employee.id matches integer.value - Predicate = fun(Emp, Int) -> - maps:get(id, Emp) =:= maps:get(value, Int) - end, - - JoinIter = operations:theta_join_iterator(EmpIter, IntIter, Predicate), - Results = operations:collect_all(JoinIter), - - % Should get employees with id in [1,5] (all 4 employees) - [ - ?_assertEqual(4, length(Results)), - ?_assert(lists:all(fun(T) -> - Id = maps:get(id, T), - Value = maps:get(value, T), - Id =:= Value andalso Id >= 1 andalso Id =< 5 - end, Results)) - ]. - -test_join_materialize_pipeline(DB) -> - % Complex pipeline: join, filter, project, materialize - - % 1. Join employees with departments - EmpIter = operations:get_tuples_iterator(DB, employees, #{}), - DeptIter = operations:get_tuples_iterator(DB, departments, #{}), - JoinIter = operations:equijoin_iterator(EmpIter, DeptIter, dept_id), - - % 2. Filter to high-budget departments - FilteredIter = operations:select_iterator(JoinIter, fun(T) -> - maps:get(budget, T) >= 90000 - end), - - % 3. Project to [name, dept_name, budget] - ProjectedIter = operations:project_iterator(FilteredIter, [name, dept_name, budget]), - - % 4. Materialize - {DB1, HighBudgetEmps} = operations:materialize(DB, ProjectedIter, high_budget_employees), - - % Verify materialized relation - ResultIter = operations:get_tuples_iterator(DB1, high_budget_employees, #{}), - Results = operations:collect_all(ResultIter), - - % Engineering (100k) has Alice and Carol - % Marketing (90k) has Dave - % Sales (80k) is excluded - [ - ?_assertEqual({finite, 3}, HighBudgetEmps#relation.cardinality), - ?_assertEqual(3, length(Results)), - ?_assert(lists:all(fun(T) -> - maps:is_key(name, T) andalso - maps:is_key(dept_name, T) andalso - maps:is_key(budget, T) andalso - not maps:is_key(id, T) andalso % Projected out - not maps:is_key(age, T) % Projected out - end, Results)), - ?_assert(lists:all(fun(T) -> maps:get(budget, T) >= 90000 end, Results)) - ]. |
