sluice

Type mapping

What your MySQL TINYINT(1) / ENUM / DECIMAL / JSON / temporal types become on Postgres (and vice versa), and on SQLite / D1 — the cross-engine translation policies.

sluice never translates one dialect straight to another. Every column type maps source-dialect → typed IR → target-dialect: source-specific knowledge lives in readers, target-specific knowledge in writers, and the IR is the only shared contract. That's why the four-direction matrix needs four readers and four writers, not twelve pairwise tables. This page is the operator-facing summary of those policies; the canonical, always-current source is docs/type-mapping.md and the runtime value contract in docs/value-types.md.

Core vs extension types #

The IR type system is a two-tier hierarchy, and the tier decides what happens on an engine that lacks a type:

Adding a new engine never amends the core; it provides the reader/writer code, and its own type dispatch IS the declaration — a missing arm refuses loudly rather than degrading silently. The orchestrator's capability questions are strategy questions (which bulk-load method, which CDC transport, which schema scope). And since v0.119.0 the target engine is asked to dry-run its own column-type emit before any data moves, so an unrenderable column is refused up front — naming the table, column and type — instead of surfacing mid-run from the target's CREATE TABLE.

MySQL ↔ Postgres #

The most-travelled direction. Notable rows below; the full table is in the canonical doc.

