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
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 createschema_def- Schema definition map with columns and primary_keyopts-:schema(default"public"), and:actor_uuid, the user the creation is logged for (sync.table.createdin 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)
True for a table this module never lists, exports or imports.
Three rules, any of which excludes a table:
- the list:
schema_migrations, theoban_*andpg_*tables, this module's ownphoenix_kit_sync_connectionsandphoenix_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,oauthorsession, 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.
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}.
@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.
Gets the primary key columns for a table.
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
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.
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.
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)
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.
Checks if a table exists.
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.