# `Electric.Replication.Eval.Env`
[🔗](https://github.com/electric-sql/electric/tree/%40core/sync-service%401.6.2/packages/sync-service/lib/electric/replication/eval/env.ex#L1)

Evaluation environment for parsing PostgreSQL expressions.

We have a need to parse PG expressions, and then be able to
execute them in Electric without reaching for Postgres on every operation.
This is achieved by parsing Postgres expressions, however Postgres has
a lot of features we're unlikely to support, and, moreover, very dynamic:
new features, new data types - everything can be redefined, including
so-called "preferred" data types, new type categories, implicit casts, etc.
Redefining all this for normal types is quite unlikely in the wild, but may be
later common for custom types.

Parsing PG expressions, we need to know what we support. It's then reasonable
to have a good set of defaults we know how to deal with, but leave an escape hatch
so that until we add support for pulling all this data from PG, we have a working
system. Even afterwards, a set of PG functions using, say `PLSQL` is likely to use
default PG functions, which we still need to know how to execute.

This module defines a struct that contains information, relevant to parsing PG
statements into something Electric can understand, while respecting PG types
and function/operator overload selections.

It's also worth noting that our defaults, especially implicit casts specifically
omit any "system" types (e.g. `regclass` or `pg_ndistinct`) because we're essentially
never going to have enough context in Electric to be able to correctly utilize those
types.

# `basic_type`

```elixir
@type basic_type() :: atom()
```

# `basic_type_registry`

```elixir
@type basic_type_registry() :: %{required(basic_type()) =&gt; type_info()}
```

# `cast_function`

```elixir
@type cast_function() :: {module :: module(), function :: atom()}
```

# `cast_key`

```elixir
@type cast_key() :: {from :: basic_type(), to :: basic_type()}
```

# `cast_registry`

```elixir
@type cast_registry() :: %{required(cast_key()) =&gt; cast_function()}
```

# `env_property`

```elixir
@type env_property() ::
  {:funcs, funcs()}
  | {:operators, funcs()}
  | {:explicit_casts, cast_registry()}
  | {:implicit_casts, implicit_cast_registry()}
  | {:known_basic_types, basic_type_registry()}
```

# `flat_pg_type`

```elixir
@type flat_pg_type() :: basic_type() | {:composite, map()} | :record | {:enum, term()}
```

# `func`

```elixir
@type func() :: %{
  optional(:strict?) =&gt; boolean(),
  optional(:immutable?) =&gt; boolean(),
  optional(:variadic_arg) =&gt; non_neg_integer(),
  args: [pg_type()],
  returns: pg_type(),
  implementation: {module(), atom()} | fun(),
  name: String.t()
}
```

# `func_id`

```elixir
@type func_id() :: {name :: String.t(), arity :: non_neg_integer()}
```

# `funcs`

```elixir
@type funcs() :: %{required(func_id()) =&gt; [func(), ...]}
```

# `implicit_cast_function`

```elixir
@type implicit_cast_function() :: cast_function() | :as_is
```

# `implicit_cast_registry`

```elixir
@type implicit_cast_registry() :: %{required(cast_key()) =&gt; implicit_cast_function()}
```

# `pg_type`

```elixir
@type pg_type() ::
  flat_pg_type()
  | {:array, flat_pg_type()}
  | {:range, flat_pg_type()}
  | {:multirange, flat_pg_type()}
```

# `t`

```elixir
@type t() :: %Electric.Replication.Eval.Env{
  allow_enums: boolean(),
  explicit_casts: cast_registry(),
  funcs: funcs(),
  implicit_casts: implicit_cast_registry(),
  known_basic_types: basic_type_registry(),
  operators: funcs()
}
```

# `type_info`

```elixir
@type type_info() :: %{category: atom(), preferred?: boolean()}
```

# `can_implicitly_coerce_types?`

```elixir
@spec can_implicitly_coerce_types?(t(), [pg_type()], [pg_type()]) :: boolean()
```

Check if a list of inputs can be implicitly coerced to a list of targets.

Note that other functions may not exactly support all of types

# `const_to_pg_string`

# `empty`

Create a new empty environment, without any default functions or explicit casts

# `find_cast_function`

```elixir
@spec find_cast_function(t(), pg_type(), pg_type()) ::
  {:ok, implicit_cast_function()}
  | {:ok, :array_cast, implicit_cast_function()}
  | :error
```

Find an appropriate cast function for the given types in this environment.

# `get_type_category`

```elixir
@spec get_type_category(t(), pg_type()) :: atom()
```

Get type category for the given type (possibly non-basic).

# `get_unified_coercion_targets`

# `implicitly_castable?`

```elixir
@spec implicitly_castable?(t(), basic_type(), basic_type()) :: boolean()
```

Check if one type is implicitly castable to another type in this environment.

# `is_preferred?`

Check if type is preferred within the type category.

# `new`

```elixir
@spec new([env_property()]) :: t()
```

Create a new environment with known defaults, merging in provided keys

# `parse_const`

```elixir
@spec parse_const(t(), String.t() | nil, pg_type()) :: {:ok, term()} | :error
```

Parse an unknown value constant as a known type in the current environment.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
