diff options
| author | Marcos Magueta <maguetamarcos@gmail.com> | 2025-12-14 00:27:18 -0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-12-14 00:27:18 -0300 |
| commit | ff5691ffb1a969e50e0bd5e4638be8e2bb342c4e (patch) | |
| tree | 565346b82901f2ee26cb49b04e1e99ba1fb0e896 /test/provenance_test.erl | |
| parent | 0862cd7c3fe4fbb5b56a0a87c3789c8361bc022c (diff) | |
| download | Sakura-ff5691ffb1a969e50e0bd5e4638be8e2bb342c4e.tar.gz Sakura-ff5691ffb1a969e50e0bd5e4638be8e2bb342c4e.zip | |
Ephemeral relations, Refactor & Cleanup (#34)
Diffstat (limited to 'test/provenance_test.erl')
| -rw-r--r-- | test/provenance_test.erl | 420 |
1 files changed, 0 insertions, 420 deletions
diff --git a/test/provenance_test.erl b/test/provenance_test.erl deleted file mode 100644 index c98e372..0000000 --- a/test/provenance_test.erl +++ /dev/null @@ -1,420 +0,0 @@ --module(provenance_test). --include_lib("eunit/include/eunit.hrl"). --include("operations.hrl"). - -%%% Provenance & Lineage Tracking Test Suite -%%% -%%% Tests for tuple metadata tracking in relational operations. -%%% Metadata is ALWAYS tracked and included in every tuple via the nested 'meta' field: -%%% - Provenance: Tracks the immediate source of each attribute -%%% - Lineage: Tracks the complete operational history of tuple creation -%%% -%%% NOTE ON TUPLE ORDERING: -%%% These tests observe tuples in a specific order (e.g., Alice before Bob) because -%%% collect_all/1 currently returns results in Merkle tree traversal order for finite -%%% relations. This ordering is an IMPLEMENTATION DETAIL and is NOT guaranteed. -%%% Relations are unordered sets - the current order is based on how tuples were -%%% inserted and hashed into the Merkle tree. Future implementations may return tuples -%%% in different orders. Tests use lists:all/2 to verify properties hold for ALL tuples -%%% rather than relying on specific ordering. - -provenance_test_() -> - {foreach, - fun setup/0, - fun cleanup/1, - [ - fun test_base_provenance/1, - fun test_join_provenance/1, - fun test_join_with_conflicts_provenance/1, - fun test_equijoin_preserves_both_sources/1, - fun test_base_lineage/1, - fun test_join_lineage/1, - fun test_select_lineage/1, - fun test_project_lineage/1, - fun test_sort_lineage/1, - fun test_take_lineage/1, - fun test_multi_operation_pipeline_lineage/1, - fun test_theta_join_lineage/1, - fun test_complex_nested_lineage/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 - }), - - {DB2, _} = operations:create_tuple(DB1, employees, #{id => 1, name => "Alice", dept_id => 10}), - {DB3, _} = operations:create_tuple(DB2, employees, #{id => 2, name => "Bob", dept_id => 20}), - - % Create departments relation - {DB4, _} = operations:create_relation(DB3, departments, #{ - dept_id => integer, - dept_name => string, - budget => integer - }), - - {DB5, _} = operations:create_tuple(DB4, departments, #{dept_id => 10, dept_name => "Engineering", budget => 100000}), - {DB6, _} = operations:create_tuple(DB5, departments, #{dept_id => 20, dept_name => "Sales", budget => 80000}), - - DB6. - -cleanup(_DB) -> - ok. - -%%% Base Provenance Tests - -test_base_provenance(DB) -> - % Query tuples (metadata always included) - Iter = operations:get_tuples_iterator(DB, employees, #{}), - Results = operations:collect_all(Iter), - - % All tuples should have correct provenance structure - [ - ?_assertEqual(2, length(Results)), - ?_assert(lists:all(fun(T) -> - maps:is_key(meta, T) andalso - begin - Meta = maps:get(meta, T), - maps:is_key(provenance, Meta) - end - end, Results)), - % Check ALL tuples have correct provenance mapping - ?_assert(lists:all(fun(T) -> - Meta = maps:get(meta, T), - Prov = maps:get(provenance, Meta), - % All attributes should map to {employees, attr_name} - maps:get(id, Prov) =:= {employees, id} andalso - maps:get(name, Prov) =:= {employees, name} andalso - maps:get(dept_id, Prov) =:= {employees, dept_id} - end, Results)) - ]. - -%%% Join Provenance Tests - -test_join_provenance(DB) -> - % Create iterators (metadata always included) - EmpIter = operations:get_tuples_iterator(DB, employees, #{}), - DeptIter = operations:get_tuples_iterator(DB, departments, #{}), - - % Join them - JoinIter = operations:equijoin_iterator(EmpIter, DeptIter, dept_id), - Results = operations:collect_all(JoinIter), - - % All results should have metadata with provenance - [ - ?_assertEqual(2, length(Results)), - ?_assert(lists:all(fun(T) -> maps:is_key(meta, T) end, Results)), - % Check that ALL tuples have correct provenance structure - ?_assert(lists:all(fun(T) -> - Meta = maps:get(meta, T), - Prov = maps:get(provenance, Meta), - % Employee attributes should come from employees - maps:get(id, Prov) =:= {employees, id} andalso - maps:get(name, Prov) =:= {employees, name} andalso - % Department attributes should come from departments - maps:get(dept_name, Prov) =:= {departments, dept_name} andalso - maps:get(budget, Prov) =:= {departments, budget} - end, Results)) - ]. - -test_join_with_conflicts_provenance(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 % Conflict! - }), - {DB4, _} = operations:create_tuple(DB3, table_b, #{id => 1, value => 200}), - - % Create iterators with provenance - 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 be exactly one result tuple - ?_assertEqual(1, length(Results)), - - % Check provenance handles conflicts correctly - [Result] = Results, - Meta = maps:get(meta, Result), - Prov = maps:get(provenance, Meta), - - [ - % Left 'value' provenance - ?_assertEqual({table_a, value}, maps:get(value, Prov)), - % Right 'value' provenance (prefixed) - ?_assertEqual({table_b, value}, maps:get(right_value, Prov)), - % Join attribute from left side - ?_assertEqual({table_a, id}, maps:get(id, Prov)), - % Join attribute from right side (prefixed) - ?_assertEqual({table_b, id}, maps:get(right_id, Prov)) - ]. - -test_equijoin_preserves_both_sources(DB) -> - % Even though equijoin only keeps one dept_id value, - % provenance should track both sources - 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), - - % Check that ALL tuples have both dept_id sources in provenance - [ - ?_assert(lists:all(fun(T) -> - Meta = maps:get(meta, T), - Prov = maps:get(provenance, Meta), - % Left side dept_id - maps:is_key(dept_id, Prov) andalso - % Right side dept_id (prefixed) - maps:is_key(right_dept_id, Prov) - end, Results)), - % Verify the sources are correct for ALL tuples - ?_assert(lists:all(fun(T) -> - Meta = maps:get(meta, T), - Prov = maps:get(provenance, Meta), - maps:get(dept_id, Prov) =:= {employees, dept_id} andalso - maps:get(right_dept_id, Prov) =:= {departments, dept_id} - end, Results)) - ]. - -%%% Lineage Tests - -test_base_lineage(DB) -> - % Query tuples (metadata always included) - Iter = operations:get_tuples_iterator(DB, employees, #{}), - Results = operations:collect_all(Iter), - - % All tuples should have lineage showing base relation - [ - ?_assertEqual(2, length(Results)), - ?_assert(lists:all(fun(T) -> - Meta = maps:get(meta, T), - Lineage = maps:get(lineage, Meta), - Lineage =:= {base, employees} - end, Results)) - ]. - -test_join_lineage(DB) -> - % Create iterators (metadata always included) - EmpIter = operations:get_tuples_iterator(DB, employees, #{}), - DeptIter = operations:get_tuples_iterator(DB, departments, #{}), - - % Join them - JoinIter = operations:equijoin_iterator(EmpIter, DeptIter, dept_id), - Results = operations:collect_all(JoinIter), - - % All results should have lineage showing join operation - [ - ?_assertEqual(2, length(Results)), - ?_assert(lists:all(fun(T) -> - Meta = maps:get(meta, T), - Lineage = maps:get(lineage, Meta), - % Should be a join of two base relations - case Lineage of - {join, dept_id, {base, employees}, {base, departments}} -> true; - _ -> false - end - end, Results)) - ]. - -%%% Comprehensive Lineage Tests - -test_select_lineage(DB) -> - % Test lineage through select (filter) operation - Iter = operations:get_tuples_iterator(DB, employees, #{}), - - Predicate = fun(T) -> maps:get(id, T, 0) =:= 1 end, - FilteredIter = operations:select_iterator(Iter, Predicate), - Results = operations:collect_all(FilteredIter), - - % Should have lineage: select wrapping base - [ - ?_assertEqual(1, length(Results)), - ?_assert(lists:all(fun(T) -> - Meta = maps:get(meta, T), - Lineage = maps:get(lineage, Meta), - case Lineage of - {select, Pred, {base, employees}} when is_function(Pred) -> true; - _ -> false - end - end, Results)) - ]. - -test_project_lineage(DB) -> - % Test lineage through project operation - Iter = operations:get_tuples_iterator(DB, employees, #{}), - ProjectedIter = operations:project_iterator(Iter, [name, dept_id]), - Results = operations:collect_all(ProjectedIter), - - % Should have lineage: project wrapping base - [ - ?_assertEqual(2, length(Results)), - ?_assert(lists:all(fun(T) -> - Meta = maps:get(meta, T), - Lineage = maps:get(lineage, Meta), - case Lineage of - {project, [name, dept_id], {base, employees}} -> true; - _ -> false - end - end, Results)) - ]. - -test_sort_lineage(DB) -> - % Test lineage through sort operation - Iter = operations:get_tuples_iterator(DB, employees, #{}), - - CompareFun = fun(A, B) -> maps:get(id, A, 0) =< maps:get(id, B, 0) end, - SortedIter = operations:sort_iterator(Iter, CompareFun), - Results = operations:collect_all(SortedIter), - - % Should have lineage: sort wrapping base - [ - ?_assertEqual(2, length(Results)), - ?_assert(lists:all(fun(T) -> - Meta = maps:get(meta, T), - Lineage = maps:get(lineage, Meta), - case Lineage of - {sort, Cmp, {base, employees}} when is_function(Cmp) -> true; - _ -> false - end - end, Results)) - ]. - -test_take_lineage(DB) -> - % Test lineage through take (limit) operation - Iter = operations:get_tuples_iterator(DB, employees, #{}), - TakeIter = operations:take_iterator(Iter, 1), - Results = operations:collect_all(TakeIter), - - % Should have lineage: take wrapping base - [ - ?_assertEqual(1, length(Results)), - ?_assert(lists:all(fun(T) -> - Meta = maps:get(meta, T), - Lineage = maps:get(lineage, Meta), - case Lineage of - {take, 1, {base, employees}} -> true; - _ -> false - end - end, Results)) - ]. - -test_multi_operation_pipeline_lineage(DB) -> - % Test complex pipeline: employees → select(id=1) → join(departments) → project([name, dept_name]) - - % 1. Start with employees base relation - EmpIter = operations:get_tuples_iterator(DB, employees, #{}), - - % 2. Filter to Alice (id=1) - Predicate = fun(T) -> maps:get(id, T, 0) =:= 1 end, - FilteredEmp = operations:select_iterator(EmpIter, Predicate), - - % 3. Join with departments - DeptIter = operations:get_tuples_iterator(DB, departments, #{}), - JoinedIter = operations:equijoin_iterator(FilteredEmp, DeptIter, dept_id), - - % 4. Project to [name, dept_name] - ProjectedIter = operations:project_iterator(JoinedIter, [name, dept_name]), - - Results = operations:collect_all(ProjectedIter), - - % Expected lineage: - % {project, [name, dept_name], - % {join, dept_id, - % {select, Pred, {base, employees}}, - % {base, departments}}} - - [ - ?_assertEqual(1, length(Results)), % Only Alice - ?_assert(lists:all(fun(T) -> - Meta = maps:get(meta, T), - Lineage = maps:get(lineage, Meta), - case Lineage of - {project, [name, dept_name], - {join, dept_id, - {select, Pred, {base, employees}}, - {base, departments}}} when is_function(Pred) -> true; - _ -> false - end - end, Results)) - ]. - -test_theta_join_lineage(DB) -> - % Test lineage for theta-join operation - EmpIter = operations:get_tuples_iterator(DB, employees, #{}), - DeptIter = operations:get_tuples_iterator(DB, departments, #{}), - - Predicate = fun(Emp, Dept) -> - maps:get(dept_id, Emp) =:= maps:get(dept_id, Dept) - end, - - JoinIter = operations:theta_join_iterator(EmpIter, DeptIter, Predicate), - Results = operations:collect_all(JoinIter), - - % Should have theta_join lineage - [ - ?_assertEqual(2, length(Results)), - ?_assert(lists:all(fun(T) -> - Meta = maps:get(meta, T), - Lineage = maps:get(lineage, Meta), - case Lineage of - {theta_join, Pred, {base, employees}, {base, departments}} - when is_function(Pred) -> true; - _ -> false - end - end, Results)) - ]. - -test_complex_nested_lineage(DB) -> - % Test deeply nested lineage: - % employees → select(id=1) → project([name, dept_id]) → join(departments → select(budget>50000)) → take(1) - - % Left side: employees → select → project - EmpIter = operations:get_tuples_iterator(DB, employees, #{}), - FilteredEmp = operations:select_iterator(EmpIter, fun(T) -> maps:get(id, T, 0) =:= 1 end), - ProjectedEmp = operations:project_iterator(FilteredEmp, [name, dept_id]), - - % Right side: departments → select - DeptIter = operations:get_tuples_iterator(DB, departments, #{}), - FilteredDept = operations:select_iterator(DeptIter, fun(D) -> maps:get(budget, D, 0) > 50000 end), - - % Join both sides - JoinedIter = operations:equijoin_iterator(ProjectedEmp, FilteredDept, dept_id), - - % Take first result - TakeIter = operations:take_iterator(JoinedIter, 1), - - Results = operations:collect_all(TakeIter), - - % Expected lineage should be deeply nested - [ - ?_assertEqual(1, length(Results)), - ?_assert(lists:all(fun(T) -> - Meta = maps:get(meta, T), - Lineage = maps:get(lineage, Meta), - case Lineage of - {take, 1, - {join, dept_id, - {project, [name, dept_id], - {select, _, {base, employees}}}, - {select, _, {base, departments}}}} -> true; - _ -> false - end - end, Results)) - ]. |
