title: Unable to migrate MediaWiki from MySQL to PostgreSQL
body: <p>good people of Stack Overflow.</p>
  
  <p>I've been trying to migrate a very old installation of MediaWiki from MySQL to PostrgreSQL database, but I got to multiple dead ends. The currently running version is 1.26.1. Unfortunately, database integrity cannot be guaranteed, because the wiki was set up back in 2012 and all the db tables are running on MyISAM engine.</p>
  
  <p>The <strong>main goal</strong> is to put everything in docker with PostgreSQL while saving all of the MediaWiki contents, authors and page history.</p>
  
  <p>I've created a local copy of the database and MediaWiki on my local machine and successfully upgraded to version 1.34 (with the update.php script). Then I tried:</p>
  
  <p><strong>1. Load db using pgloader</strong></p>
  
  <p>The pgloader successfully migrated the MySQL db into PostgreSQL. Configured <code>$wgDBmwschema</code>. The <code>update.php</code> crashes.</p>
  
  <pre><code>./update.php 
  MediaWiki 1.34.1 Updater
  
  Your composer.lock file is up to date with current dependencies!
  Going to run database updates for cz_improliga_wiki-cz_improliga_wiki-
  Depending on the size of your database this may take a while!
  Abort with control-c in the next five seconds (skip this countdown with --quick) ... 3 0
  Renaming table user to mwuser
  Renaming table text to pagecontent
  ...skipping: index user_pkey doesn't exist.
  ...skipping: index user_user_name_key doesn't exist.
  ...skipping: index text_pkey doesn't exist.
  ...sequence ipblocks_ipb_id_seq already exists.
  ...sequence revision_rev_id_seq already exists.
  ...sequence text_old_id_seq already exists.
  ...sequence recentchanges_rc_id_seq already exists.
  ...sequence logging_log_id_seq already exists.
  ...sequence page_restrictions_pr_id_seq already exists.
  ...sequence uploadstash_us_id_seq already exists.
  ...sequence category_cat_id_seq already exists.
  ...category table already exists.
  ...page table already exists.
  ...querycachetwo table already exists.
  ...page_props table already exists.
  ...page_restrictions table already exists.
  Creating profiling table ...done.
  ...protected_titles table already exists.
  ...redirect table already exists.
  ...updatelog table already exists.
  ...change_tag table already exists.
  ...user_properties table already exists.
  ...log_search table already exists.
  ...l10n_cache table already exists.
  ...iwlinks table already exists.
  ...module_deps table already exists.
  ...uploadstash table already exists.
  ...user_former_groups table already exists.
  ...sites table already exists.
  ...bot_passwords table already exists.
  ...obsolete table 'archive2' does not exist
  ...column 'updatelog.ul_value' already exists
  ...column 'archive.ar_deleted' already exists
  ...column 'archive.ar_len' already exists
  ...column 'archive.ar_page_id' already exists
  ...column 'archive.ar_parent_id' already exists
  ...column 'archive.ar_content_model' already exists
  ...column 'archive.ar_content_format' already exists
  ...column 'categorylinks.cl_sortkey_prefix' already exists
  ...column 'categorylinks.cl_collation' already exists
  ...column 'categorylinks.cl_type' already exists
  ...column 'image.img_sha1' already exists
  ...column 'ipblocks.ipb_allow_usertalk' already exists
  ...column 'ipblocks.ipb_anon_only' already exists
  ...column 'ipblocks.ipb_block_email' already exists
  ...column 'ipblocks.ipb_create_account' already exists
  ...column 'ipblocks.ipb_deleted' already exists
  ...column 'ipblocks.ipb_enable_autoblock' already exists
  ...column 'ipblocks.ipb_parent_block_id' already exists
  ...column 'filearchive.fa_deleted' already exists
  ...column 'filearchive.fa_sha1' already exists
  ...column 'logging.log_deleted' already exists
  ...column 'logging.log_id' already exists
  ...column 'logging.log_params' already exists
  ...column 'mwuser.user_editcount' already exists
  ...column 'mwuser.user_newpass_time' already exists
  ...column 'oldimage.oi_deleted' already exists
  ...column 'oldimage.oi_major_mime' already exists
  ...column 'oldimage.oi_media_type' already exists
  ...column 'oldimage.oi_metadata' already exists
  ...column 'oldimage.oi_minor_mime' already exists
  ...column 'oldimage.oi_sha1' already exists
  ...column 'page.page_content_model' already exists
  ...column 'page_restrictions.pr_id' already exists
  Adding column 'profiling.pf_memory'
  Wikimedia\Rdbms\DBQueryError from line 1603 of /var/www/html/includes/libs/rdbms/database/Database.php: A database query error has occurred. Did you forget to run your application's database schema updater after upgrading? 
  Query: ALTER TABLE profiling ADD pf_memory NUMERIC(18,10) NOT NULL DEFAULT 0
  Function: Wikimedia\Rdbms\DBConnRef::query
  Error: 42701 ERROR:  column "pf_memory" of relation "profiling" already exists
  
  
  #0 /var/www/html/includes/libs/rdbms/database/Database.php(1574): Wikimedia\Rdbms\Database-&gt;getQueryExceptionAndLog('ERROR:  column ...', '42701', 'ALTER TABLE pro...', 'Wikimedia\\Rdbms...')
  #1 /var/www/html/includes/libs/rdbms/database/Database.php(1152): Wikimedia\Rdbms\Database-&gt;reportQueryError('ERROR:  column ...', '42701', 'ALTER TABLE pro...', 'Wikimedia\\Rdbms...', false)
  #2 /var/www/html/includes/libs/rdbms/database/DBConnRef.php(68): Wikimedia\Rdbms\Database-&gt;query('ALTER TABLE pro...', 'Wikimedia\\Rdbms...', 0)
  #3 /var/www/html/includes/libs/rdbms/database/DBConnRef.php(292): Wikimedia\Rdbms\DBConnRef-&gt;__call('query', Array)
  #4 /var/www/html/includes/installer/PostgresUpdater.php(931): Wikimedia\Rdbms\DBConnRef-&gt;query('ALTER TABLE pro...')
  #5 /var/www/html/includes/installer/DatabaseUpdater.php(490): PostgresUpdater-&gt;addPgField('profiling', 'pf_memory', 'NUMERIC(18,10) ...')
  #6 /var/www/html/includes/installer/DatabaseUpdater.php(454): DatabaseUpdater-&gt;runUpdates(Array, false)
  #7 /var/www/html/maintenance/update.php(205): DatabaseUpdater-&gt;doUpdates(Array)
  #8 /var/www/html/maintenance/doMaintenance.php(99): UpdateMediaWiki-&gt;execute()
  #9 /var/www/html/maintenance/update.php(277): require_once('/var/www/html/m...')
  #10 {main}
  </code></pre>
  
  <p>On the other hand, the wiki loads. Unfortunately, the encoding is incorrect making the whole Wiki unreadable and breaking links.</p>
  
  <p>This is an example of the first sentence on the main page:</p>
  
  <blockquote>
    <p>ImproWiki je projekt \304\214esk\303\251 improviza\304\215n\303\255 ligy, kter\303\275 si d\303\241v\303\241 za c\303\255l sjednotit fungov\303\241n\303\255 improviza\304\215n\303\255ch kategori\303\255, dopl\305\210ovat know-how mezi improviz\303\241tory a roz\305\241\303\255\305\231it pov\304\233dom\303\255 o impru obecn\304\233.</p>
  </blockquote>
  
  <p><strong>2. Migrate db using mediawiki_mysql2postgres.pl</strong></p>
  
  <p>I ran the script with <code>--verbose --verbose</code>, but it gets stuck with </p>
  
  <blockquote>
    <p>Too many loops!</p>
  </blockquote>
  
  <p>This is the full log output.</p>
  
  <pre><code>Locating the mysqldump executable
  Checking that mysqldump can handle our flags
  Writing file "mediawiki_upgrade.pg"
  Trying to determine database version...
  Old schema is from MediaWiki version 105
  Writing file "mediawiki_upgrade.pg"
  Reading in the Postgres schema information
    Found table mwuser
      Got a timestamp for column user_newpass_time
      Got a timestamp for column user_email_token_expires
      Got a timestamp for column user_email_authenticated
      Got a timestamp for column user_touched
      Got a timestamp for column user_registration
      Got a timestamp for column user_password_expires
    Found table actor
    Found table user_groups
      Got a timestamp for column ug_expiry
    Found table user_former_groups
    Found table user_newtalk
      Got a timestamp for column user_last_timestamp
    Found table bot_passwords
    Found table page
      Got a timestamp for column page_touched
      Got a timestamp for column page_links_updated
    Found table revision
      Got a timestamp for column rev_timestamp
    Found table revision_comment_temp
    Found table revision_actor_temp
      Got a timestamp for column revactor_timestamp
    Found table ip_changes
      Got a timestamp for column ipc_rev_timestamp
    Found table pagecontent
    Found table comment
    Found table page_restrictions
      Got a timestamp for column pr_expiry
    Found table page_props
    Found table archive
      Got a timestamp for column ar_timestamp
    Found table slots
    Found table content
    Found table slot_roles
    Found table content_models
    Found table redirect
    Found table pagelinks
    Found table templatelinks
    Found table imagelinks
    Found table categorylinks
      Got a timestamp for column cl_timestamp
    Found table change_tag_def
    Found table externallinks
    Found table langlinks
    Found table site_stats
    Found table ipblocks
      Got a timestamp for column ipb_timestamp
      Got a timestamp for column ipb_expiry
    Found table ipblocks_restrictions
    Found table image
      Got a timestamp for column img_timestamp
    Found table oldimage
      Got a timestamp for column oi_timestamp
    Found table filearchive
      Got a timestamp for column fa_deleted_timestamp
      Got a timestamp for column fa_timestamp
    Found table uploadstash
      Got a timestamp for column us_timestamp
    Found table recentchanges
      Got a timestamp for column rc_timestamp
    Found table watchlist
      Got a timestamp for column wl_notificationtimestamp
    Found table interwiki
    Found table querycache
    Found table querycache_info
      Got a timestamp for column qci_timestamp
    Found table querycachetwo
    Found table objectcache
      Got a timestamp for column exptime
    Found table logging
      Got a timestamp for column log_timestamp
    Found table log_search
    Found table job
      Got a timestamp for column job_timestamp
      Got a timestamp for column job_token_timestamp
    Found table profiling
    Found table protected_titles
      Got a timestamp for column pt_timestamp
      Got a timestamp for column pt_expiry
    Found table updatelog
    Found table category
    Found table change_tag
    Found table user_properties
    Found table l10n_cache
    Found table iwlinks
    Found table module_deps
    Found table sites
    Found table site_identifiers
  Reading in schema exception information
  Figuring out order of tables to dump
  Too many loops!
  </code></pre>
  
  <p>I tried to get more info from the <a href="https://phabricator.wikimedia.org/source/mediawiki/browse/master/maintenance/postgres/mediawiki_mysql2postgres.pl" rel="nofollow noreferrer">script itself</a>, but I'm not a Perl person.</p>
  
  <p>I also tried to figure out what are the foreign key loops in the db, but since all tables run on MyISAM, there are no Foreign Keys at all.</p>
  
  <p>Any suggestions on how to proceed to achieve the main goal? </p>