Lotus.Source behaviour (Lotus v0.16.5)

Copy Markdown View Source

Source behavior for database-specific operations.

Defines the interface that each database source adapter's operations module must implement.

Summary

Callbacks

Applies a list of filters to an existing query, returning a new query string.

Applies a list of sorts to an existing query, returning a new query string.

Return the list of built-in deny rules for system tables and metadata relations that should be hidden from the schema browser for this source.

Return the list of built-in schema denies that should be hidden from schema listing.

Return the default schemas for this source when no schema options are provided.

Retrieve the execution plan for a SQL query.

Gets the schema information for a specific table.

List the exception modules that this source formats specially in format_error/1.

Return the SQL parameter placeholders for LIMIT and OFFSET clauses.

Lists all schemas in the given repository.

Lists all tables in the given repository for the specified schemas.

Return the SQL parameter placeholder string for a variable at a given index.

Quotes an identifier (column name, table name) using source-specific syntax.

Resolves which schema contains a table given a list of schemas to search.

Functions

Applies filters to a query using the source-specific implementation.

Applies sorts to a query using the source-specific implementation.

Returns the list of built-in deny rules for system tables and metadata relations.

Returns the list of built-in schema denies that should be hidden from schema listing.

Returns the default schemas for the given repository's source.

Executes a function within a transaction with source-specific session management.

Retrieves the execution plan for a SQL query.

Formats a database error into a consistent, human-readable string.

Gets the schema information for a specific table.

Returns the source-specific SQL parameter placeholders for LIMIT and OFFSET clauses.

Lists all schemas in the given repository.

Lists all tables in the given repository for the specified schemas.

Returns the source-specific SQL parameter placeholder to substitute for {{var}} occurrences.

Quotes an identifier (column name, table name) using source-specific syntax.

Resolves which schema contains a table given a list of schemas to search.

Sets the search path (schema list) for the given repository, if supported by the underlying source.

Sets the statement timeout (in milliseconds) for the given repository, if supported by the underlying source.

Types

repo()

@type repo() :: Ecto.Repo.t()

Callbacks

apply_filters(sql, filters)

@callback apply_filters(sql :: String.t(), filters :: [Lotus.Query.Filter.t()]) ::
  String.t()

Applies a list of filters to an existing query, returning a new query string.

For SQL sources, this typically wraps the original query in a CTE and appends WHERE clauses. Non-SQL sources may implement entirely different strategies.

Returns the original query unchanged when filters is empty.

apply_sorts(sql, sorts)

@callback apply_sorts(sql :: String.t(), sorts :: [Lotus.Query.Sort.t()]) :: String.t()

Applies a list of sorts to an existing query, returning a new query string.

For SQL sources, this appends an ORDER BY clause. Applied after filters so the ORDER BY operates on the filtered result set.

Returns the original query unchanged when sorts is empty.

builtin_denies(repo)

@callback builtin_denies(repo()) :: [
  {String.t() | nil | Regex.t(), String.t() | Regex.t()}
]

Return the list of built-in deny rules for system tables and metadata relations that should be hidden from the schema browser for this source.

Each rule is a {schema_pattern, table_pattern} tuple where patterns can be exact strings or regexes. Example rules:

  • {"pg_catalog", ~r/.*/} → deny all tables in Postgres pg_catalog
  • {nil, ~r/^sqlite_/} → deny all tables starting with sqlite_ in SQLite
  • {"public", "schema_migrations"} → deny migrations table in Postgres

builtin_schema_denies(repo)

@callback builtin_schema_denies(repo()) :: [String.t() | Regex.t()]

Return the list of built-in schema denies that should be hidden from schema listing.

Returns a list of schema patterns (strings or regexes) that should be denied.

Examples:

  • PostgreSQL: ["pg_catalog", "information_schema", ~r/^pg_temp/]
  • MySQL: ["mysql", "information_schema", "performance_schema", "sys"]
  • SQLite: [] (no schemas)

default_schemas(repo)

@callback default_schemas(repo()) :: [String.t()]

Return the default schemas for this source when no schema options are provided.

Each database source defines its own appropriate default:

  • PostgreSQL → ["public"]
  • MySQL → [database_name] (uses database name as schema)
  • SQLite → [] (schema-less)

execute_in_transaction(repo, function, keyword)

@callback execute_in_transaction(repo(), (-> any()), keyword()) ::
  {:ok, any()} | {:error, any()}

explain_plan(repo, sql, params, opts)

@callback explain_plan(repo(), sql :: String.t(), params :: list(), opts :: keyword()) ::
  {:ok, String.t()} | {:error, term()}

Retrieve the execution plan for a SQL query.

Uses the database-specific EXPLAIN syntax to return the query plan as a string suitable for analysis.

