Lotus (Lotus v0.9.0)
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 ]
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
@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() ) :: {: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")
Returns whether unique query names are enforced.
Updates an existing query.
Returns the current version of Lotus.