Schema introspection functionality for Lotus.
Provides functions to list schemas, tables and inspect table schemas across different database adapters (PostgreSQL, MySQL, SQLite, etc.).
Visibility Filtering
All schema and table listing functions automatically apply visibility rules configured in your application:
- Schema visibility filters which schemas are accessible
- Table visibility filters which tables within allowed schemas are accessible
- Built-in security automatically blocks system schemas and tables
Schema visibility takes precedence - if a schema is denied, all tables within it are blocked regardless of table-level rules.
Middleware and Caching
Every discovery call fires two middleware events (see Lotus.Middleware):
- The kind-specific
:after_list_*event with a kind-specific payload. - The unified
:after_discoverevent with%{kind:, source:, result:, context:}.
Both events run outside the cache callback — only the raw, visibility-filtered adapter result is cached. Context-sensitive middleware (e.g. per-tenant table filtering) is therefore safe to use without poisoning the cache, at the cost of running the middleware pipeline on every call.
Lotus.Visibility.Resolver callbacks receive (source_name, scope). When scope
is nil (the default), cache keys are identical to pre-scope versions. When
non-nil, a digest of the scope is appended to the cache key so different scopes
produce independent cached entries. Keep scope low-cardinality for good cache
hit rates.
A resolver that reads runtime context (e.g. the process dictionary) instead of
using the scope argument will cache incorrectly — place context-dependent
logic in middleware or use scope to key the cache.
Database-Specific Behavior
- PostgreSQL: Returns namespaced
{schema, table}tuples - MySQL: Returns
{database, table}tuples (schemas = databases in MySQL) - SQLite: Returns table names as strings (schema-less)
Summary
Functions
Describes a specific table (column definitions).
Gets basic statistics about a table.
Lists all relations (tables with namespace information) in the given repository.
Lists all visible schemas in the given repository.
Lists all visible tables in the given repository.
Functions
@spec describe_table(module() | String.t(), String.t(), keyword()) :: {:ok, [map()]} | {:error, term()}
Describes a specific table (column definitions).
Returns a list of column definitions with their types and constraints.
Options
:schema- Look for table in specific namespace:schemas- Search for table in multiple namespaces (first match wins):search_path- Use PostgreSQL search_path to resolve table location:cache- Cache options (profile, ttl_ms, etc.):context- Opaque value passed to the:after_describe_tableand:after_discovermiddleware events (seeLotus.Middleware):scope- Opaque value passed to the visibility resolver and hashed into the cache key. Different scopes produce independent cached entries.
Examples
{:ok, columns} = Lotus.Schema.describe_table(MyApp.Repo, "users")
# Column definitions for public.users
{:ok, columns} = Lotus.Schema.describe_table("postgres", "customers", schema: "reporting")
# Column definitions for reporting.customers
{:ok, columns} = Lotus.Schema.describe_table("postgres", "customers", search_path: "reporting, public")
# Finds customers table using search_path resolution
@spec get_table_stats(module() | String.t(), String.t(), keyword()) :: {:ok, map()} | {:error, binary()}
Gets basic statistics about a table.
Returns information like row count and table size.
Options
:schema- Look for table in specific schema:schemas- Search for table in multiple schemas (first match wins):search_path- Use PostgreSQL search_path to resolve table location:scope- Opaque value passed to the visibility resolver and hashed into the cache key:cache- Cache options (profile, ttl_ms, etc.)
Examples
{:ok, stats} = Lotus.Schema.get_table_stats(MyApp.Repo, "users")
# Returns: %{row_count: 1234}
{:ok, stats} = Lotus.Schema.get_table_stats("postgres", "customers", schema: "reporting")
# Gets stats for reporting.customersSources whose adapter implements Lotus.Source.Adapter.table_stats/3
answer from that callback and may return additional keys alongside
:row_count. Everything else falls back to SELECT COUNT(*).
@spec list_relations( module() | String.t(), keyword() ) :: {:ok, [{String.t() | nil, String.t()}]} | {:error, term()}
Lists all relations (tables with namespace information) in the given repository.
Similar to list_tables/2 but returns {schema, table} tuples instead of just table names. Useful for UIs that need to display the full namespace-qualified list.
Options
:schema- Search in specific schema:schemas- Search in multiple schemas:search_path- Use PostgreSQL search_path:include_views- Include views in results (default: false):cache- Cache options (profile, ttl_ms, etc.):context- Opaque value passed to the:after_list_relationsand:after_discovermiddleware events (seeLotus.Middleware):scope- Opaque value passed to the visibility resolver and hashed into the cache key. Different scopes produce independent cached entries.
Examples
{:ok, relations} = Lotus.Schema.list_relations("postgres", search_path: "reporting, public")
# Returns [{"reporting", "customers"}, {"reporting", "orders"}, {"public", "users"}, ...]
Lists all visible schemas in the given repository.
Returns a list of schema names filtered by visibility rules. For databases without schemas (like SQLite), returns an empty list.
Note: Results are automatically filtered by schema visibility rules.
System schemas (like pg_catalog) are always blocked for security.
Options
:cache- Cache options (profile, ttl_ms, etc.):context- Opaque value passed to the:after_list_schemasand:after_discovermiddleware events (seeLotus.Middleware):scope- Opaque value passed to the visibility resolver and hashed into the cache key. Different scopes produce independent cached entries.
Examples
{:ok, schemas} = Lotus.Schema.list_schemas(MyApp.Repo)
# PostgreSQL: ["public", "reporting", ...] (filtered by visibility)
{:ok, schemas} = Lotus.Schema.list_schemas("mysql")
# MySQL: ["app_production", "analytics_db", ...] (databases = schemas)
{:ok, schemas} = Lotus.Schema.list_schemas("sqlite")
# SQLite: [] (schema-less database)
@spec list_tables( module() | String.t(), keyword() ) :: {:ok, [{String.t(), String.t()}] | [String.t()]} | {:error, term()}
Lists all visible tables in the given repository.
For databases with schemas (like PostgreSQL), returns {schema, table} tuples. For databases without schemas (like SQLite), returns just table names as strings.
Note: Results are automatically filtered by visibility rules:
- Schema visibility is checked first - denied schemas block all their tables
- Table visibility is then applied to tables in allowed schemas
- System tables are always blocked for security
Options
:schema- Search in specific schema (e.g.,schema: "reporting"):schemas- Search in multiple schemas (e.g.,schemas: ["reporting", "public"]):search_path- Use PostgreSQL search_path (e.g.,search_path: "reporting, public"):include_views- Include views in results (default: false):cache- Cache options (profile, ttl_ms, etc.):context- Opaque value passed to the:after_list_tablesand:after_discovermiddleware events (seeLotus.Middleware):scope- Opaque value passed to the visibility resolver and hashed into the cache key. Different scopes produce independent cached entries.
Examples
{:ok, tables} = Lotus.Schema.list_tables(MyApp.Repo)
# PostgreSQL: [{"public", "users"}, {"public", "posts"}, ...] (filtered by visibility)
{:ok, tables} = Lotus.Schema.list_tables("postgres", search_path: "reporting, public")
# PostgreSQL: [{"reporting", "customers"}, {"reporting", "orders"}, {"public", "users"}, ...]
{:ok, tables} = Lotus.Schema.list_tables("mysql")
# MySQL: [{"app_db", "users"}, {"analytics_db", "reports"}, ...] (databases = schemas)
{:ok, tables} = Lotus.Schema.list_tables("sqlite")
# SQLite: ["products", "orders", "order_items"] (schema-less)