Packages
electric
1.5.0
1.7.8
1.7.7
1.7.6
1.7.5
1.7.4
1.7.3
1.7.2
1.7.1
1.7.0
1.6.10
1.6.9
1.6.8
1.6.7
1.6.6
1.6.5
1.6.4
1.6.3
1.6.2
1.6.1
1.6.0
1.5.1
1.5.0
1.4.16
1.4.16-beta-1
1.4.15
1.4.14
1.4.13
1.4.12
1.4.11
1.4.10
1.4.8
1.4.7
1.4.6
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.4
1.3.3
1.3.2
1.2.4
1.2.3
1.2.2
1.2.1
1.2.0
1.1.14
1.1.13
1.1.12
1.1.11
1.1.10
1.1.9
1.1.8
1.1.7
1.1.6
retired
1.1.5
retired
1.1.4
retired
1.1.3
retired
1.1.2
1.1.1
1.1.0
1.0.24
1.0.23
1.0.22
1.0.21
1.0.20
1.0.19
1.0.18
1.0.17
1.0.15
1.0.13
1.0.12
1.0.11
1.0.10
1.0.9
1.0.5
1.0.4
1.0.3
1.0.2
1.0.1
1.0.0
1.0.0-beta.23
1.0.0-beta.22
1.0.0-beta.20
1.0.0-beta.19
1.0.0-beta.18
1.0.0-beta.17
1.0.0-beta.16
1.0.0-beta.15
1.0.0-beta.14
1.0.0-beta.13
1.0.0-beta.12
1.0.0-beta.11
1.0.0-beta.10
1.0.0-beta.9
1.0.0-beta.8
1.0.0-beta.7
1.0.0-beta.6
1.0.0-beta.5
1.0.0-beta.4
1.0.0-beta.3
1.0.0-beta.2
1.0.0-beta.1
0.9.5
0.9.4
0.9.3
0.9.2
0.9.1
0.9.0
0.8.1
0.8.0
0.7.7
0.7.6
0.7.5
0.7.4
0.7.3
0.7.2
0.7.1
0.7.0
0.6.3
0.6.2
0.6.1
0.5.2
0.4.4
Postgres sync engine. Sync little subsets of your Postgres data into local apps and services.
Current section
Files
Jump to
Current section
Files
lib/electric/postgres/inspector.ex
defmodule Electric.Postgres.Inspector do
alias Electric.Replication.Eval.Parser
import Electric, only: :macros
@type relation :: Electric.relation()
@type relation_id :: Electric.relation_id()
@type relation_kind :: :ordinary_table | :partitioned_table
@type type_kind :: :base | :composite | :domain | :enum | :pseudo | :range | :multirange
@type column_info :: %{
name: String.t(),
type: String.t(),
type_mod: integer() | nil,
type_kind: type_kind(),
formatted_type: String.t(),
pk_position: non_neg_integer() | nil,
type_id: {typid :: non_neg_integer(), typmod :: integer()},
array_dimensions: non_neg_integer(),
not_null: boolean(),
array_type: String.t()
}
@type relation_info :: %{
relation_id: relation_id(),
relation: relation(),
kind: relation_kind(),
parent: nil | relation(),
children: nil | [relation(), ...]
}
@type supported_features :: %{
supports_generated_column_replication: boolean()
}
@callback load_relation_oid(relation(), opts :: term()) ::
{:ok, Electric.oid_relation()}
| :table_not_found
| {:error, String.t() | :connection_not_available}
@callback load_relation_info(relation_id(), opts :: term()) ::
{:ok, relation_info()}
| :table_not_found
| {:error, String.t() | :connection_not_available}
@callback load_column_info(relation_id(), opts :: term()) ::
{:ok, [column_info()]}
| :table_not_found
| {:error, String.t() | :connection_not_available}
@callback load_supported_features(opts :: term()) ::
{:ok, supported_features()}
| {:error, String.t() | :connection_not_available}
@callback clean(relation_id(), opts :: term()) :: :ok
@callback list_relations_with_stale_cache(opts :: term()) ::
{:ok, [Electric.oid_relation()]} | :error
@type inspector :: {module(), opts :: term()}
def for_stack(stack_id) do
Electric.StackConfig.lookup!(stack_id, :inspector)
end
@doc """
Expects the table name provided by the user and validates that the table exists,
returning the OID.
Table name is expected to have been normalized beforehand
"""
@spec load_relation_oid(relation(), inspector()) ::
{:ok, Electric.oid_relation()}
| :table_not_found
| {:error, String.t() | :connection_not_available}
def load_relation_oid(relation, {module, opts}) when is_relation(relation) do
module.load_relation_oid(relation, opts)
end
@doc """
Load additional information about a given relation.
Additional information includes the relation kind, parent/child relationships,
and other metadata.
"""
@spec load_relation_info(relation_id(), inspector()) ::
{:ok, relation_info()}
| :table_not_found
| {:error, String.t() | :connection_not_available}
def load_relation_info(relation_id, {module, opts}) when is_relation_id(relation_id) do
module.load_relation_info(relation_id, opts)
end
@doc """
Load column information about a given table using a provided inspector.
"""
@spec load_column_info(relation_id(), inspector()) ::
{:ok, [column_info()]}
| :table_not_found
| {:error, String.t() | :connection_not_available}
def load_column_info(relation_id, {module, opts}) when is_relation_id(relation_id) do
module.load_column_info(relation_id, opts)
end
@doc """
Load the supported features on the target database using a provided inspector.
"""
@spec load_supported_features(inspector()) ::
{:ok, supported_features()} | {:error, String.t() | :connection_not_available}
def load_supported_features({module, opts}) do
module.load_supported_features(opts)
end
@doc """
Clean up all information about a given relation using a provided inspector.
"""
@spec clean(relation_id(), inspector()) :: :ok
def clean(relation_id, {module, opts}) when is_relation_id(relation_id) do
module.clean(relation_id, opts)
end
@doc """
Get columns that should be considered a PK for table. If the table
has no PK, then we're considering all columns as identifying.
"""
@spec get_pk_cols([column_info(), ...]) :: [String.t(), ...]
def get_pk_cols([_ | _] = columns) do
columns
|> Enum.reject(&is_nil(&1.pk_position))
|> Enum.sort_by(& &1.pk_position)
|> Enum.map(& &1.name)
|> case do
[] -> Enum.map(columns, & &1.name)
results -> results
end
end
@doc """
Convert a column list into something that can be used by
`Electric.Replication.Eval.Parser.parse_and_validate_expression/2`
"""
@spec columns_to_expr([column_info(), ...]) :: Parser.refs_map()
def columns_to_expr(columns) when is_list(columns) do
Map.new(columns, fn
%{name: name, array_type: arr_type} when not is_nil(arr_type) ->
{[name], {:array, as_atom(arr_type)}}
%{name: name, type_kind: :enum, type: type_name} ->
{[name], {:enum, type_name}}
%{name: name, type: type} ->
{[name], as_atom(type)}
end)
end
@doc """
List relations that have stale cache. Doesn't clean the cache immediately,
that's left to the caller. Inspectors without cache will return an `:error`.
"""
@spec list_relations_with_stale_cache(inspector()) ::
{:ok, [Electric.oid_relation()]} | :error
def list_relations_with_stale_cache({module, opts}) do
module.list_relations_with_stale_cache(opts)
end
defp as_atom(type) when is_binary(type), do: String.to_atom(type)
defp as_atom(type) when is_atom(type), do: type
end