diff options
| author | Marcos Magueta <maguetamarcos@gmail.com> | 2025-11-25 15:59:08 -0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-11-25 15:59:08 -0300 |
| commit | 3f7cb91ef7ae44d3f641f2e77826965fcd0afeed (patch) | |
| tree | adb493947fe7c3ee511346c984f8046b4c48856e /src/repl.erl | |
| parent | 62a017ce95ad2f6d9445b5b96f7aeba3f7c0d42f (diff) | |
| download | Sakura-3f7cb91ef7ae44d3f641f2e77826965fcd0afeed.tar.gz Sakura-3f7cb91ef7ae44d3f641f2e77826965fcd0afeed.zip | |
Pseudo-REPL and sketch planner (#12)
Diffstat (limited to 'src/repl.erl')
| -rw-r--r-- | src/repl.erl | 333 |
1 files changed, 333 insertions, 0 deletions
diff --git a/src/repl.erl b/src/repl.erl new file mode 100644 index 0000000..572d17c --- /dev/null +++ b/src/repl.erl @@ -0,0 +1,333 @@ +%%% @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" + "========================================". |