MariaDB reads and writes through this same MySQL mapping. As a MySQL-family flavor it uses the mysql rows here in both directions; the divergences are all on the catalog side, not the type table — MariaDB's native uuid / inet6 / inet4 types, its per-table (not per-schema) CHECK-constraint names, a geometry SRID it stores but won't echo in SHOW CREATE, and a different COLUMN_DEFAULT dialect. The MariaDB field notes cover each.
MySQLPostgresNotes
TINYINT(1)booleanThe MySQL boolean convention. A value outside {0,1} would collapse to true, so sluice REFUSES loudly (SLUICE-E-VALUE-TINYINT1-RANGE) at the first such value — a fail-fast preflight catches it before the copy — rather than silently carrying it. Remedy: change the source column type away from TINYINT(1) (e.g. MODIFY … SMALLINT), which works on every source; or, for a non-Vitess MySQL bulk migrate, --type-override col=smallint to keep the integer (smallint is the safe floor — a tinyint override could round-trip back to a boolean). The override does not apply on a PlanetScale/Vitess source, whose type comes from the replication wire.
TINYINT / SMALLINT / MEDIUMINT / INT / BIGINTsmallint / smallint / integer / integer / bigintMEDIUMINT widens to integer on PG (no 3-byte int). Signed ranks map straight across.
… UNSIGNEDwidens one ranktinyint→smallint, smallint→integer, mediumint/int→bigint. bigint unsigned → bigint (uniform): PG has no unsigned 64-bit, so values in (2^63-1, 2^64-1] aren't representable — but this is the only mapping that keeps an AUTO_INCREMENT PK and its FK children type-consistent (the default Rails/Laravel/Django schema). Surfaced by a loud range-narrowing notice at schema preview / migrate preflight; override to numeric to keep the full range (then the column can't be an identity key).
DECIMAL(p,s) / NUMERICnumeric(p,s)Carried as a string end-to-end; precision is lossless. A bare Postgres numeric (no p/s) is arbitrary-precision — PG→PG round-trips it bare; PG→MySQL widens to DECIMAL(65,30) (MySQL's max) with a loud widening notice. A value that exceeds it is refused, never rounded: past 35 integer digits MySQL refuses the write itself (Error 1264), and since v0.156.4 a value with more significant fractional digits than the target column's scale — past 30 for DECIMAL(65,30), or past a constrained DECIMAL(p,s)'s scale — refuses with SLUICE-E-VALUE-UNREPRESENTABLE marked DECIMAL-SCALE-EXCEEDED, as does a DECIMAL column's literal DEFAULT past the scale. Trailing zeros past the scale lose nothing and are not refused. To carry every digit, --type-override the column to a text type; otherwise round on the source. See values past the scale for the repair on a MySQL target written by an earlier release.
FLOAT / DOUBLEreal / double precisionFinite values ride through exactly. NaN/±Inf are Postgres-only, so PG→MySQL refuses them loudly with SLUICE-E-VALUE-UNREPRESENTABLE (MySQL has no non-finite floats) rather than coerce them; MySQL→PG never produces one.
CHAR(n) / VARCHAR(n) / TINY..LONGTEXTchar(n) / varchar(n) / textA PG varchar(N) above MySQL's representable cap down-maps to the smallest MySQL TEXT-family type, with a loud advisory. Charset/collation are carried same-engine, dropped-with-WARN cross-engine (collation names aren't portable). Values are always carried as UTF-8; a column in a non-UTF-8 charset is converted on the change stream too (v0.156.3) — see non-UTF-8 character sets.
DATE / TIME(p) / DATETIME(p) / TIMESTAMP(p)date / time(p) / timestamp(p) / timestamptz(p)MySQL TIMESTAMP always stores UTC → PG timestamptz. A bare PG time/timestamp (no precision) round-trips bare PG→PG but materializes (6) on a MySQL target. A PG timetz → MySQL drops the zone (MySQL has no tz-aware time). Zero/partial MySQL dates (0000-00-00) are refused unless --zero-date=null|epoch (SLUICE-E-VALUE-ZERO-DATE).
ENUM('a','b')enum type (default) or text + CHECKDefault emits a PG CREATE TYPE … AS ENUM; per-column override for text + a CHECK constraint. A PG enum → MySQL becomes a column-level ENUM(...) (no shared type; each column gets its own).
SET('a','b')text[] + CHECKMembership preserved via a CHECK; override to a comma-delimited text.
JSONjsonb (default) / jsonMySQL JSON and PG jsonb both validate + normalise; PG json (no b) preserves whitespace/key order. Carried as raw bytes.
(no MySQL type)uuidPG uuid → MySQL CHAR(36) / BINARY(16).
JSON (degraded)T[] (array)MySQL has no array type: a PG array → MySQL JSON (empty {}→[], NULL element→JSON null, nested preserved). No array-specific override exists: to land a column as parseable text instead, force it with --type-override TABLE.COL=text. Multi-dimensional arrays are pinned per element family — see the field note on the pgx codec that silently flattened numeric[][].
VARCHAR(45/30)inet / cidr / macaddrPG network types have no MySQL native form: inet/cidr→VARCHAR(45), macaddr→VARCHAR(30) (auto-shaped since v0.7.0; overridable).
spatial typesgeometry (PostGIS)Requires PostGIS on the target via --enable-pg-extension; carried as WKB. Every subtype/SRID preserved. Toward MySQL: a Z/M-dimensional column refuses at preflight since v0.124.0 (MySQL 8 has no Z/M geometry — previously the copy aborted mid-run on the server's raw 1416), a 2D geography lands as planar geometry (bytes + SRID exact) with the geodesic→planar flatten surfaced as a schema preview note, and a value with NaN/Inf coordinates (incl. POINT EMPTY) refuses with SLUICE-E-VALUE-UNREPRESENTABLE.

Decimal values past the target scale: DECIMAL-SCALE-EXCEEDED (v0.156.4) #

An unconstrained Postgres numeric lands on a MySQL-family target as DECIMAL(65,30), MySQL's widest fixed-point form: 35 integer digits and 30 fractional digits. Past 35 integer digits MySQL refuses the write itself (Error 1264). Past 30 fractional digits it does not: MySQL and MariaDB round the value and report only Note 1265, which even strict sql_mode accepts. The bulk copy (LOAD DATA and the batched INSERT path) was always loud, because it refuses a strict-mode write whose warning list is non-empty; the CDC applier never read that list. So through v0.156.3, sync (log-based or postgres-trigger), chain restore, sync from-backup and the added-column backfill stored such values rounded, at exit 0 — measured on MySQL 8.0.46 and MariaDB 11.4: 0.1234567890123456789012345678901 landed as 0.123456789012345678901234567890, and -0.99999999999999999999999999999999 as -1.000000000000000000000000000000. A DECIMAL column's literal DEFAULT past the scale was stored rounded the same way on CREATE TABLE and on a forwarded ADD COLUMN.

Since v0.156.4 sluice refuses any DECIMAL value, and any DECIMAL column's literal DEFAULT, whose significant fractional digits exceed the target column's scale — before the driver sees it, with SLUICE-E-VALUE-UNREPRESENTABLE marked DECIMAL-SCALE-EXCEEDED, naming the column and the digits lost. It applies to a constrained DECIMAL(p,s) column too, and to a float column retyped to a narrower DECIMAL with --type-override. Trailing zeros past the scale lose nothing and are not refused (1.5000…0 lands as 1.5). To proceed, round the value on the source if the digits are not needed, or --type-override the column to a text type to carry every digit. numeric[] values land in a JSON column as strings and were always exact.

If a MySQL, MariaDB or PlanetScale/Vitess target was written by the CDC applier from a Postgres source or chain before v0.156.4 (sync, chain restore or sync from-backup), compare its numeric-sourced DECIMAL columns against the source and re-copy the affected rows: a value with more fractional digits than the target scale is stored rounded, and upgrading does not find or fix it. A target DEFAULT taken from such a source column holds the rounded value — re-declare it from the source. Remaining, stated: a value for a column the applier's descriptor map does not know at all (a cold cache) is not checked. A Postgres target's numeric(p,s) also rounds over-scale input without notice, reachable only through a narrowing --type-override or a pre-existing narrower target column.

Non-ASCII text in MySQL-family DEFAULTs and expressions #

Row values never pass through the catalog, so they were never affected by what follows. Schema text does: sluice builds the target's DEFAULTs, generated columns, CHECK constraints and functional indexes from the source catalog, and both MySQL's and MariaDB's information_schema render some non-ASCII text unfaithfully. sluice recovers it; where it cannot recover it faithfully, it refuses the schema read, naming the object, rather than guessing.

If you migrated or synced such a schema before v0.156.2, a non-ASCII expression DEFAULT, generated column, CHECK or functional index may be garbled on the target — and a garbled generated-column expression makes the target compute wrong values for every row. Compare each against the source's SHOW CREATE TABLE, re-declare those that differ, and recompute a generated column whose expression was wrong (a STORED column keeps its wrong values until re-declared). v0.156.1's notes said a character DEFAULT beyond the BMP reaches the target intact; that was true of literal defaults only. A mydumper dump of a MySQL source carries each expression as SHOW CREATE TABLE printed it, so a character beyond the BMP in one is already ???? in the dump and cannot be recovered from it.

Columns in a non-UTF-8 character set: the change stream (v0.156.3) #

A legacy schema's CHAR/VARCHAR/TEXT columns are often declared latin1, cp1251, sjis, gbk, utf16 or another non-UTF-8 charset. They map to the same IR string type as a utf8mb4 column, and the value is always carried as UTF-8. The bulk copy (migrate, a sync cold start, a backup full) never sees the stored bytes: it reads over a utf8mb4 connection, so the server converts every value first. The change stream does see them — the binlog row image (MySQL, MariaDB) and the VStream row event (PlanetScale / Vitess) both carry a character column's value as the bytes stored in its own charset. Since v0.156.3 sluice converts those bytes by the column's charset, so sync, backup stream and backup incremental carry what the bulk copy carries: every 8-bit charset, latin1 as MySQL's cp1252 variant, the Japanese, Korean and GB charsets, the UTF-16/UTF-32/UCS-2 family, and swe7 (not ASCII-compatible: { is ä). Each conversion table is graded against the server's own CONVERT(… USING utf8mb4) over its whole code space on MySQL 8.0 and MariaDB 11.4. Keys and before-images are converted too, so a row updated or deleted by a non-ASCII text key is found on the target. A PlanetScale/Vitess column with a _bin collation (latin1_bin, utf8mb4_bin, …) arrives typed VARBINARY/BLOB/BINARY and is decoded as text by its collation; the declared column type decides, so a true binary column stays bytes.

What cannot be converted faithfully refuses with CHARSET-NOT-DECODABLE, naming the table, column and charset (the value is not printed):

Pure-ASCII values in those columns stream normally. To proceed past the refusal, convert the column to utf8mb4 on the source and re-snapshot the table (sync … --restart-from-scratch), or exclude the table. Do not convert and resume: the stream replays history written in the old charset.

A charset change while the stream is behind it. A replay of history recorded before an ALTER … CHARACTER SET (a warm resume after downtime, a lagging stream, a cold-start handoff whose copy spanned the ALTER) carries bytes in the old charset.

Windows that remain — on MariaDB NO_LOG and PlanetScale / Vitess only. For a change from utf8mb4 into a non-UTF-8 charset these are silent where v0.156.2 was exact (it carried a utf8mb4 value's stored bytes, which are its UTF-8); for a change between two non-UTF-8 charsets they are silent where v0.156.2 was loud or silent by the bytes. If a stream was behind such a change, re-snapshot (or take a fresh full backup) yourself:
  • a sync restart between the replayed rows and the ALTER — the next run reaches the ALTER with no decoded rows to compare, and the backup window check does not reach sync;
  • a backup window that decoded the replayed rows after an earlier window had already recorded the charset change (its start and end schema agree);
  • a second charset change to a table the same reader session already crossed an ALTER on;
  • a MODIFY/CHANGE that changes a column's charset implicitly (no CHARACTER SET/COLLATE, taking the table default), and a table-default change (ALTER TABLE … DEFAULT CHARSET);
  • an online schema change — gh-ost, pt-online-schema-change, PlanetScale deploy requests and Vitess online DDL — which swaps in a shadow table with a RENAME and never streams an ALTER … CHARACTER SET on the table;
  • an unclassifiable statement that names no charset keyword at all.
False positives are loud: an ALTER setting a non-UTF-8 column to the charset and collation it already has, a replayed collation-only change on such a column, and a charset ALTER the parser cannot classify on a table with non-UTF-8 columns all refuse, live or not. On MariaDB, set binlog_row_metadata=MINIMAL (or FULL) so each row event records the charset it was written in; it applies only to binlog events written after the change — history already written stays unrecorded.
If you streamed or backed up such a column on v0.156.2 or earlier. The bulk copy was always exact; the change stream carried each value as its raw stored bytes. backup stream / backup incremental recorded every non-ASCII value of such a column as � (U+FFFD) or as a different character, silently — take a fresh full backup with v0.156.3; chain restore, sync from-backup and backup verify now WARN with LEGACY-CHARSET-INCREMENT naming each affected incremental's table and columns. Under sync, a value whose raw bytes happened to be valid UTF-8 landed as a different character at exit 0 (a latin1 é as é; every non-ASCII swe7 value), and a later UPDATE or DELETE keyed on such a value never found its row; the rest failed loudly at the target. Compare those columns against the source for rows changed after cutover and repair from the source, or re-copy the table (--restart-from-scratch). Full operator detail: docs/operator/migrating-legacy-mysql.md in the sluice repo.

SQLite & Cloudflare D1 #

SQLite (and D1, which is SQLite over HTTP) is the one engine whose value storage isn't pinned by its column declaration — a column has a type affinity, and each stored value carries its own storage class. sluice resolves an IR type from the declared type in a load-bearing order: declared temporal / bool spellings win first, affinity second.

SQLite declared / affinityIR → typical targetNotes
DATE / DATETIME·TIMESTAMP / TIMEdate / timestamp (no tz) / timeDeclared spelling overrides affinity (they'd otherwise read as NUMERIC decimals). The value encoding is an operator choice — --sqlite-date-encoding (iso default / unixepoch / unixmillis / julian); a storage-class mismatch is refused loudly, naming the row.
BOOL / BOOLEANbooleanDecodes 0/1 and truthy text; anything else is refused.
INTEGER affinitybigintSQLite integers are 64-bit signed. Integers above 253 round-trip exactly via the (typeof, text/hex) projection (the lossless live-D1 reader path).
TEXT affinitytextUnbounded — declared VARCHAR(n) lengths aren't enforced by SQLite, so no misleading bound is carried.
REAL affinitydouble precision8-byte IEEE-754.
NUMERIC affinityunconstrained numericArbitrary precision.

As a migrate target, SQLite emits the declared type its reader reads back to the same IR type. The one load-bearing wrinkle: an ir.Decimal is stored with TEXT affinity (the exact decimal string), not NUMERIC — NUMERIC affinity would coerce 19.99 to the binary float 19.989999999999998 and silently corrupt money (Bug 162); it reads back as text (a documented downgrade). Anything SQLite has no faithful storage for — geometry, inet/cidr/macaddr, bit, interval, array, domain — is refused loudly at emit time, never coerced to a silently-wrong column. D1 is not a write target: emit a SQLite .db (--target-driver sqlite) and wrangler d1 import it.

Column-fidelity markers you will see in the log #

Some schema facts have no spelling on the target, or have one that only some paths can use. sluice never drops one silently: each carries a grep-stable marker on a WARN line — or, where carrying on would land a wrong value, on the refusal that stops the run — so you can search a run's logs for it.

MarkerWhat it means
IDENTITY-ALWAYS-DOWNGRADEDA Postgres GENERATED ALWAYS AS IDENTITY column was created on the target as GENERATED BY DEFAULT AS IDENTITY, so the bulk copy can land the source's ids (an ALWAYS column refuses an explicit value with SQLSTATE 428C9). One WARN per run, naming every affected table.column. sync cold start, restore and chain restore keep BY DEFAULT deliberately — each hands the target to a change applier that writes explicit ids, which ALWAYS would refuse — as do a shard-consolidation and a --force-cold-start migrate, which are not the last bulk load into the table. On those paths the target accepts application-supplied ids the source rejected; the remedy after the last explicit-id write is ALTER TABLE … ALTER COLUMN … SET GENERATED ALWAYS per named column.
IDENTITY-ALWAYS-RESTOREDmigrate restored ALWAYS after the copy and the constraints phase, on the tables it created itself this run. A table that already existed on the target (the schema preview → apply-yourself flow) is never altered and stays BY DEFAULT.
IDENTITY-OPTIONS-NOT-CARRIEDToward MySQL or SQLite, neither the ALWAYS/BY DEFAULT mode nor the identity sequence's options exist; one WARN per column, and the column lands as a plain AUTO_INCREMENT / rowid alias that accepts explicit values and steps by one (MySQL: by the server's auto_increment_increment). Toward a Postgres target the options — START, INCREMENT, MINVALUE, MAXVALUE, CACHE, CYCLE — are carried exactly on every path.
GENERATED-VIRTUAL-PROMOTED-TO-STOREDA VIRTUAL generated column (a PostgreSQL 18 source, or a MySQL VIRTUAL column) landed STORED on the target: the invariant survives, the storage tradeoff changes. It stays VIRTUAL on a Postgres target at 18 or newer; it is promoted on an older target, and on PG 18+ for a column the source indexes (MySQL allows that; PostgreSQL 18 refuses CREATE INDEX on a virtual generated column) so the index can build.
INDEX-COLLATION-DROPPEDA SQLite/D1 index column's non-BINARY collation (NOCASE, RTRIM) was dropped on a Postgres or MySQL target, naming the columns. Only ever on a non-unique index, where the collation changes ordering and cost rather than which rows are legal — on a PRIMARY KEY, a UNIQUE or a constraint-backed unique the migration is refused at preflight instead, before any data moves. See Keys, indexes, and index collation.
INERT-FLAGYou passed a tuning flag the resolved engine pair never reads — --bulk-parallelism on a Vitess-source sync start, --max-target-connections on a SQLite/D1 target. Before ADR-0118 such a flag parsed, nothing consumed it, and you got the unchanged behaviour with no signal. One WARN naming the flag, the command, the engine it is inert on and why — with the knob that applies instead where one exists. Nothing is refused.
LOB-DEFAULT-NOT-CARRIEDA DEFAULT on a TEXT, BLOB, JSON or GEOMETRY column that a MySQL-family target cannot hold. Since v0.156.1 sluice carries such a DEFAULT to a MySQL 8.0.13+ (PlanetScale and Vitess included) or MariaDB target in the parenthesised form both accept — DEFAULT ('txt'), DEFAULT (X'00AB00'), DEFAULT ('{"a": 1}') — translated per value family; through v0.156.0 every such DEFAULT was dropped. What still cannot be carried — any such DEFAULT on a MySQL older than 8.0.13 or whose version could not be read, a geometry literal, an array of an unsupported element type, a bytes literal whose encoding is ambiguous — is handled by where it appears. On CREATE TABLE (migrate, a sync cold start, restore) the column is created without it and this WARN names the table, column and reason; the copied rows carry their own values, so only later inserts that omit the column are affected. A forwarded ADD COLUMN is instead refused before the target ALTER with SLUICE-E-VALUE-UNREPRESENTABLE naming this marker, because the target would fill every row it already holds with NULL where the source holds the default: add the column on the target yourself with the right value for the existing rows, then resume.
DECIMAL-SCALE-EXCEEDED (refusal)Since v0.156.4: a DECIMAL value — or a DECIMAL column's literal DEFAULT, on CREATE TABLE and a forwarded ADD COLUMN alike — with more significant fractional digits than the MySQL, MariaDB or PlanetScale/Vitess target column's scale, typically a Postgres unconstrained numeric past 30 decimal places landing in DECIMAL(65,30). MySQL and MariaDB would round it and report only Note 1265, which even strict sql_mode accepts; sluice refuses before the driver sees it with SLUICE-E-VALUE-UNREPRESENTABLE, naming the column and the digits lost. Trailing zeros past the scale are not refused. Round on the source if the digits are not needed, or map the column to a text type with --type-override. See values past the scale.
ENUM-LABEL-NOT-RECOVERABLE (refusal)A MySQL, MariaDB or PlanetScale/Vitess ENUM/SET label holding a character outside the Basic Multilingual Plane (an emoji). The server keeps the label intact in the table, but every catalog surface — information_schema, SHOW CREATE TABLE, mysqldump — writes that character as ?, so the target's type is created with '?b' (a schema-read WARN says so). The copy fails loudly at the row's INSERT. CDC (sync, backup stream) names ENUM positions and SET bits through the catalog, so since v0.156.1 a row using such a label stops the stream with this marker, naming the table, column and label — through v0.156.0 it landed as '?b', silently. On a binlog source running binlog_row_metadata=FULL the true labels are read from each row event and the stream carries on; VStream always refuses. A label that genuinely is '?' on a utf8mb4/utf16/utf32 column is refused the same way. Recovery: SET PERSIST binlog_row_metadata = 'FULL' (and widen the target column or rename the labels, since the target's type still holds '?b'), rename the source labels to BMP characters, or --type-override=TABLE.COL=text. If you streamed such a column on an earlier release, compare it against the source.
CHARSET-NOT-DECODABLE (refusal)A MySQL-family change stream (binlog or VStream) met a value in a non-UTF-8 character column that it cannot convert to UTF-8 faithfully: a non-ASCII big5 value; on PlanetScale/Vitess, a non-ASCII value in a gbk, big5, tis620 or gb18030 column (vttablet sends those with collation 0); the tis620 bytes MySQL maps to U+FFFD; or a PlanetScale/Vitess ENUM/SET cell that is a member under both readings. Names the table, column and charset, not the value. Remedy: convert the column to utf8mb4 on the source and re-snapshot (--restart-from-scratch) — never convert and resume — or exclude the table. See non-UTF-8 character sets.
CHARSET-HISTORY-UNRECORDEDA WARN, once per table with non-UTF-8 text columns, on a source whose change stream records no written charset — MariaDB under the default binlog_row_metadata=NO_LOG, and PlanetScale/Vitess. A replay across a charset change there is decoded by the column's current charset; a replay across an ALTER into a non-UTF-8 charset refuses with SLUICE-E-CDC-SCHEMA-REPLAY-MISMATCH when the stream reaches the ALTER (sync: re-snapshot the table; backups: take a fresh full). The marker also rides the refusal (with SLUICE-E-CDC-SCHEMA-REPLAY-MISMATCH) that a backup incremental / backup stream window gives, before it commits, when the column's charset changed into a non-UTF-8 charset between the window's start and end schema while its rows were decoded by the new one. Some windows stay silent — see non-UTF-8 character sets. On MariaDB, binlog_row_metadata=MINIMAL records charsets for events written after it is set.
LEGACY-CHARSET-INCREMENTA WARN (the command carries on) from chain restore, sync from-backup and backup verify, for each incremental that sluice v0.156.2 or earlier captured from a MySQL-family source with a non-UTF-8 text column, naming the table and columns. That incremental may hold each non-ASCII value it changed in those columns as � or as a different character, and the chain cannot give the value back: take a fresh full backup with v0.156.3 or later and repair rows restored from the old chain against the source. Restoring a bare full and export-as-parquet read no incremental and do not WARN.
BACKUP-VALUE-NOT-UTF8 (refusal)backup full, backup incremental or backup stream met a text value that is not valid UTF-8 — a SQLite TEXT value written from raw bytes, a Postgres SQL_ASCII database, legacy invalid bytes in a MySQL utf8mb4 column. Backup chunks are JSON, and through v0.156.2 such a value was written as � at exit 0. Names the table, column, change image and nested position; backup stream stops rather than retrying, and no manifest is committed for the refused window. Remedy: repair the value at the source, or store it as BLOB/bytea. Meanwhile backup full can skip it with --exclude-table or --redact <table>.<column>=null; backup incremental and backup stream have neither and stop until the source is repaired. See Backups.

Per-column overrides #

The default policies cover the common case; override per column in YAML (mappings:) or on the CLI. Overrides are typed against the IR, not dialect syntax:

Run sluice schema preview first to see the exact target DDL sluice would emit, including every widening/narrowing advisory and any untranslatable-expression refusal — before touching the target.