aboutsummaryrefslogtreecommitdiffstats
path: root/src/repl.erl
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/repl.erl333
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"
+ "========================================".