What happens
I set up a sink with an initial backfill on a table whose primary key includes a Postgres enum column. The backfill never makes progress — the TableReaderServer for that consumer keeps crashing (roughly once a second) and the backfill stays stuck in active.
The crash:
[error] [TableReaderServer] ID fetch task failed backfill_id=...
** (MatchError) no match of right hand side value:
{:error,
%Postgrex.Error{
postgres: %{
code: :invalid_text_representation,
message: "invalid input value for enum mood: \"\"",
routine: "enum_recv",
where: "unnamed portal parameter $2",
pg_code: "22P02"
}
}}
(sequin 0.1.0) lib/sequin/runtime/table_reader.ex:177: Sequin.Runtime.TableReader.fetch_batch_pks/4
Running v0.14.6, but the relevant code looks the same on main.
Why
The initial keyset cursor uses an empty string for any column whose type isn't uuid / number / date / time:
# lib/sequin/runtime/keyset_cursor.ex
def min_for_type(_), do: ""
casted_cursor_values/2 passes that through untouched for an enum column (cast_value(_, val), do: val), so the batch query ends up binding $2 = "". Postgres refuses "" as an enum value (enum_recv, SQLSTATE 22P02). And because fetch_batch_pks/4 matches the result with
{:ok, %Postgrex.Result{} = result} = Postgres.query(db_or_conn, sql, params, timeout: timeout)
the {:error, _} isn't handled, the task raises, and it retries forever.
Repro
CREATE TYPE mood AS ENUM ('calm', 'happy', 'sad');
CREATE TABLE t (
id uuid DEFAULT gen_random_uuid(),
m mood,
PRIMARY KEY (m, id)
);
INSERT INTO t (m) VALUES ('calm'), ('happy'), ('sad');
Point a sink at public.t with an initial backfill and it starts crash-looping right away.
Related
Same root cause, quieter symptom: with a character(n) / bpchar primary key the empty-string cursor doesn't raise, but the backfill finishes having read 0 rows — it ends up completed with rows_processed_count = 0. uuid / text / timestamp keys work fine.
Suggestion
min_for_type/1 returning "" isn't a valid minimum for a lot of column types. For an enum it could use the first label (ORDER BY enumsortorder LIMIT 1); more generally the backfill could seed the cursor from SELECT min(<sort_col>) instead of a synthetic value. And fetch_batch_pks/4 probably shouldn't hard-match the query result — right now any Postgres error there turns into a crash loop.
Can put together a PR if that'd help.
What happens
I set up a sink with an initial backfill on a table whose primary key includes a Postgres
enumcolumn. The backfill never makes progress — theTableReaderServerfor that consumer keeps crashing (roughly once a second) and the backfill stays stuck inactive.The crash:
Running v0.14.6, but the relevant code looks the same on
main.Why
The initial keyset cursor uses an empty string for any column whose type isn't uuid / number / date / time:
casted_cursor_values/2passes that through untouched for an enum column (cast_value(_, val), do: val), so the batch query ends up binding$2 = "". Postgres refuses""as an enum value (enum_recv, SQLSTATE 22P02). And becausefetch_batch_pks/4matches the result withthe
{:error, _}isn't handled, the task raises, and it retries forever.Repro
Point a sink at
public.twith an initial backfill and it starts crash-looping right away.Related
Same root cause, quieter symptom: with a
character(n)/bpcharprimary key the empty-string cursor doesn't raise, but the backfill finishes having read 0 rows — it ends upcompletedwithrows_processed_count = 0. uuid / text / timestamp keys work fine.Suggestion
min_for_type/1returning""isn't a valid minimum for a lot of column types. For an enum it could use the first label (ORDER BY enumsortorder LIMIT 1); more generally the backfill could seed the cursor fromSELECT min(<sort_col>)instead of a synthetic value. Andfetch_batch_pks/4probably shouldn't hard-match the query result — right now any Postgres error there turns into a crash loop.Can put together a PR if that'd help.