aboutsummaryrefslogtreecommitdiffstats
path: root/test/query_pipeline_test.erl
diff options
context:
space:
mode:
Diffstat (limited to 'test/query_pipeline_test.erl')
-rw-r--r--test/query_pipeline_test.erl190
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)
+ ].