defmodule Electric.Postgres.Inspector.DirectInspector do @behaviour Electric.Postgres.Inspector @doc """ Returns the PG relation from the table name. """ def load_relation(table, conn) do # The extra cast from $1 to text is needed because of Postgrex' OID type encoding # see: https://github.com/elixir-ecto/postgrex#oid-type-encoding query = """ SELECT nspname, relname, pg_class.oid FROM pg_class JOIN pg_namespace ON relnamespace = pg_namespace.oid WHERE relkind = 'r' AND pg_class.oid = $1::text::regclass """ case Postgrex.query(conn, query, [table]) do {:ok, result} -> # We expect exactly one row because the query didn't fail # so the relation exists since we could cast it to a regclass [[schema, table, oid]] = result.rows {:ok, %{relation_id: oid, relation: {schema, table}}} {:error, err} -> {:error, Exception.message(err)} end end @doc """ Load table information (refs) from the database """ def load_column_info({namespace, tbl}, conn) do query = """ SELECT attname as name, (atttypid, atttypmod) as type_id, attndims as array_dimensions, atttypmod as type_mod, attnotnull as not_null, pg_type.typname as type, elem_pg_type.typname as array_type, -- type of the element inside the array or nil if it's not an array format_type(pg_attribute.atttypid, pg_attribute.atttypmod) AS formatted_type, array_position(indkey, attnum) as pk_position FROM pg_class JOIN pg_namespace ON relnamespace = pg_namespace.oid JOIN pg_attribute ON attrelid = pg_class.oid AND attnum >= 0 JOIN pg_type ON atttypid = pg_type.oid LEFT JOIN pg_index ON indrelid = pg_class.oid AND indisprimary LEFT JOIN pg_type AS elem_pg_type ON pg_type.typelem = elem_pg_type.oid WHERE relname = $1 AND nspname = $2 AND relkind = 'r' ORDER BY pg_class.oid, attnum """ result = Postgrex.query!(conn, query, [tbl, namespace]) if Enum.empty?(result.rows) do # Fixme: this is not necessarily true. The table might exist but have no columns. :table_not_found else columns = Enum.map(result.columns, &String.to_atom/1) rows = Enum.map(result.rows, fn row -> Enum.zip(columns, row) |> Map.new() end) {:ok, rows} end end def clean(_, _), do: true end