PhoenixKitSync.ConnectionNotifier.Prepare (PhoenixKitSync v0.3.0)

Copy Markdown View Source

Record-transformation and value-preparation helpers shared across the import paths in ConnectionNotifier.

Three concerns:

  1. value/4, key_value/2, value/3 and value/1 — convert JSON-transported scalars back into Postgrex-ready terms. A string is read by the type of the column it goes into, never by its shape: ISO8601 text becomes a DateTime / Date / Time only in a timestamp / date / time column, and a numeric string a %Decimal{} (numeric, decimal) or a float (double precision, real) only in a numeric one. "2026-01-01" in a text column stays text. value/3 and value/1 are the forms for a caller with no column type to go by; they read by shape.
  2. numeric_column_types/1 — maps each numeric column of a table to :decimal or :float. Cached once per table-import by the importer; empty map on error (safe "don't coerce" fallback).
  3. Record field helpers (get_field/2, put_field/3, drop_field/2, normalize_keys/1) — records arrive with either string or atom keys depending on whether they came through JSON or internal code. These helpers access/modify fields uniformly without creating atoms from untrusted data.

All functions are pure or depend only on SchemaInspector (for column introspection) — none touch HTTP, the socket, or the websocket client.

Summary

Types

A column's type as the importer names it: the information_schema data_type, or {:array, element_type} for an array column.

Functions

Prepares a key value (a primary key, a unique column) for a lookup in a column of SQL type type. Read by that type like value/4; by shape when type is nil.

Maps each numeric column of table_name to the term a numeric string in it should become: :decimal for numeric/decimal, :float for double precision/real. Empty map on error.

Prepares value for column, whose SQL type column_types names (%{column => information_schema data_type}). A string is parsed only as what that type holds; in any other column (text, varchar, citext, an enum, ...) it is passed as it came. numeric_cols is the map numeric_column_types/1 returns.

Types

column_type()

@type column_type() :: String.t() | {:array, String.t() | nil}

A column's type as the importer names it: the information_schema data_type, or {:array, element_type} for an array column.

Functions

drop_field(record, field)

@spec drop_field(map(), String.t()) :: map()

get_field(record, field)

@spec get_field(map(), String.t()) :: any()

key_value(value, type)

@spec key_value(any(), column_type() | nil) :: any()

Prepares a key value (a primary key, a unique column) for a lookup in a column of SQL type type. Read by that type like value/4; by shape when type is nil.

normalize_keys(record)

@spec normalize_keys(map()) :: map()

numeric_column_types(table_name)

@spec numeric_column_types(String.t()) :: %{required(String.t()) => :decimal | :float}

Maps each numeric column of table_name to the term a numeric string in it should become: :decimal for numeric/decimal, :float for double precision/real. Empty map on error.

put_field(record, field, value)

@spec put_field(map(), String.t(), any()) :: map()

value(value)

@spec value(any()) :: any()

value(value, column, column_types)

@spec value(any(), String.t(), %{required(String.t()) => :decimal | :float}) :: any()

value(list, column, numeric_cols, column_types)

@spec value(any(), String.t(), %{required(String.t()) => :decimal | :float}, %{
  required(String.t()) => column_type() | nil
}) :: any()

Prepares value for column, whose SQL type column_types names (%{column => information_schema data_type}). A string is parsed only as what that type holds; in any other column (text, varchar, citext, an enum, ...) it is passed as it came. numeric_cols is the map numeric_column_types/1 returns.

A column column_types does not name is read by shape, as value/3 does.

An array column is named {:array, element_type} and takes a list, whose elements are read by the element type (PhoenixKitSync.DataImporter.prepare_array/2). A list for any other column (a json value) is passed as it came.

A map is read as the base64 wrapper of bytes (%{"__phoenix_kit_binary__" => ...}) only in a bytea or uuid column; in any other column the type of which is known it is passed as it came, so a json object of that shape stays an object.