Examples of source-specific behavior:

  • Postgres → EXPLAIN (FORMAT JSON) <sql>
  • MySQL → EXPLAIN FORMAT=JSON <sql>
  • SQLite → EXPLAIN QUERY PLAN <sql>

Options:

  • :search_path — PostgreSQL search path (optional)

format_error(any)

@callback format_error(any()) :: String.t()

get_table_schema(repo, schema, table)

@callback get_table_schema(repo(), schema :: String.t() | nil, table :: String.t()) :: [
  %{
    name: String.t(),
    type: String.t(),
    nullable: boolean(),
    default: String.t() | nil,
    primary_key: boolean()
  }
]

Gets the schema information for a specific table.

Returns a list of column definitions. Each column is a map with exactly these keys:

  • :name - Column name (String.t())
  • :type - SQL type as string (e.g., "varchar(255)", "integer", "text")
  • :nullable - Whether column allows NULL (boolean)
  • :default - Default value as string or nil
  • :primary_key - Whether column is part of primary key (boolean)

Example return value

[
  %{
    name: "id",
    type: "integer",
    nullable: false,
    default: nil,
    primary_key: true
  },
  %{
    name: "email",
    type: "varchar(255)",
    nullable: false,
    default: nil,
    primary_key: false
  }
]

handled_errors()

@callback handled_errors() :: [module()]

List the exception modules that this source formats specially in format_error/1.

limit_offset_placeholders(limit_index, offset_index)

@callback limit_offset_placeholders(
  limit_index :: pos_integer(),
  offset_index :: pos_integer()
) :: {limit_placeholder :: String.t(), offset_placeholder :: String.t()}

Return the SQL parameter placeholders for LIMIT and OFFSET clauses.

Some databases (like MySQL) don't support typed placeholders for LIMIT/OFFSET, while others (like PostgreSQL) do.

Returns a tuple of {limit_placeholder, offset_placeholder}.

Examples:

  • Postgres → {"$1", "$2"}
  • MySQL → {"?", "?"}
  • SQLite → {"?", "?"}

list_schemas(repo)

@callback list_schemas(repo()) :: [String.t()]

Lists all schemas in the given repository.

Returns a list of schema names. For databases without schema support (like SQLite), returns an empty list.

Return format

  • PostgreSQL/MySQL: ["public", "reporting", "analytics", ...]
  • SQLite: []

list_tables(repo, schemas, include_views?)

@callback list_tables(repo(), schemas :: [String.t()], include_views? :: boolean()) :: [
  {schema :: String.t() | nil, table :: String.t()}
]

Lists all tables in the given repository for the specified schemas.

Returns a list of {schema, table} tuples. For databases without schema support (like SQLite), schema will always be nil.

Return format

  • PostgreSQL/MySQL: [{"public", "users"}, {"reporting", "orders"}, ...]
  • SQLite: [{nil, "users"}, {nil, "orders"}, ...]

Options:

  • :include_views - Include views in results (default: false)

param_placeholder(index, var, type)

@callback param_placeholder(
  index :: pos_integer(),
  var :: String.t(),
  type :: atom() | nil
) :: String.t()

Return the SQL parameter placeholder string for a variable at a given index.

The placeholder may include database-specific type casting based on the variable type.

Examples of source-specific output:

  • Postgres → "$1" (untyped), "$1::integer" (typed)
  • MySQL → "?" (untyped), "CAST(? AS SIGNED)" (typed)
  • SQLite → "?" (always untyped)

Supported types for casting: :date, :datetime, :time, :number, :integer, :boolean, :json

quote_identifier(t)

@callback quote_identifier(String.t()) :: String.t()

Quotes an identifier (column name, table name) using source-specific syntax.

Examples:

  • PostgreSQL → "column_name"
  • MySQL → `column_name`
  • SQLite → "column_name"

resolve_table_schema(repo, table, schemas)

@callback resolve_table_schema(repo(), table :: String.t(), schemas :: [String.t()]) ::
  String.t() | nil

Resolves which schema contains a table given a list of schemas to search.

Returns the schema name if found, nil otherwise. For databases without schema support (SQLite), this should always return nil.

The search should respect the order of schemas provided (first match wins).

set_search_path(repo, t)

@callback set_search_path(repo(), String.t()) :: :ok | no_return()

set_statement_timeout(repo, non_neg_integer)

@callback set_statement_timeout(repo(), non_neg_integer()) :: :ok | no_return()

Functions

apply_filters(repo_or_name, sql, filters)

@spec apply_filters(repo() | String.t() | nil, String.t(), [Lotus.Query.Filter.t()]) ::
  String.t()

Applies filters to a query using the source-specific implementation.

Returns the original query unchanged when filters is empty.

apply_sorts(repo_or_name, sql, sorts)

@spec apply_sorts(repo() | String.t() | nil, String.t(), [Lotus.Query.Sort.t()]) ::
  String.t()

Applies sorts to a query using the source-specific implementation.

