%%% @doc Interactive REPL Helper for Domino Query Planner %%% %%% Provides convenience functions for working with the query planner %%% in the Erlang shell (REPL). Includes utilities for pretty-printing %%% results and working with prepared statements. %%% %%% == Quick Start == %%% %%% ``` %%% % Start the system %%% repl:start(). %%% %%% % Create a database with sample data %%% DB = repl:example_db(). %%% %%% % Execute a query %%% repl:q(DB, {select, {relation, employees}, fun(T) -> maps:get(age, T) > 30 end}). %%% %%% % Explain a query plan %%% repl:explain({project, {relation, employees}, [name, age]}). %%% ``` %%% %%% == Query Syntax Examples == %%% %%% ``` %%% % Scan relation %%% {scan, employees} %%% %%% % Filter %%% {select, {scan, employees}, fun(T) -> maps:get(age, T) > 30 end} %%% %%% % Project %%% {project, {scan, employees}, [name, age]} %%% %%% % Join %%% {join, {scan, employees}, {scan, departments}, dept_id} %%% %%% % Rename %%% {rename, age, years, {scan, employees}} %%% %%% % Complex pipeline %%% {take, %%% {sort, %%% {project, %%% {select, %%% {join, %%% {scan, employees}, %%% {scan, departments}, %%% dept_id}, %%% fun(T) -> maps:get(age, T) > 30 end}, %%% [name, dept_name, age]}, %%% fun(A, B) -> maps:get(age, A) =< maps:get(age, B) end}, %%% 5} %%% ``` %%% %%% @author Nekoma Team %%% @copyright 2025 -module(repl). -export([start/0, example_db/0, q/2, q/3, query/2, show/1, show/2, explain/1, pretty/1, modes/0, help/0]). -include("operations.hrl"). %%% Public API %% @doc Start the Domino system and initialize Mnesia. %% %% Call this first when starting a new REPL session. %% %% @returns ok -spec start() -> ok. start() -> operations:setup(), io:format("~n~s~n", [banner()]), io:format("Domino REPL started. Type repl:help() for usage.~n~n"), ok. %% @doc Create an example database with sample data. %% %% Creates a database with employees and departments relations %% for testing and experimentation. %% %% @returns Database state -spec example_db() -> term(). example_db() -> DB = operations:create_database(example_db), % Create employees relation {DB1, _} = operations:create_relation(DB, employees, #{ id => integer, name => string, dept_id => integer, age => integer, salary => integer }), {DB2, _} = operations:create_tuple(DB1, employees, #{id => 1, name => "Alice", dept_id => 10, age => 30, salary => 80000}), {DB3, _} = operations:create_tuple(DB2, employees, #{id => 2, name => "Bob", dept_id => 20, age => 35, salary => 90000}), {DB4, _} = operations:create_tuple(DB3, employees, #{id => 3, name => "Carol", dept_id => 10, age => 28, salary => 75000}), {DB5, _} = operations:create_tuple(DB4, employees, #{id => 4, name => "Dave", dept_id => 30, age => 42, salary => 95000}), {DB6, _} = operations:create_tuple(DB5, employees, #{id => 5, name => "Eve", dept_id => 20, age => 25, salary => 70000}), % Create departments relation {DB7, _} = operations:create_relation(DB6, departments, #{ dept_id => integer, dept_name => string, budget => integer }), {DB8, _} = operations:create_tuple(DB7, departments, #{dept_id => 10, dept_name => "Engineering", budget => 100000}), {DB9, _} = operations:create_tuple(DB8, departments, #{dept_id => 20, dept_name => "Sales", budget => 80000}), {DB10, _} = operations:create_tuple(DB9, departments, #{dept_id => 30, dept_name => "Marketing", budget => 90000}), {DB11, _} = operations:create_infinite_relation(DB10, #{ name => naturals, schema => #{}, cardinality => aleph_zero, generator => {primitive, naturals}, constraints => #{value => {gte, 0}} }), io:format("Example database created with:~n"), io:format(" - employees (5 tuples)~n"), io:format(" - departments (3 tuples)~n~n"), DB11. %% @doc Execute a query and pretty-print results (default table mode). %% %% Convenience function that prepares, executes, and displays %% query results in a readable format. Returns `ok' to avoid %% duplicate output in the Erlang shell. %% %% If you need the results for further processing, use `query/2'. %% %% @param DB Database state %% @param Plan Query plan tuple %% @returns ok -spec q(term(), query_planner:query_plan()) -> ok. q(DB, Plan) -> q(DB, Plan, table). %% @doc Execute a query with specified visualization mode. %% %% Displays query results using the specified visualization mode. %% Returns `ok' to avoid duplicate output in the Erlang shell. %% %% If you need the results for further processing, use `query/2'. %% %% @param DB Database state %% @param Plan Query plan tuple %% @param Mode Visualization mode (table, tree, nested_table, etc.) %% @returns ok -spec q(term(), query_planner:query_plan(), visualizer:render_mode()) -> ok. q(DB, Plan, Mode) -> PreparedPlan = query_planner:prepare(Plan), Results = query_planner:collect(DB, PreparedPlan), visualizer:render(Results, Mode). %% @doc Execute a query without printing results. %% %% This function executes a query and returns the raw results without %% any visualization. Use this when you want to work with the results %% programmatically or decide later how to display them. %% %% @param DB Database state %% @param Plan Query plan tuple %% @returns List of result tuples %% %% == Example == %% ``` %% Results = repl:query(DB, {scan, employees}). %% FilteredResults = lists:filter(fun(T) -> maps:get(age, T) > 30 end, Results). %% repl:show(FilteredResults, tree). %% ``` -spec query(term(), query_planner:query_plan()) -> [map()]. query(DB, Plan) -> PreparedPlan = query_planner:prepare(Plan), query_planner:collect(DB, PreparedPlan). %% @doc Display results using default visualization mode (table). %% %% Takes a list of tuples (e.g., from `query/2`) and displays them %% using the table visualization mode. %% %% @param Results List of result tuples %% @returns ok -spec show([map()]) -> ok. show(Results) -> show(Results, table). %% @doc Display results using specified visualization mode. %% %% Takes a list of tuples (e.g., from `query/2`) and displays them %% using the specified visualization mode. %% %% @param Results List of result tuples %% @param Mode Visualization mode (table, tree, nested_table, etc.) %% @returns ok %% %% == Example == %% ``` %% Results = repl:query(DB, {scan, employees}), %% repl:show(Results, tree). %% ``` -spec show([map()], visualizer:render_mode()) -> ok. show(Results, Mode) -> visualizer:render(Results, Mode). %% @doc Explain a query plan. %% %% Displays the query execution plan in a readable tree format. %% %% @param Plan Query plan tuple %% @returns ok -spec explain(query_planner:query_plan()) -> ok. explain(Plan) -> Explanation = query_planner:explain(Plan), io:format("~nQuery Plan:~n~s~n", [Explanation]), ok. %% @doc List available visualization modes. %% %% @returns List of available render modes -spec modes() -> [visualizer:render_mode()]. modes() -> visualizer:modes(). %% @doc Pretty-print a list of tuples. %% %% Displays tuples using the visualizer module (default table mode). %% %% @param Results List of result tuples %% @returns ok -spec pretty([map()]) -> ok. pretty(Results) -> visualizer:render(Results, table). %% @doc Display help message with query examples. %% %% @returns ok -spec help() -> ok. help() -> io:format("~n~s~n~n", [banner()]), io:format("Domino Query REPL - Lisp-inspired tuple-based query language~n~n"), io:format("Quick Start:~n"), io:format(" repl:start(). %% Initialize system~n"), io:format(" DB = repl:example_db(). %% Create example database~n"), io:format(" repl:q(DB, {scan, employees}). %% Run a query~n~n"), io:format("Query Operators (all lazy by default):~n"), io:format(" {scan, Name} - Scan relation~n"), io:format(" {select, Plan, Predicate} - Filter tuples~n"), io:format(" {project, Plan, [Attrs]} - Project attributes~n"), io:format(" {join, Left, Right, Attr} - Equijoin~n"), io:format(" {theta_join, Left, Right, Predicate} - Theta join~n"), io:format(" {sort, Plan, Comparator} - Sort tuples~n"), io:format(" {take, Plan, N} - Limit results~n"), io:format(" {rename, OldAttr, NewAttr, Plan} - Rename attribute~n"), io:format(" {materialize, Plan} - Force evaluation (all)~n"), io:format(" {materialize, Plan, N} - Force evaluation (N tuples)~n~n"), io:format("Lazy vs Eager:~n"), io:format(" All operators return lazy iterators (relations)~n"), io:format(" Use 'materialize' to force eager evaluation~n"), io:format(" Materialize can be nested anywhere in the query~n~n"), io:format("Query Execution:~n"), io:format(" repl:q(DB, Plan) - Execute and display (returns ok)~n"), io:format(" repl:q(DB, Plan, Mode) - Execute and display with mode (returns ok)~n"), io:format(" repl:query(DB, Plan) - Execute without display (returns results)~n"), io:format(" repl:show(Results) - Display results (returns ok)~n"), io:format(" repl:show(Results, Mode) - Display results with mode (returns ok)~n~n"), io:format("Visualization Modes:~n"), io:format(" Modes: table, tree, nested_table, linked_tables, outline~n"), io:format(" repl:modes() - List all available modes~n~n"), io:format("Examples:~n"), io:format(" %% Filter employees over 30~n"), io:format(" repl:q(DB, {select, {scan, employees}, ~n"), io:format(" fun(T) -> maps:get(age, T) > 30 end}).~n~n"), io:format(" %% Project name and age~n"), io:format(" repl:q(DB, {project, {scan, employees}, [name, age]}).~n~n"), io:format(" %% Join employees with departments~n"), io:format(" repl:q(DB, {join, {scan, employees}, ~n"), io:format(" {scan, departments}, dept_id}).~n~n"), io:format(" %% Rename age to years~n"), io:format(" repl:q(DB, {rename, age, years, {scan, employees}}).~n~n"), io:format(" %% Materialize first 10 tuples (eager evaluation)~n"), io:format(" repl:q(DB, {materialize, {scan, employees}, 10}).~n~n"), io:format(" %% Nested materialize - select from materialized data~n"), io:format(" repl:q(DB, {select, {materialize, {scan, employees}},~n"), io:format(" fun(T) -> maps:get(age, T) > 30 end}).~n~n"), io:format(" %% Use different visualization modes~n"), io:format(" repl:q(DB, {scan, employees}, tree).~n"), io:format(" repl:q(DB, {scan, employees}, nested_table).~n"), io:format(" repl:q(DB, {scan, employees}, outline).~n~n"), io:format(" %% Query without printing, then show results~n"), io:format(" Results = repl:query(DB, {scan, employees}).~n"), io:format(" Filtered = lists:filter(fun(T) -> maps:get(age, T) > 30 end, Results).~n"), io:format(" repl:show(Filtered, tree).~n~n"), io:format(" %% Explain a query plan~n"), io:format(" repl:explain({select, {scan, employees}, ~n"), io:format(" fun(T) -> maps:get(age, T) > 30 end}).~n~n"), ok. %%% Internal Functions banner() -> "========================================\n" " Domino - Extended Relational DB\n" " Tasmania / RM/T Implementation\n" "========================================".