Lotus.Visibility (Lotus v0.9.2)
View SourceSchema and table visibility filtering for Lotus.
Implements a two-level visibility system where schema visibility takes precedence:
- Schema visibility is checked first - if a schema is denied, all its tables are blocked
- Table visibility is only checked if the schema is allowed
This ensures security by default while providing fine-grained control.
Understanding Schemas Across Database Systems
Important: "Schema" means different things in different databases:
PostgreSQL
- True namespaced schemas within a single database
- Examples:
public,reporting,tenant_123 - System schemas:
pg_catalog,information_schema,pg_toast
MySQL
- Schemas = Databases (synonymous terms)
- Examples:
lotus_production,analytics_db,warehouse - System schemas:
mysql,information_schema,performance_schema,sys
SQLite
- No schema support (schema-less database)
- Schema visibility rules don't apply
Quick Start
config :lotus,
# Schema-level rules (higher precedence)
schema_visibility: %{
postgres: [
allow: ["public", ~r/^tenant_/], # Only public + tenant schemas
deny: ["legacy"] # Block legacy schema
],
mysql: [
allow: ["app_db", "analytics_db"], # Only these databases
deny: ["staging_db"] # Block staging database
]
},
# Table-level rules (lower precedence)
table_visibility: %{
default: [
deny: ["user_passwords", "api_keys", ~r/^audit_/]
],
postgres: [
allow: [
{"public", ~r/^dim_/}, # Dimension tables only
{"analytics", ~r/.*/} # All analytics tables
]
]
}Rule Evaluation
1. Schema Gating (First Check)
if not allowed_schema?(repo_name, schema) do
false # Schema denied → all tables blocked
else
# Schema allowed → check table rules
end2. Schema-Scoped Allow Posture
Allow rules are scoped to specific schemas, not global:
# Rules: allow: [{"restricted", "allowed_table"}]
{"restricted", "any_table"} → denied (has allow posture, must match)
{"public", "any_table"} → allowed (no allow posture for public)3. Deny Always Wins
Any deny rule (builtin or user-defined) blocks access immediately.
Rule Formats
Schema Rules
"exact_name"- Matches exact schema name~r/pattern/- Regex pattern for dynamic matching:all- Special allow value (permits all schemas)
Table Rules
{"schema", "table"}- Exact schema.table match{"schema", ~r/pattern/}- Tables matching regex in specific schema{~r/schema_pattern/, "table"}- Table in schemas matching pattern"table"- Table name in any schema (global rule)
Built-in Security
System schemas are automatically denied:
- PostgreSQL:
pg_catalog,information_schema,pg_toast,pg_temp_* - MySQL:
mysql,information_schema,performance_schema,sys - All databases:
schema_migrations,lotus_queries
Examples
Multi-tenant Application
config :lotus,
schema_visibility: %{
postgres: [
allow: ["public", ~r/^tenant_\d+$/], # tenant_123, etc.
deny: ["admin"]
]
},
table_visibility: %{
postgres: [
allow: [
{"public", ~r/^shared_/}, # Shared lookup tables
{~r/^tenant_/, "users"}, # Users in each tenant
{~r/^tenant_/, "orders"} # Orders in each tenant
],
deny: [
{~r/^tenant_/, "audit_logs"} # Hide audit logs
]
]
}Data Warehouse
config :lotus,
schema_visibility: %{
postgres: [
allow: ["public", "warehouse", "analytics"]
]
},
table_visibility: %{
postgres: [
allow: [
{"public", ~r/^dim_/}, # Dimension tables
{"public", ~r/^fact_/}, # Fact tables
{"warehouse", ~r/.*/}, # All warehouse
{"analytics", ~r/^report_/} # Only reports
],
deny: [
{"public", ~r/^raw_/} # Hide raw data
]
]
}MySQL Multi-Database
config :lotus,
schema_visibility: %{
mysql: [
# Remember: schemas = databases in MySQL
allow: ["app_production", "analytics_warehouse"],
deny: ["staging_db", "backup_db"]
]
}API
Direct visibility checking:
# Check schema visibility
Lotus.Visibility.allowed_schema?("postgres", "public") # true/false
# Check table visibility
Lotus.Visibility.allowed_relation?("postgres", {"public", "users"}) # true/false
# Filter lists
Lotus.Visibility.filter_schemas(["public", "pg_catalog"], "postgres") # ["public"]
# Validate requested schemas
Lotus.Visibility.validate_schemas(["public", "restricted"], "postgres")
# :ok | {:error, :schema_not_visible, denied: [...]}Schema-aware Lotus functions automatically apply visibility:
{:ok, schemas} = Lotus.list_schemas("postgres") # Filtered list
{:ok, tables} = Lotus.list_tables("postgres") # Filtered list
{:error, msg} = Lotus.list_tables("postgres", schemas: ["denied"]) # ErrorFor more detailed examples and configuration patterns, see the Visibility Guide.
Summary
Functions
Checks if a relation (schema, table) is allowed for the given data repo.
Checks if a schema is visible for the given data repo.
Filters a list of relations to only those that are visible.
Filters a list of schemas to only those that are visible.
Validates that all requested schemas are visible.
Functions
Checks if a relation (schema, table) is allowed for the given data repo.
This now checks schema visibility first, then table visibility.
Checks if a schema is visible for the given data repo.
Returns:
trueif the schema is allowedfalseif the schema is denied
@spec filter_relations([{String.t() | nil, String.t()}], String.t()) :: [ {String.t() | nil, String.t()} ]
Filters a list of relations to only those that are visible.
Filters a list of schemas to only those that are visible.
@spec validate_schemas([String.t()], String.t()) :: :ok | {:error, :schema_not_visible, [{:denied, [String.t()]}]}
Validates that all requested schemas are visible.
Returns:
:okif all schemas are visible{:error, :schema_not_visible, denied: [schemas]}if any are denied