Returns the original query unchanged when sorts is empty.

builtin_denies(repo)

@spec builtin_denies(repo()) :: [
  {String.t() | nil | Regex.t(), String.t() | Regex.t()}
]

Returns the list of built-in deny rules for system tables and metadata relations.

These rules are used by the visibility module to filter out system tables.

builtin_schema_denies(repo)

@spec builtin_schema_denies(repo()) :: [String.t() | Regex.t()]

Returns the list of built-in schema denies that should be hidden from schema listing.

These rules are used by the visibility module to filter out system schemas.

default_schemas(repo)

Returns the default schemas for the given repository's source.

Each database source defines its own appropriate default:

  • PostgreSQL → ["public"]
  • MySQL → [database_name] (uses database name as schema)
  • SQLite → [] (schema-less)

execute_in_transaction(repo, fun, opts \\ [])

@spec execute_in_transaction(repo(), (-> any()), keyword()) ::
  {:ok, any()} | {:error, any()}

Executes a function within a transaction with source-specific session management.

The source handles:

  • Starting a transaction with appropriate timeout
  • Setting read-only mode, statement timeout, and search path if specified in opts
  • Running the provided function
  • Properly cleaning up session state (important for MySQL/SQLite session persistence)

Options:

  • :read_only - whether to run in read-only mode (default: true)
  • :statement_timeout_ms - statement timeout in milliseconds (default: 5000)
  • :timeout - transaction timeout in milliseconds (default: 15000)
  • :search_path - PostgreSQL search path (optional)

explain_plan(repo, sql, params \\ [], opts \\ [])

@spec explain_plan(repo(), String.t(), list(), keyword()) ::
  {:ok, String.t()} | {:error, term()}

Retrieves the execution plan for a SQL query.

Dispatches to the source-specific implementation based on the repo's adapter. Returns the plan as a string suitable for AI analysis.

Options:

  • :search_path - PostgreSQL search path (optional)

format_error(error)

@spec format_error(any()) :: String.t()

Formats a database error into a consistent, human-readable string.

Dispatches to the correct source if the error type is recognized, otherwise falls back to the default implementation.

get_table_schema(repo, schema, table)

@spec get_table_schema(repo(), String.t() | nil, String.t()) :: [
  %{
    name: String.t(),
    type: String.t(),
    nullable: boolean(),
    default: String.t() | nil,
    primary_key: boolean()
  }
]

Gets the schema information for a specific table.

Dispatches to the source-specific implementation based on the repo's adapter.

limit_offset_placeholders(repo_or_name, limit_index, offset_index)

@spec limit_offset_placeholders(
  repo() | String.t() | nil,
  pos_integer(),
  pos_integer()
) ::
  {String.t(), String.t()}

Returns the source-specific SQL parameter placeholders for LIMIT and OFFSET clauses.

  • repo_or_name can be the Repo module or a data-repo name string.
  • limit_index and offset_index are 1-based indexes for the parameters.

Falls back to the configured default data repo when nil is given.

list_schemas(repo)

@spec list_schemas(repo()) :: [String.t()]

Lists all schemas in the given repository.

Dispatches to the source-specific implementation based on the repo's adapter.

list_tables(repo, schemas, include_views? \\ false)

@spec list_tables(repo(), [String.t()], boolean()) :: [{String.t() | nil, String.t()}]

Lists all tables in the given repository for the specified schemas.

Dispatches to the source-specific implementation based on the repo's adapter.

param_placeholder(repo_or_name, index, var, type)

@spec param_placeholder(
  repo() | String.t() | nil,
  pos_integer(),
  String.t(),
  atom() | nil
) ::
  String.t()

Returns the source-specific SQL parameter placeholder to substitute for {{var}} occurrences.

  • repo_or_name can be the Repo module or a data-repo name string.
  • index is 1-based (for drivers like Postgres that need $1, $2, …).
  • var and type are available to sources if they need special handling.

Falls back to the configured default data repo when nil is given.

quote_identifier(repo_or_name, identifier)

@spec quote_identifier(repo() | String.t() | nil, String.t()) :: String.t()

Quotes an identifier (column name, table name) using source-specific syntax.

resolve_table_schema(repo, table, schemas)

@spec resolve_table_schema(repo(), String.t(), [String.t()]) :: String.t() | nil

Resolves which schema contains a table given a list of schemas to search.

Dispatches to the source-specific implementation based on the repo's adapter.

set_search_path(repo, path)

@spec set_search_path(repo(), String.t()) :: :ok | no_return()

Sets the search path (schema list) for the given repository, if supported by the underlying source.

On unsupported sources this is a no-op.

set_statement_timeout(repo, ms)

@spec set_statement_timeout(repo(), non_neg_integer()) :: :ok | no_return()

Sets the statement timeout (in milliseconds) for the given repository, if supported by the underlying source.