Conventions & Limitations
pgferry tries to be explicit about where it is opinionated and where it deliberately stops short of guessing.
Naming
Section titled “Naming”snake_case_identifiers = trueconverts source identifiers to PostgreSQL-stylesnake_case.snake_case_identifiers = falsekeeps names lowercased only.- Generated PostgreSQL SQL uses quoted identifiers.
Examples:
parentUserIdbecomesparent_user_idUserNamebecomesusernamewhensnake_case_identifiers = false- PostgreSQL SQL is emitted as
"app"."users"rather thanapp.users
Auto-increment and sequences
Section titled “Auto-increment and sequences”MySQL and MariaDB auto_increment, SQLite integer primary key auto-increment behavior, and MSSQL IDENTITY columns are recreated as PostgreSQL sequences after data load.
The sequence flow is:
- Create the sequence after the bulk load.
- Set it to
max(column) + 1. - Attach it as the column default.
Orphan cleanup
Section titled “Orphan cleanup”When clean_orphans = true, pgferry checks foreign-key relationships before adding PostgreSQL FKs.
Cleanup behavior:
ON DELETE SET NULLforeign keys are repaired by setting the child columns toNULL.- Other delete rules cause the orphaned child rows to be deleted.
clean_orphans_mode = "apply" (the default) actually does the work. "report" only inspects and logs — handy when you want a preview without touching rows, but the migration stops before FK creation so you can react first.
Set clean_orphans_max_rows to a positive number when you want a fuse: if the total orphan rows across all FKs would exceed that cap, pgferry aborts instead of deleting half the internet. 0 means no fuse.
Disable orphan cleanup entirely when you want FK creation to fail naturally so you can inspect the data problem yourself or fix it with before_fk hooks.
Generated columns
Section titled “Generated columns”Generated columns are copied as their materialized values. pgferry does not recreate the source expression semantics automatically.
What you get:
- the copied data values
- warnings in
planoutput - a clear signal that you may need an
after_dataorafter_allfollow-up step
Reported semantic drift
Section titled “Reported semantic drift”pgferry plan also reports schema semantics that do not necessarily block the migration but still need review because pgferry skips or leaves them behind.
Current examples include:
- skipped source defaults that cannot be recreated automatically
- source
CHECKconstraints that are not recreated - table and column comments / extended properties that are not migrated
- source partitioning metadata that is detected but not rebuilt in PostgreSQL
Treat these as cutover work items, not as noise. They are usually the difference between a load that completed and a schema that still behaves the way the source system did.
Unsupported objects and features
Section titled “Unsupported objects and features”pgferry reports these rather than silently faking them:
Source objects
Section titled “Source objects”- views
- routines or procedures
- source triggers
These are not migrated automatically. Recreate them with hooks or separate DDL.
Unsupported or skipped indexes
Section titled “Unsupported or skipped indexes”MySQL and MariaDB:
FULLTEXTSPATIALunless[postgis].enabled = true- prefix indexes
- expression indexes
SQLite:
- partial indexes
- expression indexes
MSSQL:
- XML indexes
- spatial indexes
- filtered indexes
Unsupported indexes are logged as warnings so the migration can continue safely.
Unsupported column types
Section titled “Unsupported column types”Unsupported source column types are collected up front and the migration aborts before table creation starts.
If you intentionally want a softer landing, set:
[type_mapping]unknown_as_text = trueChunking limits
Section titled “Chunking limits”Chunking only applies to tables with a single-column numeric primary key. Tables with composite, non-numeric, or missing primary keys fall back to full-table COPY.
Gaps in the numeric primary key range are fine. The chunk simply returns fewer rows.
Checkpoint behavior
Section titled “Checkpoint behavior”When resume = true, pgferry writes pgferry_checkpoint.json next to the config file.
Important details:
- writes are atomic
- progress flushes are batched
- a checkpoint is deleted automatically after a successful migration
- old or incompatible checkpoints are rejected instead of reused unsafely
pgferry checkpoint status migration.tomlprints the stored table/chunk progress and compatibility summary without connecting to any database
Source-specific caveats
Section titled “Source-specific caveats”MySQL and MariaDB
Section titled “MySQL and MariaDB”enum_modeandset_modecontrol semantic handling of enums and sets.zero_date_modecontrols how0000-00-00values are handled.- MariaDB native
uuidcolumns and JSON aliases are normalized onto PostgreSQLuuidandjson/jsonbsemantics during COPY. [postgis]enables native spatial migration only for MySQL. MariaDB should usespatial_modefallback storage modes instead._cicollations can be mapped tocitextwithci_as_citext = true.
SQLite
Section titled “SQLite”- SQLite always runs with one worker.
source_snapshot_mode = "single_tx"is unsupported.- In-memory SQLite databases are rejected.
- The source database is opened read-only.
source_schemadefaults todbo.timestampandrowversionmap tobytea, not PostgreSQL datetime types.moneyandsmallmoneymap tonumericby default.single_txrequires snapshot isolation on the source database.
What pgferry does automatically vs what you still own
Section titled “What pgferry does automatically vs what you still own”| pgferry handles | You still need to handle |
|---|---|
| schema introspection | application-specific cutover sequencing |
| table creation | views, routines, and source-trigger replacements |
| data COPY with chunking | unsupported index redesign when needed |
| PKs, indexes, FKs, sequences, optional trigger emulation | semantic recreation of generated-column expressions |
| extension checks for supported features | any validation beyond what you choose to script or configure |