Lotus (Lotus v0.9.2)
View SourceLotus is a lightweight Elixir library for saving and executing read-only SQL queries.
This module provides the main public API, orchestrating between:
- Storage: Query persistence and management
- Runner: SQL execution with safety checks
- Migrations: Database schema management
Configuration
Add to your config:
config :lotus,
repo: MyApp.Repo,
primary_key_type: :id, # or :binary_id
foreign_key_type: :id # or :binary_id
Usage
# Create and save a query with variables
{:ok, query} = Lotus.create_query(%{
name: "Active Users",
statement: "SELECT * FROM users WHERE active = {{is_active}}",
variables: [
%{name: "is_active", type: :text, label: "Is Active", default: "true"}
],
search_path: "reporting, public"
})
# Execute a saved query
{:ok, results} = Lotus.run_query(query)
# Execute SQL directly (read-only)
{:ok, results} = Lotus.run_sql("SELECT * FROM products WHERE price > $1", [100])
Summary
Functions
Checks if a query can be run with the provided variables.
Creates a new saved query.
Returns all configured data repositories.
Returns the default data repository as a {name, module} tuple.
Deletes a saved query.
Gets a data repository by name.
Gets a single query by ID. Returns nil if not found.
Gets a single query by ID. Raises if not found.
Gets the schema for a specific table.
Gets statistics for a specific table.
Lists the names of all configured data repositories.
Lists all saved queries.
Lists all relations (tables with schema information) in a data repository.
Lists all schemas in the given repository.
Lists all tables in a data repository.
Returns the configured Ecto repository where Lotus stores query definitions.
Run a saved query (by struct or id).
Run ad-hoc SQL (bypassing storage), still read-only and sandboxed.
Returns whether unique query names are enforced.
Updates an existing query.
Returns the current version of Lotus.
Types
@type cache_opt() :: :bypass | :refresh | {:ttl_ms, non_neg_integer()} | {:profile, atom()} | {:tags, [binary()]}
@type opts() :: [ timeout: non_neg_integer(), statement_timeout_ms: non_neg_integer(), read_only: boolean(), search_path: binary() | nil, repo: binary() | nil, vars: map(), cache: [cache_opt()] | :bypass | :refresh | nil, window: window_opts() ]
@type window_count_mode() :: :none | :exact
@type window_opts() :: [ limit: pos_integer(), offset: non_neg_integer(), count: window_count_mode() ]
Functions
@spec can_run?(Lotus.Storage.Query.t(), opts()) :: boolean()
Checks if a query can be run with the provided variables.
Returns true if all required variables have values (either from defaults or supplied vars), false otherwise.
Examples
# Query with all required variables having defaults
Lotus.can_run?(query)
# => true
# Query missing required variables
Lotus.can_run?(query)
# => false
# Query with runtime variable overrides
Lotus.can_run?(query, vars: %{"user_id" => 123})
# => true (if user_id was the missing variable)
Creates a new saved query.
Returns all configured data repositories.
Returns the default data repository as a {name, module} tuple.
- If there's only one data repo configured, returns it
- If multiple repos are configured and default_repo is set, returns that repo
- If multiple repos are configured without default_repo, raises an error
- If no data repos are configured, raises an error
Deletes a saved query.
Gets a data repository by name.
Raises if the repository is not configured.
Gets a single query by ID. Returns nil if not found.
Gets a single query by ID. Raises if not found.
Gets the schema for a specific table.
Examples
{:ok, schema} = Lotus.get_table_schema("primary", "users")
{:ok, schema} = Lotus.get_table_schema("postgres", "customers", schema: "reporting")
{:ok, schema} = Lotus.get_table_schema(MyApp.DataRepo, "products", search_path: "analytics, public")
Gets statistics for a specific table.
Examples
{:ok, stats} = Lotus.get_table_stats("primary", "users")
{:ok, stats} = Lotus.get_table_stats("postgres", "customers", schema: "reporting")
# Returns %{row_count: 1234}
Lists the names of all configured data repositories.
Useful for building UI dropdowns.
Lists all saved queries.
Lists all relations (tables with schema information) in a data repository.
Examples
{:ok, relations} = Lotus.list_relations("postgres", search_path: "reporting, public")
# Returns [{"reporting", "customers"}, {"public", "users"}, ...]
Lists all schemas in the given repository.
Returns a list of schema names. For databases without schemas (like SQLite), returns an empty list.
Examples
{:ok, schemas} = Lotus.list_schemas("postgres")
# Returns ["public", "reporting", ...]
{:ok, schemas} = Lotus.list_schemas("sqlite")
# Returns []
Lists all tables in a data repository.
For databases with schemas (PostgreSQL), returns {schema, table} tuples. For databases without schemas (SQLite), returns just table names as strings.
Examples
{:ok, tables} = Lotus.list_tables("postgres")
# Returns [{"public", "users"}, {"public", "posts"}, ...]
{:ok, tables} = Lotus.list_tables("postgres", search_path: "reporting, public")
# Returns [{"reporting", "customers"}, {"public", "users"}, ...]
{:ok, tables} = Lotus.list_tables("sqlite")
# Returns ["products", "orders", "order_items"]
Returns the configured Ecto repository where Lotus stores query definitions.
@spec run_query(Lotus.Storage.Query.t() | term(), opts()) :: {:ok, Lotus.Result.t()} | {:error, term()}
Run a saved query (by struct or id).
Variables in the query statement (using {{variable_name}}
syntax) are
substituted with values from the query's default variables and any runtime
overrides provided via the vars
option.
Variable Resolution
Variables are resolved in this order:
- Runtime values from
vars
option (highest priority) - Default values from the query's variable definitions
- If neither exists, raises an error for missing required variable
Examples
# Run query with default variable values
Lotus.run_query(query)
# Override variables at runtime
Lotus.run_query(query, vars: %{"min_age" => 25, "status" => "active"})
# Run with timeout and repo options
Lotus.run_query(query, timeout: 10_000, repo: MyApp.DataRepo)
# Run by query ID
Lotus.run_query(query_id, vars: %{"user_id" => 123})
Variable Types
Variables are automatically cast based on their type definition:
:text
- Used as-is (strings):number
- Cast from string to integer:date
- Cast from ISO8601 string to Date struct
Windowed pagination
Pass window: [limit: pos_integer, offset: non_neg_integer, count: :none | :exact]
to
return only a page of rows from the original query. When count: :exact
, Lotus will
also compute SELECT COUNT(*) FROM (original_sql)
and include meta.total_count
in
the result. The num_rows
field always reflects the number of rows in the returned page.
@spec run_sql(binary(), [any()], read_only: boolean(), statement_timeout_ms: non_neg_integer(), timeout: non_neg_integer(), search_path: binary() | nil, repo: atom() | binary(), window: window_opts() ) :: {:ok, Lotus.Result.t()} | {:error, term()}
Run ad-hoc SQL (bypassing storage), still read-only and sandboxed.
Examples
# Run against default configured repo
{:ok, result} = Lotus.run_sql("SELECT * FROM users")
# Run against specific repo
{:ok, result} = Lotus.run_sql("SELECT * FROM products", [], repo: MyApp.DataRepo)
# With parameters
{:ok, result} = Lotus.run_sql("SELECT * FROM users WHERE id = $1", [123])
# With search_path for schema resolution
{:ok, result} = Lotus.run_sql("SELECT * FROM users", [], search_path: "reporting, public")
Windowed pagination
Pass window: [limit: pos_integer, offset: non_neg_integer, count: :none | :exact]
to
page results from the SQL. See run_query/2
for details. The cache key automatically
incorporates the window so different pages are cached independently.
Returns whether unique query names are enforced.
Updates an existing query.
Returns the current version of Lotus.