aboutsummaryrefslogtreecommitdiffstats
path: root/test/function_relations_test.erl
diff options
context:
space:
mode:
Diffstat (limited to 'test/function_relations_test.erl')
-rw-r--r--test/function_relations_test.erl347
1 files changed, 0 insertions, 347 deletions
diff --git a/test/function_relations_test.erl b/test/function_relations_test.erl
deleted file mode 100644
index a6ab2d2..0000000
--- a/test/function_relations_test.erl
+++ /dev/null
@@ -1,347 +0,0 @@
--module(function_relations_test).
--include_lib("eunit/include/eunit.hrl").
--include("operations.hrl").
-
-%%% Function Relations Test Suite
-%%%
-%%% Testing infinite relations that represent arithmetic operations.
-%%% These work like functions but backwards too (Prolog-style)
-%%%
-%%% - Plus: {(a, b, sum) | sum = a + b }
-%%% - Times: {(a, b, product) | product = a * b}
-%%% - Minus: {(a, b, difference) | difference = a - b}
-%%% - Divide: {(a, b, quotient, remainder) | a = b*quotient + remainder}
-
-function_relations_test_() ->
- {foreach,
- fun setup/0,
- fun cleanup/1,
- [
- fun test_plus_both_operands_constrained/1,
- fun test_plus_a_and_sum_constrained/1,
- fun test_plus_b_and_sum_constrained/1,
- fun test_plus_only_sum_constrained/1,
- fun test_plus_sum_range_constrained/1,
- fun test_plus_insufficient_constraints/1,
- fun test_times_both_operands_constrained/1,
- fun test_times_insufficient_constraints/1,
- fun test_minus_both_operands_constrained/1,
- fun test_divide_both_operands_constrained/1,
- fun test_divide_avoids_zero/1,
- fun test_plus_join_with_finite/1,
- fun test_materialized_plus_relation/1
- ]}.
-
-%%% Setup and Cleanup
-
-setup() ->
- operations:setup(),
- DB = operations:create_database(test_db),
-
- % Integers and naturals are now built-in to every database
- % Just create the function relations using the new format
-
- % Create function relations - note how their schemas reference the domain relations
- {DB1, _Plus} = operations:create_immutable_relation(DB, #immutable_relation_spec{
- name = plus,
- schema = #{a => integer, b => integer, sum => integer},
- generator = {generators, plus},
- membership_criteria = #{},
- cardinality = aleph_zero
- }),
-
- {DB2, _Times} = operations:create_immutable_relation(DB1, #immutable_relation_spec{
- name = times,
- schema = #{a => integer, b => integer, product => integer},
- generator = {generators, times},
- membership_criteria = #{},
- cardinality = aleph_zero
- }),
-
- {DB3, _Minus} = operations:create_immutable_relation(DB2, #immutable_relation_spec{
- name = minus,
- schema = #{a => integer, b => integer, difference => integer},
- generator = {generators, minus},
- membership_criteria = #{},
- cardinality = aleph_zero
- }),
-
- {DB4, _Divide} = operations:create_immutable_relation(DB3, #immutable_relation_spec{
- name = divide,
- schema = #{a => integer, b => integer, quotient => integer, remainder => natural},
- generator = {generators, divide},
- membership_criteria = #{},
- cardinality = aleph_zero
- }),
-
- DB4.
-
-cleanup(_DB) ->
- ok.
-
-%%% Helper Functions
-
-%% Strip metadata from tuples for comparison
-strip_meta(Tuple) when is_map(Tuple) ->
- maps:remove(meta, Tuple);
-strip_meta(Tuples) when is_list(Tuples) ->
- [strip_meta(T) || T <- Tuples].
-
-%%% Plus Relation Tests
-
-test_plus_both_operands_constrained(DB) ->
- % Both operands bounded: a ∈ [1,3], b ∈ [2,4]
- % TODO: eventually this should be a proper join like Integer[A], Integer[B], Plus[A,B,Sum]
- % but for now just pass constraints directly
- Iterator = operations:get_tuples_iterator(DB, plus, #{
- a => {range, 1, 3},
- b => {range, 2, 4}
- }),
- Results = operations:collect_all(Iterator),
- DataOnly = strip_meta(Results),
-
- % should get 9 tuples total (3 × 3 = 9)
- % like (1,2,3), (1,3,4), (1,4,5), (2,2,4), ..., (3,4,7)
- [
- ?_assertEqual(9, length(Results)),
- ?_assert(lists:member(#{a => 1, b => 2, sum => 3}, DataOnly)),
- ?_assert(lists:member(#{a => 2, b => 3, sum => 5}, DataOnly)),
- ?_assert(lists:member(#{a => 3, b => 4, sum => 7}, DataOnly)),
- ?_assert(lists:all(fun(#{a := A, b := B, sum := S}) ->
- S =:= A + B
- end, DataOnly))
- ].
-
-test_plus_a_and_sum_constrained(DB) ->
- % Test reversibility - given A and Sum, compute B
- % a ∈ [1,3], sum ∈ [5,7], so b = sum - a
- % This is like asking: Plus[A, ?, Sum]
- Iterator = operations:get_tuples_iterator(DB, plus, #{
- a => {range, 1, 3},
- sum => {range, 5, 7}
- }),
- Results = operations:collect_all(Iterator),
- DataOnly = strip_meta(Results),
-
- % Expected tuples: (1,4,5), (1,5,6), (1,6,7), (2,3,5), (2,4,6), (2,5,7), (3,2,5), (3,3,6), (3,4,7)
- % (filtered to only keep where b >= 0)
- [
- ?_assert(length(Results) > 0),
- ?_assert(lists:all(fun(#{a := A, b := B, sum := S}) ->
- S =:= A + B andalso B >= 0
- end, DataOnly)),
- ?_assert(lists:member(#{a => 1, b => 4, sum => 5}, DataOnly)),
- ?_assert(lists:member(#{a => 3, b => 4, sum => 7}, DataOnly))
- ].
-
-test_plus_b_and_sum_constrained(DB) ->
- % Another reversibility test - now given B and Sum, find A
- % b ∈ [2,4], sum ∈ [5,6] → a = sum - b
- % Like: Plus[?, B, Sum]
- Iterator = operations:get_tuples_iterator(DB, plus, #{
- b => {range, 2, 4},
- sum => {range, 5, 6}
- }),
- Results = operations:collect_all(Iterator),
- DataOnly = strip_meta(Results),
-
- [
- ?_assert(length(Results) > 0),
- ?_assert(lists:all(fun(#{a := A, b := B, sum := S}) ->
- S =:= A + B andalso A >= 0
- end, DataOnly)),
- ?_assert(lists:member(#{a => 3, b => 2, sum => 5}, DataOnly)),
- ?_assert(lists:member(#{a => 2, b => 4, sum => 6}, DataOnly))
- ].
-
-test_plus_only_sum_constrained(DB) ->
- % Only constrain the sum - should give us all ways to add up to 5
- % Plus[?, ?, 5] generates: (0,5), (1,4), (2,3), (3,2), (4,1), (5,0)
- Iterator = operations:get_tuples_iterator(DB, plus, #{
- sum => {eq, 5}
- }),
- Results = operations:collect_all(Iterator),
- DataOnly = strip_meta(Results),
-
- [
- ?_assertEqual(6, length(Results)),
- ?_assert(lists:member(#{a => 0, b => 5, sum => 5}, DataOnly)),
- ?_assert(lists:member(#{a => 2, b => 3, sum => 5}, DataOnly)),
- ?_assert(lists:member(#{a => 5, b => 0, sum => 5}, DataOnly)),
- ?_assert(lists:all(fun(#{a := A, b := B, sum := S}) ->
- S =:= A + B andalso S =:= 5
- end, DataOnly))
- ].
-
-test_plus_sum_range_constrained(DB) ->
- % sum in a range [2,3] instead of single value
- Iterator = operations:get_tuples_iterator(DB, plus, #{
- sum => {range, 2, 3}
- }),
- Results = operations:collect_all(Iterator),
- DataOnly = strip_meta(Results),
-
- % For sum=2: (0,2), (1,1), (2,0) → 3 pairs
- % For sum=3: (0,3), (1,2), (2,1), (3,0) → 4 pairs
- % Total = 7
- [
- ?_assertEqual(7, length(Results)),
- ?_assert(lists:all(fun(#{sum := S}) -> S >= 2 andalso S =< 3 end, DataOnly)),
- ?_assert(lists:member(#{a => 0, b => 2, sum => 2}, DataOnly)),
- ?_assert(lists:member(#{a => 3, b => 0, sum => 3}, DataOnly))
- ].
-
-test_plus_insufficient_constraints(DB) ->
- % Try querying with zero constraints - should fail since we'd get infinite results
- Iterator = operations:get_tuples_iterator(DB, plus, #{}),
- Result = operations:next_tuple(Iterator),
-
- [
- ?_assertMatch({error, {unbounded_plus, _}}, Result)
- ].
-
-%%% Times (multiplication) Tests
-
-test_times_both_operands_constrained(DB) ->
- % Basic multiplication with both inputs bounded
- Iterator = operations:get_tuples_iterator(DB, times, #{
- a => {range, 2, 4},
- b => {range, 3, 5}
- }),
- Results = operations:collect_all(Iterator),
- DataOnly = strip_meta(Results),
-
- % 3×3 = 9 tuples
- [
- ?_assertEqual(9, length(Results)),
- ?_assert(lists:member(#{a => 2, b => 3, product => 6}, DataOnly)),
- ?_assert(lists:member(#{a => 3, b => 4, product => 12}, DataOnly)),
- ?_assert(lists:member(#{a => 4, b => 5, product => 20}, DataOnly)),
- ?_assert(lists:all(fun(#{a := A, b := B, product := P}) ->
- P =:= A * B
- end, DataOnly))
- ].
-
-test_times_insufficient_constraints(DB) ->
- % Times doesn't support full reversibility yet (would need factorization)
- % So constraining just one operand should error
- Iterator = operations:get_tuples_iterator(DB, times, #{a => {range, 1, 5}}),
- Result = operations:next_tuple(Iterator),
-
- [
- ?_assertMatch({error, {unbounded_times, _}}, Result)
- ].
-
-%%% Minus (subtraction) Tests
-
-test_minus_both_operands_constrained(DB) ->
- % subtraction with both operands bounded
- Iterator = operations:get_tuples_iterator(DB, minus, #{
- a => {range, 5, 7},
- b => {range, 2, 3}
- }),
- Results = operations:collect_all(Iterator),
- DataOnly = strip_meta(Results),
-
- % 3 × 2 = 6 tuples
- [
- ?_assertEqual(6, length(Results)),
- ?_assert(lists:member(#{a => 5, b => 2, difference => 3}, DataOnly)),
- ?_assert(lists:member(#{a => 7, b => 3, difference => 4}, DataOnly)),
- ?_assert(lists:all(fun(#{a := A, b := B, difference := D}) ->
- D =:= A - B
- end, DataOnly))
- ].
-
-%%% Divide (integer division) Tests
-
-test_divide_both_operands_constrained(DB) ->
- % Integer division with quotient and remainder
- Iterator = operations:get_tuples_iterator(DB, divide, #{
- a => {range, 10, 12},
- b => {range, 3, 4}
- }),
- Results = operations:collect_all(Iterator),
- DataOnly = strip_meta(Results),
-
- % 3 × 2 = 6 tuples
- [
- ?_assertEqual(6, length(Results)),
- ?_assert(lists:member(#{a => 10, b => 3, quotient => 3, remainder => 1}, DataOnly)),
- ?_assert(lists:member(#{a => 12, b => 4, quotient => 3, remainder => 0}, DataOnly)),
- ?_assert(lists:all(fun(#{a := A, b := B, quotient := Q, remainder := R}) ->
- A =:= B * Q + R andalso R >= 0 andalso R < abs(B)
- end, DataOnly))
- ].
-
-test_divide_avoids_zero(DB) ->
- % Make sure we don't try to divide by zero
- % even if the range includes 0
- Iterator = operations:get_tuples_iterator(DB, divide, #{
- a => {range, 5, 7},
- b => {range, 0, 2}
- }),
- Results = operations:collect_all(Iterator),
-
- % 3 × 2 = 6 (skips b=0, only uses b=1,2)
- [
- ?_assertEqual(6, length(Results)),
- ?_assert(lists:all(fun(#{b := B}) -> B =/= 0 end, Results))
- ].
-
-%%% Integration Tests
-
-test_plus_join_with_finite(DB) ->
- % Simulate joining an infinite relation with a finite one
- {DB1, _Employees} = operations:create_relation(DB, employees, #{
- name => string,
- age => integer
- }),
-
- {DB2, _} = operations:create_tuple(DB1, employees, #{name => "Alice", age => 30}),
- {DB3, _} = operations:create_tuple(DB2, employees, #{name => "Bob", age => 35}),
-
- EmployeeIter = operations:get_tuples_iterator(DB3, employees, #{}),
- Employees = operations:collect_all(EmployeeIter),
-
- % For each employee's age, find all the ways to add up to it
- % (simulating: Employee JOIN Plus ON Plus.sum = Employee.age)
- Results = lists:flatmap(fun(#{age := Age}) ->
- PlusIter = operations:get_tuples_iterator(DB3, plus, #{
- a => {range, 0, 15},
- b => {range, 0, 15},
- sum => {eq, Age}
- }),
- Tuples = operations:collect_all(PlusIter),
- [#{age => Age, plus_tuple => T} || T <- Tuples]
- end, Employees),
-
- % verify we got some results
- [
- ?_assert(length(Results) > 0),
- ?_assert(lists:any(fun(#{age := Age, plus_tuple := #{sum := S}}) ->
- Age =:= S
- end, Results))
- ].
-
-test_materialized_plus_relation(DB) ->
- % Take an infinite relation, constrain it, and materialize into a finite one
- PlusIter = operations:get_tuples_iterator(DB, plus, #{
- a => {range, 0, 2},
- b => {range, 0, 2}
- }),
-
- % Turn it into an actual stored relation
- {DB1, SmallPlus} = operations:materialize(DB, PlusIter, small_plus),
-
- % check that it worked
- ResultIter = operations:get_tuples_iterator(DB1, small_plus, #{}),
- Results = operations:collect_all(ResultIter),
-
- [
- ?_assertEqual({finite, 9}, SmallPlus#relation.cardinality),
- ?_assertEqual(9, length(Results)),
- ?_assert(lists:all(fun(#{a := A, b := B, sum := S}) ->
- S =:= A + B andalso A >= 0 andalso A =< 2 andalso B >= 0 andalso B =< 2
- end, Results))
- ].