PhoenixKitSync.SchemaInspector (PhoenixKitSync v0.2.5)

Copy Markdown View Source

Inspects database schema for DB Sync module.

Discovers available tables, their columns, and metadata. Uses PostgreSQL information_schema for introspection.

Security Considerations

  • Only returns tables in the public schema by default
  • System tables (pg_*, information_schema) are excluded
  • Admin can configure allowed/blocked tables (future feature)

Example

iex> SchemaInspector.list_tables()
{:ok, [
  %{name: "users", estimated_count: 150},
  %{name: "posts", estimated_count: 1200},
  ...
]}

iex> SchemaInspector.get_schema("users")
{:ok, %{
  table: "users",
  columns: [
    %{name: "id", type: "bigint", nullable: false, primary_key: true},
    %{name: "email", type: "character varying", nullable: false},
    ...
  ],
  primary_key: ["id"]
}}

Summary

Functions

Creates a table based on a schema definition from another database.

True for a table this module never lists, exports or imports.

Returns a map of table_name => list of referenced table names (FK dependencies). Only includes tables in the public schema.

Returns FK column details for a table: list of %{column, referenced_table, referenced_column}.

Gets the exact count of records in a local table.

Gets the primary key columns for a table.

Gets the schema (columns, types, constraints) for a specific table.

Returns a content checksum for a table (MD5 hash of all rows cast to text). Used to detect actual data differences beyond row count.

Returns unique constraint columns for a table (excluding primary key). Used to match records by unique fields (e.g., match users by email). Returns list of lists (each inner list is a set of columns forming a unique constraint).

Lists all available tables with row counts.

Brings a table schema to the shape create_table/3 builds from: "columns" as %{"name", "type", "nullable", "primary_key"} maps and "primary_key" as a list of column names.

Checks if a table exists.

Validates a PostgreSQL identifier (table or column name).

Functions

create_table(table_name, schema_def, opts \\ [])

@spec create_table(String.t(), map(), keyword()) :: :ok | {:error, any()}

Creates a table based on a schema definition from another database.

Used by DB Sync to create tables that exist on sender but not on receiver.

Parameters

  • table_name - Name of the table to create
  • schema_def - Schema definition map with columns and primary_key
  • opts - :schema (default "public"), and :actor_uuid, the user the creation is logged for (sync.table.created in the activity log)

Example

schema_def = %{
  "columns" => [
    %{"name" => "id", "type" => "bigint", "nullable" => false, "primary_key" => true},
    %{"name" => "name", "type" => "character varying", "nullable" => true}
  ],
  "primary_key" => ["id"]
}
SchemaInspector.create_table("users", schema_def)

excluded_table?(name)

@spec excluded_table?(term()) :: boolean()

True for a table this module never lists, exports or imports.

Three rules, any of which excludes a table:

  • the list: schema_migrations, the oban_* and pg_* tables, this module's own phoenix_kit_sync_connections and phoenix_kit_sync_transfers (the token hash is the REST credential), phoenix_kit_settings, and the tables that hold session tokens, OAuth tokens, invitations and provider keys (phoenix_kit_users_tokens, phoenix_kit_user_oauth_providers, phoenix_kit_organization_invitations, phoenix_kit_ai_accounts, phoenix_kit_ai_endpoints, phoenix_kit_buckets, phoenix_kit_payment_provider_configs)
  • the name: any table whose name contains token, secret, credential, password, api_key, oauth or session, so a credentials table added by another module is excluded before anyone lists it here
  • the host's own list, config :phoenix_kit_sync, excluded_tables: [...]

Names are compared without regard to case, on every rule.

The sender's own table list is data, not trusted: a pull checks the name here before it writes anything locally.

get_all_foreign_keys(opts \\ [])

@spec get_all_foreign_keys(keyword()) :: {:ok, map()} | {:error, any()}

Returns a map of table_name => list of referenced table names (FK dependencies). Only includes tables in the public schema.

get_foreign_key_columns(table_name, opts \\ [])

@spec get_foreign_key_columns(String.t(), keyword()) ::
  {:ok, [map()]} | {:error, any()}

Returns FK column details for a table: list of %{column, referenced_table, referenced_column}.

get_local_count(table_name, opts \\ [])

@spec get_local_count(String.t(), keyword()) ::
  {:ok, non_neg_integer()} | {:error, any()}

Gets the exact count of records in a local table.

This is used by the receiver to compare local vs sender record counts.

get_primary_key(table_name, opts \\ [])

@spec get_primary_key(String.t(), keyword()) :: {:ok, [String.t()]} | {:error, any()}

Gets the primary key columns for a table.

get_schema(table_name, opts \\ [])

@spec get_schema(String.t(), keyword()) :: {:ok, map()} | {:error, any()}

Gets the schema (columns, types, constraints) for a specific table.

Returns

  • {:ok, schema} - Map with table info and columns
  • {:error, :not_found} - Table doesn't exist
  • {:error, reason} - Database error

get_table_checksum(table_name, opts \\ [])

@spec get_table_checksum(String.t(), keyword()) :: {:ok, String.t()} | {:error, any()}

Returns a content checksum for a table (MD5 hash of all rows cast to text). Used to detect actual data differences beyond row count.

Skips checksumming for tables with more than max_rows rows (default: 10_000) to avoid expensive full-table scans. Returns {:ok, :too_large} in that case.

get_unique_columns(table_name, opts \\ [])

@spec get_unique_columns(String.t(), keyword()) ::
  {:ok, [[String.t()]]} | {:error, any()}

Returns unique constraint columns for a table (excluding primary key). Used to match records by unique fields (e.g., match users by email). Returns list of lists (each inner list is a set of columns forming a unique constraint).

list_tables(opts \\ [])

@spec list_tables(keyword()) :: {:ok, [map()]} | {:error, any()}

Lists all available tables with row counts.

Returns tables from the public schema, excluding system tables and security-sensitive tables.

Options

  • :include_phoenix_kit - Include phoenixkit* tables (default: true)
  • :schema - Database schema to inspect (default: "public")
  • :exact_counts - Use exact COUNT(*) instead of pg_stat estimates (default: true)

normalize_schema_def(schema_def)

@spec normalize_schema_def(map()) :: %{required(String.t()) => list()}

Brings a table schema to the shape create_table/3 builds from: "columns" as %{"name", "type", "nullable", "primary_key"} maps and "primary_key" as a list of column names.

Accepts what get_schema/2 returns (structs, or their JSON) and what the table-schema API sends (column_name, data_type, is_nullable, character_maximum_length, and primary_key from senders that include it), so schemas from older and newer senders both work. Names and types are not checked here; create_table/3 does that.

table_exists?(table_name, opts \\ [])

@spec table_exists?(String.t(), keyword()) :: boolean()

Checks if a table exists.

valid_identifier?(name)

@spec valid_identifier?(term()) :: boolean()

Validates a PostgreSQL identifier (table or column name).

Only accepts names that start with a letter or underscore and contain only alphanumerics and underscores. Anything else — including SQL metacharacters, whitespace, or quotes — is rejected.

Use this to guard any dynamic identifier that will be interpolated into raw SQL, before quoting it with double quotes.