Lotus.Schema (Lotus v1.0.0)

Copy Markdown View Source

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):

  1. The kind-specific :after_list_* event with a kind-specific payload.
  2. The unified :after_discover event 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

describe_table(repo_or_name, table_name, opts \\ [])

@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_table and :after_discover middleware events (see Lotus.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

get_table_stats(repo_or_name, table_name, opts \\ [])

@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.customers

Sources 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(*).

list_relations(repo_or_name, opts \\ [])

@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_relations and :after_discover middleware events (see Lotus.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"}, ...]

list_schemas(repo_or_name, opts \\ [])

@spec list_schemas(
  module() | String.t(),
  keyword()
) :: {:ok, [String.t()]} | {:error, term()}

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_schemas and :after_discover middleware events (see Lotus.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)

list_tables(repo_or_name, opts \\ [])

@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:

  1. Schema visibility is checked first - denied schemas block all their tables
  2. Table visibility is then applied to tables in allowed schemas
  3. 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_tables and :after_discover middleware events (see Lotus.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)