diff options
| author | Marcos Magueta <maguetamarcos@gmail.com> | 2025-11-22 22:32:04 -0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-11-22 22:32:04 -0300 |
| commit | 09be36e5f5e2c5714b9542df8ccc363ab86ac6da (patch) | |
| tree | 06105bcbcc8cacc70e977e2370af270ae713226e /test/query_pipeline_test.erl | |
| parent | a56ea3089524b440b12b660d36c07f46b4fd9eb3 (diff) | |
| download | Sakura-09be36e5f5e2c5714b9542df8ccc363ab86ac6da.tar.gz Sakura-09be36e5f5e2c5714b9542df8ccc363ab86ac6da.zip | |
Infinite relations (#10)
Diffstat (limited to 'test/query_pipeline_test.erl')
| -rw-r--r-- | test/query_pipeline_test.erl | 190 |
1 files changed, 190 insertions, 0 deletions
diff --git a/test/query_pipeline_test.erl b/test/query_pipeline_test.erl new file mode 100644 index 0000000..389ff7c --- /dev/null +++ b/test/query_pipeline_test.erl @@ -0,0 +1,190 @@ +-module(query_pipeline_test). +-include_lib("eunit/include/eunit.hrl"). +-include("operations.hrl"). + +%%% Test Suite for Query Pipeline (Volcano Iterator Model) + +query_pipeline_test_() -> + {foreach, + fun setup/0, + fun cleanup/1, + [ + fun test_select_iterator/1, + fun test_project_iterator/1, + fun test_take_iterator/1, + fun test_sort_iterator/1, + fun test_composed_pipeline/1, + fun test_materialize_finite/1, + fun test_materialize_infinite/1, + fun test_complete_query_pipeline/1 + ]}. + +%%% Setup and Cleanup + +setup() -> + operations:setup(), + DB = operations:create_database(test_db), + + %% Create employees relation with test data + {DB1, _} = operations:create_relation(DB, employees, #{ + name => string, + age => integer, + department => string + }), + + {DB2, _} = operations:create_tuple(DB1, employees, #{name => "Alice", age => 35, department => "Engineering"}), + {DB3, _} = operations:create_tuple(DB2, employees, #{name => "Bob", age => 28, department => "Sales"}), + {DB4, _} = operations:create_tuple(DB3, employees, #{name => "Carol", age => 42, department => "Engineering"}), + {DB5, _} = operations:create_tuple(DB4, employees, #{name => "Dave", age => 31, department => "Marketing"}), + {DB6, _} = operations:create_tuple(DB5, employees, #{name => "Eve", age => 29, department => "Engineering"}), + + DB6. + +cleanup(_DB) -> + ok. + +%%% Tests + +test_select_iterator(DB) -> + %% Filter employees age > 30 + BaseIter = operations:get_tuples_iterator(DB, employees, #{}), + FilteredIter = operations:select_iterator(BaseIter, + fun(E) -> maps:get(age, E) > 30 end), + Results = operations:collect_all(FilteredIter), + + %% Should get 3 employees: Alice (35), Carol (42), Dave (31) + [ + ?_assertEqual(3, length(Results)), + ?_assert(lists:all(fun(E) -> maps:get(age, E) > 30 end, Results)) + ]. + +test_project_iterator(DB) -> + %% Project to [name, age] only + BaseIter = operations:get_tuples_iterator(DB, employees, #{}), + ProjectedIter = operations:project_iterator(BaseIter, [name, age]), + Results = operations:collect_all(ProjectedIter), + + %% Should have only name and age fields + [ + ?_assertEqual(5, length(Results)), + ?_assert(lists:all(fun(E) -> + maps:is_key(name, E) andalso + maps:is_key(age, E) andalso + not maps:is_key(department, E) + end, Results)) + ]. + +test_take_iterator(DB) -> + %% Take first 3 employees + BaseIter = operations:get_tuples_iterator(DB, employees, #{}), + LimitedIter = operations:take_iterator(BaseIter, 3), + Results = operations:collect_all(LimitedIter), + + [ + ?_assertEqual(3, length(Results)) + ]. + +test_sort_iterator(DB) -> + %% Sort by age ascending + BaseIter = operations:get_tuples_iterator(DB, employees, #{}), + SortedIter = operations:sort_iterator(BaseIter, + fun(A, B) -> maps:get(age, A) =< maps:get(age, B) end), + Results = operations:collect_all(SortedIter), + + Ages = [maps:get(age, E) || E <- Results], + + [ + ?_assertEqual(5, length(Results)), + ?_assertEqual([28, 29, 31, 35, 42], Ages) + ]. + +test_composed_pipeline(DB) -> + %% Complex pipeline: filter age > 30, sort by age, take 2, project [name] + Iter1 = operations:get_tuples_iterator(DB, employees, #{}), + Iter2 = operations:select_iterator(Iter1, fun(E) -> maps:get(age, E) > 30 end), + Iter3 = operations:sort_iterator(Iter2, fun(A, B) -> maps:get(age, A) =< maps:get(age, B) end), + Iter4 = operations:take_iterator(Iter3, 2), + Pipeline = operations:project_iterator(Iter4, [name]), + + Results = operations:collect_all(Pipeline), + + %% Should get 2 oldest employees over 30: Dave (31), Alice (35) + Names = [maps:get(name, R) || R <- Results], + + [ + ?_assertEqual(2, length(Results)), + ?_assertEqual(["Dave", "Alice"], Names), + ?_assert(lists:all(fun(R) -> not maps:is_key(age, R) end, Results)) + ]. + +test_materialize_finite(DB) -> + %% Build pipeline and materialize + Iter1 = operations:get_tuples_iterator(DB, employees, #{}), + Iter2 = operations:select_iterator(Iter1, fun(E) -> + maps:get(department, E) =:= "Engineering" + end), + Pipeline = operations:project_iterator(Iter2, [name, age]), + + {DB1, EngineersRel} = operations:materialize(DB, Pipeline, engineers), + + %% Verify new relation + [ + ?_assert(is_record(EngineersRel, relation)), + ?_assertEqual(engineers, EngineersRel#relation.name), + ?_assertEqual({finite, 3}, EngineersRel#relation.cardinality), + + %% Can query the materialized relation + ?_assertMatch([_,_,_], + operations:collect_all(operations:get_tuples_iterator(DB1, engineers, #{}))) + ]. + +test_materialize_infinite(DB) -> + %% Create infinite naturals + {DB1, _} = operations:create_infinite_relation(DB, #{ + name => naturals, + schema => #{}, + cardinality => aleph_zero, + generator => {primitive, naturals}, + constraints => #{value => {gte, 0}} + }), + + %% Take 100 and materialize + Pipeline = operations:get_tuples_iterator(DB1, naturals, #{value => {range, 0, 99}}), + {_DB2, Naturals100} = operations:materialize(DB1, Pipeline, naturals_100), + + [ + ?_assertEqual({finite, 100}, Naturals100#relation.cardinality), + ?_assertEqual(naturals_100, Naturals100#relation.name) + ]. + +test_complete_query_pipeline(DB) -> + %% Realistic query: + %% SELECT name, age FROM employees + %% WHERE age > 28 + %% ORDER BY age DESC + %% LIMIT 3 + + Iter1 = operations:get_tuples_iterator(DB, employees, #{}), + Iter2 = operations:select_iterator(Iter1, fun(E) -> maps:get(age, E) > 28 end), + Iter3 = operations:sort_iterator(Iter2, fun(A, B) -> + maps:get(age, A) >= maps:get(age, B) % DESC + end), + Iter4 = operations:take_iterator(Iter3, 3), + Pipeline = operations:project_iterator(Iter4, [name, age]), + + {DB1, TopEmployees} = operations:materialize(DB, Pipeline, top_3_employees), + + %% Verify result - NOTE: Relations are unordered sets! + %% After materialization, tuples come back in hash order, not sort order + Results = operations:collect_all(operations:get_tuples_iterator(DB1, top_3_employees, #{})), + Ages = lists:sort(fun(A, B) -> A >= B end, [maps:get(age, R) || R <- Results]), + + [ + ?_assertEqual(3, length(Results)), + ?_assertEqual([42, 35, 31], Ages), % Verify we have the right ages (sorted DESC for check) + ?_assert(lists:all(fun(R) -> + Age = maps:get(age, R), + lists:member(Age, [42, 35, 31]) + end, Results)), + ?_assertEqual({finite, 3}, TopEmployees#relation.cardinality) + ]. |
