usr/src/cmd/postgres/postgresql-upgrade/pg_upgrade.sh
author Jon Tibble <meths@btinternet.com>
Mon, 04 May 2015 14:04:39 +0100
branchoi_151a
changeset 254 9c2a4ac793f0
parent 0 b34509ac961f
permissions -rw-r--r--
Bash patch catchup including shellshock

#!/bin/ksh
#
# CDDL HEADER START
#
# The contents of this file are subject to the terms of the
# Common Development and Distribution License (the "License").
# You may not use this file except in compliance with the License.
#
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
# or http://www.opensolaris.org/os/licensing.
# See the License for the specific language governing permissions
# and limitations under the License.
#
# When distributing Covered Code, include this CDDL HEADER in each
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
# If applicable, add the following below this CDDL HEADER, with the
# fields enclosed by brackets "[]" replaced with your own identifying
# information: Portions Copyright [yyyy] [name of copyright owner]
#
# CDDL HEADER END
#
# Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
# Use is subject to license terms.
#
#ident	"@(#)pg_upgrade.sh	1.4	09/01/08 SMI"

# PostgreSQL upgrade script from 8.1 -> 8.2

# Global variable definition
VERSION="0.11"

PATH=/usr/bin

PG_UPG_BASEDIR=${PG_UPG_BASEDIR:-/usr/postgres/upgrade}
PG_OLD_BINDIR=${PG_OLD_BINDIR:-${PG_UPG_BASEDIR}/81/bin}
PG_OLD_DATADIR=${PG_OLD_DATADIR:-/var/lib/pgsql/data}
PG_NEW_BASEDIR=${PG_NEW_BINDIR:-/usr/postgres/8.2}
PG_NEW_BINDIR=${PG_NEW_BINDIR:-${PG_NEW_BASEDIR}/bin}
PG_NEW_DATADIR=${PG_NEW_DATADIR:-/var/postgres/8.2/data}

PG_UPG_CFGDIR=${PG_UPG_CFGDIR:-${PG_UPG_BASEDIR}/etc}

MY_DIR="`dirname $0`"

PG_OLD_SERVER="${PG_OLD_BINDIR}/postmaster"
PG_NEW_SERVER="${PG_NEW_BINDIR}/postgres"
PG_PGDUMP="${PG_NEW_BINDIR}/pg_dumpall"
PG_PSQL="${PG_NEW_BINDIR}/psql"

TRANS_CMD="cp"

# Store work data on harddrive to protect memory outage on /tmp 
export TMPDIR=/var/tmp

function phase_start
{
  PGU_PHASE=$1
  PGU_PHASE_DESC=$2
  printf "BEGIN PHASE: %s\n" "${PGU_PHASE_DESC}"
}

function phase_end
{
  printf "END PHASE: %s\n" "${PGU_PHASE_DESC}"
  unset PGU_PHASE
  unset PGU_PHASE_DESC
}

function phase_out
{
  echo "${PGU_PHASE:-UNKNOWN}: $*"
}

function phase_errout
{
  phase_out "ERROR: $*"
}
#####################################################
# Usage info
#####################################################
function usage
{
   cat <<EOT 
PostgreSQL Upgrade script from version 8.1 to 8.2 (c)2008 Sun Microsytems
Version: ${VERSION}

usage: pg_upgrade.sh [--help|-h] [-m] [-s <old datadir>] [-d <new datdir>]

    -s <old datadir>    Default value is ${PG_OLD_DATADIR}
    -d <new datadir>    Default value is ${PG_NEW_DATADIR}
    -m                  Move datafiles (copy mode is safe and default behavior).

    Script assumes default Solaris installation layout. It
    can be changed by switches or by environment variables:

    PG_OLD_BINDIR       PostgreSQL 8.1 binaries
    PG_OLD_DATADIR      Data directory with 8.1 database cluster.
    PG_NEW_BINDIR       PostgreSQL 8.2 binaries.
    PG_NEW_DATADIR      Data directory where upgraded database will be placed.

Note: Backup database cluster before upgrade is highly recommended.

EOT
}
#####################################################
# Stop postgreSQL server
# Input: PG_PID specifies postgres server PID
#####################################################
function stop_postgres
{
  if [ ! -n "${PG_PID}" ]; then
    return 0
  fi

  phase_out "Stopping PostgreSQL PID=${PG_PID}"
  kill ${PG_PID}
  wait ${PG_PID}
  unset PG_PID

  phase_out "Stopped"

  return 0
}
#####################################################
# Start postgreSQL server
#
# Note 1: We want to control start process directly and all
# postgres messages will be redirected.
#
# Note 2: We use own configuration file to protect before
# random connection during upgrade and also we enable trust
# verification. It is not very secure but for upgrade it should
# be enough.
#
# Input: $1 - data dir path
#        $2 - postgres binary
#####################################################
function start_postgres
{
  phase_out "Starting PostgreSQL"
  if [ ! -d "$1" ] ; then
    phase_errout "Data directory [$1] does not exist."
    return 1
  fi

  if [ ! -e "$2" ] ; then
    phase_errout "PostgreSQL binary [$2] does not exist."
    return 1
  fi

  if [ -f "${PID_FILE}" ] ; then
    phase_errout "Directory is in use."
    return 1
  fi

  "${2}" -D "${1}" -c "config_file=${PG_UPG_CFGDIR}/postgresql_upg.conf" \
                   -c "hba_file=${PG_UPG_CFGDIR}/pg_hba_upg.conf" \
                   -c "unix_socket_directory=${WORK_DIR}"\
        > "${WORK_DIR}/postgres_${PGU_PHASE}.log" 2>&1 &

  PG_PID=$!

  phase_out "Waiting 5 seconds for server startup."
  sleep 5
 
  return 0
}
#####################################################
# Create relation map
# Input: $1 - suffix
#        $2 - only database list
#####################################################
function create_mapping
{
  if [ "$2x" != "YESx" ]; then
    phase_out "Creating relations mapping"
  fi

  "${PG_PSQL}" -h "${WORK_DIR}" -t -A -F "|" -c \
  "select datname, db.oid, ts.spcname from pg_database db\
     join pg_tablespace ts on ts.oid = dattablespace\
     where db.datname !='template0' order by datname" template1 > "${WORK_DIR}/dblist.$1"

  if [ $? -ne 0 ]; then
    phase_errout "Get list of database failed"
    return 1
  fi 

  if [ "$2x" = "YESx" ]; then
    return 0;
  fi

  while read DATABASE DBOID DBTBSPC;
  do   
    phase_out "... database ${DATABASE}"
    "${PG_PSQL}" -h "${WORK_DIR}" -t -A -F "|" -c \
    "select  ns.nspname || '.' || cl.relname as full_name,  cl.relfilenode, COALESCE(ts.spcname,'pg_default'), cl.reltoastrelid,\
             COALESCE(toast.reltoastidxid,0) \
       from pg_class cl join pg_namespace ns on relnamespace = ns.oid \
                        left outer join pg_tablespace ts on reltablespace = ts.oid \
	                    left outer join pg_class toast on toast.oid = cl.reltoastrelid \
       where cl.oid>16383 and cl.relkind in ('r', 'i' ) and ns.nspname != 'pg_toast' order by 1"\
       "${DATABASE}" | sort -t"|" -k1,1 > "${WORK_DIR}/map_${DBOID}.$1"

    if [ $? -ne 0 ]; then
      phase_errout "Get list of relations failed for database [${DATABASE}]"
      return 1
    fi 

    "${PG_PSQL}" -h "${WORK_DIR}" -t -A -F "|" -c \
    "select  ns.nspname, cl.relname, cl.relfilenode, cl.reltoastrelid,\
             COALESCE(toast.reltoastidxid,0) \
       from pg_class cl join pg_namespace ns on relnamespace = ns.oid \
	                    left outer join pg_class toast on toast.oid = cl.reltoastrelid \
       where cl.oid>16383 and cl.relkind in ('r', 'i' ) and ns.nspname != 'pg_toast'\
		     and cl.reltoastrelid != 0 order by 1"\
       "${DATABASE}" | sort -t"|" -k1,1  > "${WORK_DIR}/map_${DBOID}_toast.$1"

    if [ $? -ne 0 ]; then
      phase_errout "Get list of relations failed for database [${DATABASE}]"
      return 1
    fi 

  done < "${WORK_DIR}/dblist.old" # it is ok, because table mapping file uses old OID in name

  phase_out "Creating tablespaces mapping" 
  "${PG_PSQL}" -h "${WORK_DIR}" -t -A -F "|" -c \
  "select spcname, oid, spclocation from pg_tablespace \
     where spcname not like 'pg_%' order by spcname" template1  | sort -t"|" -k1,1 \
     > "${WORK_DIR}/tablespaces.$1"

  RET=$?
  if [  ${RET} -ne 0 ]; then
    phase_errout "Get list of tablespaces failed"
    return 1
  fi

  return 0
} 
#####################################################
# Check database
# Input: $1 - database list   
#####################################################
function check_database
{
  phase_out "Checking metada"
  phase_out "... databases names"

  COUNT=`"${PG_PSQL}" -h "${WORK_DIR}" -t -A -c \
     "SELECT count(*) FROM pg_database WHERE datname SIMILAR TO  E'%(''|\\\\\\x7C|\\\\\\x5C)%'" template1`
  if [ $? -ne 0 -o "${COUNT}x" != "0x" ]; then
    phase_errout "Database cluster contains unsupported name of database."
    return 1
  fi

  phase_out "... tablespaces names"
  COUNT=`"${PG_PSQL}" -h "${WORK_DIR}" -t -A -c \
     "SELECT count(*) FROM pg_tablespace WHERE spcname SIMILAR TO E'%(''|\\\\\\x7C|\\\\\\x5C)%'" template1`
  if [ $? -ne 0 -o "${COUNT}x" != "0x" ]; then
    phase_errout "Database cluster contains unsupported name of tablespace."
    return 1
  fi

  create_mapping check YES || return 1

  phase_out "... tables and schema names, datatypes"
  while read DBNAME DBOID DBTBSPC;
  do
    # Check inet/cidr datatype presense
    COUNT=`"${PG_PSQL}" -h "${WORK_DIR}" -t -A -c \
       "SELECT count(*) FROM pg_attribute WHERE atttypid IN (650,651,869,1041)\
        AND attrelid>16383" ${DBNAME}`
    if [ $? -ne 0 -o "${COUNT}x" != "0x" ]; then
      phase_errout "Database ${DBNAME} contains unsupported data type (inet or cidr)."
      return 1
    fi

    COUNT=`"${PG_PSQL}" -h "${WORK_DIR}" -t -A -c \
       "SELECT count(*) FROM pg_attribute WHERE attisdropped=true\
        AND attrelid>16383" ${DBNAME}`
    if [ $? -ne 0 -o "${COUNT}x" != "0x" ]; then
      phase_errout "Database ${DBNAME} contains dropped column(s)."
      return 1
    fi

    COUNT=`"${PG_PSQL}" -h "${WORK_DIR}" -t -A -c \
       "SELECT count(*) FROM pg_class WHERE relname SIMILAR TO E'%(''|\\\\\\x7C|\\\\\\x5C)%'" ${DBNAME}`
    if [ $? -ne 0 -o "${COUNT}x" != "0x" ]; then
      phase_errout "Database ${DBNAME} contains unsupported name of tables."
      return 1
    fi

    COUNT=`"${PG_PSQL}" -h "${WORK_DIR}" -t -A -c \
       "SELECT count(*) FROM pg_namespace WHERE nspname SIMILAR TO E'%(''|\\\\\\x7C|\\\\\\x5C)%'" ${DBNAME}`
    if [ $? -ne 0 -o "${COUNT}x" != "0x" ]; then
      phase_errout "Database ${DBNAME} contains unsupported name of schema(namespace)."
      return 1
    fi

    COUNT1=`"${PG_PSQL}" -h "${WORK_DIR}" -t -A -c \
      "SELECT COUNT(DISTINCT(ns.nspname || '.' || cl.relname)) FROM pg_class cl \
      JOIN pg_namespace ns ON relnamespace = ns.oid \
      WHERE cl.oid>16383 AND cl.relkind in ('r', 'i' ) AND ns.nspname != 'pg_toast'\
      ORDER BY 1;" ${DBNAME}`

    if [ $? -ne 0 ]; then
      phase_errout "SQL commnad execution failed."
      return 1  
    fi

    COUNT2=`"${PG_PSQL}" -h "${WORK_DIR}" -t -A -c \
      "SELECT COUNT(ns.nspname || '.' || cl.relname) FROM pg_class cl \
      JOIN pg_namespace ns ON relnamespace = ns.oid \
      WHERE cl.oid>16383 AND cl.relkind in ('r', 'i' ) AND ns.nspname != 'pg_toast'\
      ORDER BY 1;" ${DBNAME}`

    if [ $? -ne 0 -o "${COUNT1}x" != "${COUNT2}x" ]; then
      phase_errout "Database ${DBNAME} contains unsupported combination of table and schema."
      return 1  
    fi
  done < "${WORK_DIR}/dblist.check"
  return 0
}
#####################################################
# Move relation
# Input: $1 - DIR_OLD
#        $2 - RELID_OLD
#        $3 - DIR_NEW
#        $4 - RELID_NEW
#####################################################
function move_rel
{
  _DIR_OLD=$1
  _RELID_OLD=$2
  _DIR_NEW=$3
  _RELID_NEW=$4

  echo "${TRANS_CMD} ${_DIR_OLD}/${_RELID_OLD} ${_DIR_NEW}/${_RELID_NEW}" >> "${WORK_DIR}/trans.log"
  "${TRANS_CMD}" "${_DIR_OLD}/${_RELID_OLD}" "${_DIR_NEW}/${_RELID_NEW}"

  # move segments - each table is splitted to 1GB chunks, we must
  # copy all chunks with new RELID.
  for FILE in "${_DIR_OLD}/${_RELID_OLD}".*
  do
     if [ -r "${FILE}" ]; then
       SUFF=`expr match "${FILE}" '.*\.\([0-9]*\)'`
       echo "${TRANS_CMD} ${FILE} ${_DIR_NEW}/${_RELID_NEW}.${SUFF}" >> "${WORK_DIR}/trans.log"
       "${TRANS_CMD}" "${FILE}" "${_DIR_NEW}/${_RELID_NEW}.${SUFF}"
       printf "."
     fi
  done 
}
#####################################################
# Get file pathes
# Input: $1 - TABLE SPACE
#        $2 - OLD DATABASE ID
#        $3 - NEW DATABASE ID
# 
# Return: __TS_OLD - Old path
#         __TS_NEW - New path
#####################################################
function get_file_path
{
  __TABLESPACE=$1
  __DBID_OLD=$2
  __DBID_NEW=$3

   if [ "${__TABLESPACE}" = "pg_default" ]; then
     __TS_OLD="${PG_OLD_DATADIR}/base/${__DBID_OLD}"
     __TS_NEW="${PG_NEW_DATADIR}/base/${__DBID_NEW}"
    else
      read TBSPC TBSPCOID_OLD TBSPCDIR_OLD TBSPCOID_NEW TBSPCDIR_NEW <<EOF
`grep "${__TABLESPACE}" "${WORK_DIR}/tablespaces.join"`
EOF
      __TS_OLD="${TBSPCDIR_OLD}/${__DBID_OLD}"
      __TS_NEW="${TBSPCDIR_NEW}/${__DBID_NEW}"
    fi
}
#####################################################
# Move or copy all tables and index in given database
# Input: $1 - MAP FILE
#        $2 - OLD DATABASE ID
#        $3 - NEW DATABASE ID
#####################################################
function move_database
{
  _MAP_FILE=$1
  _DBID_OLD=$2
  _DBID_NEW=$3

  while read TBNAME RELID_OLD TBSPC_OLD TOASTRELID_OLD TOASTIDXID_OLD RELID_NEW TBSPC_NEW TOASTRELID_NEW TOASTIDXID_NEW;
  do
    printf "o"

	if [ "${TOASTRELID_OLD}" != "${TOASTRELID_NEW}" -o "${TOASTIDXID_OLD}" != "${TOASTIDXID_NEW}" ]; then
      phase_errout "PANIC: TOAST OIDs are not equal! Table [${TBNAME}]."
      return 1
    fi

	get_file_path "${TBSPC_OLD}" "${_DBID_OLD}" "${_DBID_NEW}"
	TS_SRC=${__TS_OLD}
	TS_DST=${__TS_NEW}
    move_rel "${TS_SRC}" "${RELID_OLD}" "${TS_DST}" "${RELID_NEW}"

    # if table has toast move it as well
    if [ "${TOASTRELID_OLD}" != "0" ]; then
      printf "+"
      move_rel "${TS_SRC}" "${TOASTRELID_OLD}" "${TS_DST}" "${TOASTRELID_NEW}"
      move_rel "${TS_SRC}" "${TOASTIDXID_OLD}" "${TS_DST}" "${TOASTIDXID_NEW}"
    fi
  done < "${_MAP_FILE}"
  return 0
}
#####################################################
# Input: $1 - DATABASE DIR
#        $2 - TOASTRELID
#        $3 - TOASTIDXID
#        $4 - MODE
#####################################################
function touch_or_remove
{
  case "${4}" in
	TOUCH)  mkdir -p -m700 "${1}"
	        touch "${1}/${2}"
	        touch "${1}/${3}";;
    REMOVE) if [ -f "${1}/${2}" ]; then
              rm "${1}/${2}"
            fi
            if [ -f "${1}/${3}" ]; then
              rm "${1}/${3}"
           fi;;
    *) phase_errout "Wrong mode!" ;;
  esac  
}
#####################################################
# Create/remove toast files
# Input: $1 - TABLE MAP FILE
#        $2 - TABLE SPACE MAP FILE
#        $3 - NEW DATABASE ID
#        $4 - MODE (TOUCH/REMOVE)
#
# Note: We need create fake file in all tablespaces, it prevents 
# PostgreSQL to create table with protected OID in another
# table space
#####################################################
function toasting
{
  _MAP_FILE=$1
  _TS_MAP_FILE=$2
  _DBID_NEW=$3
  _MODE=$4

  while read TBNAME RELID TBSPC TOASTRELID TOASTIDXID;
  do
	# Does table have a toast table?
	if [ "${TOASTRELID}" != "0" ]; then
	  # Default
      touch_or_remove "${PG_NEW_DATADIR}/base/${_DBID_NEW}" "${TOASTRELID}" "${TOASTIDXID}" "${_MODE}"
      # Other tablespaces
      while read TBSPC TBSPCOID TBSPCDIR
      do
        touch_or_remove "${TBSPCDIR}/${_DBID_NEW}" "${TOASTRELID}" "${TOASTIDXID}" "${_MODE}"
      done < "${_TS_MAP_FILE}"
    fi
  done < "${_MAP_FILE}"
  return 0
}
#####################################################
# Create/remove toast files
# Input: $1 - MAP FILE
#        $2 - DATABASE NAME
#####################################################
function retoasting
{
  _MAP_FILE=$1
  _DB_NAME=$2

  "${PG_PSQL}" -h "${WORK_DIR}" -q -f "${PG_UPG_BASEDIR}/82/share/pg_upgrade.sql" "${_DB_NAME}"
  if [ $? -ne 0 ]; then
    phase_errout "Cannot create support function in database [${_DB_NAME}]."
    return 1
  fi
	
  while read NSNAME TBNAME RELID TOASTRELID TOASTIDXID;
  do
    "${PG_PSQL}" -h "${WORK_DIR}" "${_DB_NAME}" >"/dev/null" <<EOT
\set ON_ERROR_STOP true
BEGIN TRANSACTION;
SET search_path='${NSNAME}';
DELETE FROM pg_class WHERE 
  relname LIKE 
    (SELECT 'pg_toast_' || oid || '%' FROM pg_class 
       WHERE relname='${TBNAME}' AND 
             relnamespace=(SELECT oid FROM pg_namespace WHERE nspname='${NSNAME}'))
  AND relnamespace=(SELECT oid FROM pg_namespace WHERE nspname='pg_toast');

DELETE FROM pg_type WHERE 
  typname= 
    (SELECT 'pg_toast_' || oid FROM pg_class 
       WHERE relname='${TBNAME}' AND 
             relnamespace=(SELECT oid FROM pg_namespace WHERE nspname='${NSNAME}'))
  AND typnamespace=(SELECT oid FROM pg_namespace WHERE nspname='pg_toast');

UPDATE pg_class SET reltoastrelid=0, reltoastidxid=0 
  WHERE  relname='${TBNAME}' AND 
         relnamespace=(SELECT oid FROM pg_namespace WHERE nspname='${NSNAME}');

SELECT pgupg_retoasting('${TBNAME}', ${TOASTRELID}, ${TOASTIDXID});

COMMIT;
\q
EOT

  if [ $? -ne 0 ]; then
    phase_errout "Retoasting failed on database [${_DB_NAME}] and table [${NSNAME}.${TBNAME}]."
    return 1
  fi	
  done < "${_MAP_FILE}"

  "${PG_PSQL}" -h "${WORK_DIR}" -q -c \
    "DROP FUNCTION pgupg_retoasting(IN relname name, toast oid, toastidx oid)" "${_DB_NAME}" 
  if [ $? -ne 0 ]; then
    phase_errout "Cannot drop support function in database [${_DB_NAME}]."
    return 1
  fi

  return 0
}
#####################################################
# Adjust tablespaces
# Note:
# Tablespace is usualy placed on whole partition. We
# need to keep data on same partition to save time 
# during data moving. However, postgresql cannot create
# tablespace in not empty directory. We must create fake
# empty directory on same volume and after transition put
# everything back. 
# Input: $1 - schema dump file 
#        $2 - tablespaces list 
#####################################################
function adjust_tablespaces
{
  _DUMP=$1
  _TSLIST=$2

  trap 'return 1' ERR
  
  cp "${_DUMP}" "${_DUMP}.orig"
  while read _TBSPC _TSOID _TSLOC;
  do
    mkdir "${_TSLOC}/upgrade"
    mv "${_DUMP}" "${_DUMP}.temp"
    sed  -e "s|LOCATION '${_TSLOC}';|LOCATION '${_TSLOC}/upgrade';|g" "${_DUMP}.temp" > "${_DUMP}"
    rm "${_DUMP}.temp"
  done < "${_TSLIST}"
   
  return 0
}

#####################################################
# Exit function
#####################################################
function pgu_exit
{
  RET=$?
  stop_postgres
  exit ${RET}
}
####################################################
####################################################
################### START HERE #####################
####################################################
####################################################
trap pgu_exit EXIT INT TERM

while [ $# -gt 0 ]; do
  case "$1" in
	   -h|--help)
			usage
            exit 0
			;;
        -m) TRANS_CMD="mv"
            ;;
        -s) if [ "${2}" = "" ]; then
              printf "Option -s requires argument.\n"
              exit 1
            fi
            PG_OLD_DATADIR=${2}
            shift
            ;;
        -d) if [ "${2}" = "" ]; then
              printf "Option -d requires argument.\n"
              exit 1
            fi
            PG_NEW_DATADIR=${2}
            shift
            ;;
		*)	printf "Error: Wrong argument\n\n"
            usage
            exit 1
			;;
  esac
  shift
done
#####################################################
# Init phase
#####################################################
phase_start "Init" "Initialization"

WORK_DIR=`mktemp -d -t  pg_upg.XXXXXX 2>&1`
if [ ! -d "${WORK_DIR}" ]; then 
  phase_errout "Cannot create work directory: ${WORK_DIR}"
  exit 1 
fi
phase_out "Work directory: ${WORK_DIR}" 

phase_end
#####################################################
# Check phase
#####################################################
phase_start "Check" "Upgradability check"

phase_out "Old data dir: ${PG_OLD_DATADIR}"
if [ ! -d "${PG_OLD_DATADIR}" ] ; then
  phase_errout "Old data dir does not exist." 
  exit 1
fi

phase_out "Old postgreSQL server: ${PG_OLD_SERVER}"
if [ ! -x "${PG_OLD_SERVER}" ] ; then
  phase_errout "Old PostgreSQL is not found or is not executable."
  exit 1
fi

phase_out "Command pg_dumpall: ${PG_PGDUMP}"
if [ ! -x "${PG_PGDUMP}" ] ; then
  phase_errout "pg_dumpall command is not found or is not executable."
  exit 1
fi

phase_out "Command psql: ${PG_PSQL}"
if [ ! -x "${PG_PSQL}" ] ; then
  phase_errout "psql command is not found or is not executable."
  exit 1
fi

phase_out "Check old data dir"
if [ -f "${PG_OLD_DATADIR}/postmaster.pid" ] ; then
  phase_errout "${PG_OLD_DATADIR} data directory is in use."
  exit 1
fi

phase_end
#####################################################
# Old version last run
#####################################################
phase_start "Dump" "Dump meta data from old database"

IFS="|"

start_postgres "${PG_OLD_DATADIR}" "${PG_OLD_SERVER}"
if [ $? -ne 0 ]; then
  phase_errout "Can not run old PostgreSQL server."
  exit 1
fi

check_database
if [ $? -ne 0 ]; then 
   exit 1
fi

phase_out "Dumping schema ..."
"${PG_PGDUMP}" -h "${WORK_DIR}" --schema-only > "${WORK_DIR}/schema_dump.sql"
if [ $? -ne 0 ]; then
  phase_errout "Can not dump database schema."
  exit 1
fi
awk 'BEGIN{X=0} /^\\connect/{X=X+1} X<2{print}' "${WORK_DIR}/schema_dump.sql"\
	 > "${WORK_DIR}/schema_dump_part1.sql" 
awk 'BEGIN{X=0} /^\\connect/{X=X+1} X>1{print}' "${WORK_DIR}/schema_dump.sql"\
	 > "${WORK_DIR}/schema_dump_part2.sql"

phase_out "Schema dumped to ${WORK_DIR}/schema_dump.sql"

# Create relation file map list
create_mapping "old" NO || exit 1

# Good by old version
stop_postgres

# Export pg_contol file
phase_out "Export pg_control"
# We must set C locales to correctly handle pg_control in different locale environment
( LC_ALL="C" "${PG_OLD_BINDIR}/pg_controldata" "${PG_OLD_DATADIR}" > "${WORK_DIR}/pg_control.txt" )

phase_end 
#####################################################
# Create new database cluster
#####################################################
phase_start "Create" "Create new cluster"

PG_COLLATE=`grep LC_COLLATE "${WORK_DIR}/pg_control.txt" | cut -c39-`
PG_CTYPE=`grep LC_CTYPE "${WORK_DIR}/pg_control.txt" | cut -c39-`

if [ "${PG_COLLATE}x" = "x" -o "${PG_CTYPE}x" = "x" ]; then
  phase_errout "Cannot get LC_COLLATE or LC_CTYPE. See ${WORK_DIR}/pg_control.txt"
  exit 1
fi
phase_out "Initializing cluster ..."
phase_out "LC_COLLATE=${PG_COLLATE}"
phase_out "LC_CTYPE=${PG_CTYPE}"

"${PG_NEW_BINDIR}/initdb" --lc-collate="${PG_COLLATE}"\
                          --lc-ctype="${PG_CTYPE}"\
                          "${PG_NEW_DATADIR}" > "${WORK_DIR}/initdb.out" 2>&1
if [ $? -ne 0 ] ; then
  phase_errout "Cluster initialization failed. See  ${WORK_DIR}/initdb.out for detail."
  exit 1
fi

start_postgres "${PG_NEW_DATADIR}" "${PG_NEW_SERVER}"

# Vacuum freeze
phase_out "Vacuuming"

for DATABASE in  "postgres" "template1";
do   
  phase_out "... database ${DATABASE}" 
  "${PG_PSQL}" -h "${WORK_DIR}" "${DATABASE}" > /dev/null << EOT
SET vacuum_freeze_min_age=0;
VACUUM FULL
EOT
  
  if [ $? -ne 0 ]; then
     phase_errout "Vacuum full failed"
	 exit 1
  fi
done
stop_postgres

# Now adjust transaction and chekpoints

phase_out "Copying CLOG files"
rm -rf "{PG_NEW_DATADIR}"/pg_clog/[0-9]*
cp  "${PG_OLD_DATADIR}"/pg_clog/* "${PG_NEW_DATADIR}"/pg_clog

# Note we don't support multi Xid
phase_out "Adjust control file"
PG_TLI=`grep TimeLineID "${WORK_DIR}/pg_control.txt" | cut -c39-`
PG_FILE=`grep "Current log file ID" "${WORK_DIR}/pg_control.txt" | cut -c39-`
PG_SEG=`grep "Next log file segment" "${WORK_DIR}/pg_control.txt" | cut -c39-`
PG_XID=`grep NextXID "${WORK_DIR}/pg_control.txt" | cut -c39-`
PG_OID=`grep NextOID "${WORK_DIR}/pg_control.txt" | cut -c39-`

if [ "${PG_TLI}x" = "x" -o "${PG_XID}x" = "x" -o "${PG_OID}x" = "x" \
  -o "${PG_FILE}x" = "x" -o "${PG_SEG}x" = "x" ]; then
  phase_errout "Cannot process control file. See ${WORK_DIR}/pg_control.txt"
  exit 1
fi

"${PG_NEW_BINDIR}"/pg_resetxlog  -f -o ${PG_OID} -x ${PG_XID} \
   -l "${PG_TLI},${PG_FILE},${PG_SEG}" "${PG_NEW_DATADIR}"
if [ $? -ne 0 ]; then
   phase_errout "Adjusting control file fails."
   exit 1
fi

# Save control data only for debuging
( LC_ALL="C" "${PG_NEW_BINDIR}/pg_controldata" "${PG_NEW_DATADIR}" > "${WORK_DIR}/pg_control_new.txt" )

phase_end
#####################################################
# Restore databases structure
#####################################################
phase_start "Restore" "Restore databases structures"  

start_postgres "${PG_NEW_DATADIR}" "${PG_NEW_SERVER}"

phase_out "Adjust tablespace location"
adjust_tablespaces "${WORK_DIR}/schema_dump_part1.sql" "${WORK_DIR}/tablespaces.old"
if [ $? -ne 0 ]; then
  phase_errout "Cannot setup tablespaces on new database cluster."
  exit 1
fi

phase_out "Restore metadata part 1"
"${PG_PSQL}" -h "${WORK_DIR}" -q -f "${WORK_DIR}/schema_dump_part1.sql" template1 \
  > "${WORK_DIR}/restore_1.out" 2>&1
if [ $? -ne 0 ]; then
  phase_errout "Cannot restore metadata. See ${WORK_DIR}/restore_1.out for detail."
  exit 1
fi

# Create relation file map list
create_mapping new NO || exit 1

phase_out "Generate databases mapping and tablespaces mapping."

join -j 1 -t"|" "${WORK_DIR}/dblist.old" "${WORK_DIR}/dblist.new" > "${WORK_DIR}/dblist.join"
join -j 1 -t"|" "${WORK_DIR}/tablespaces.old" "${WORK_DIR}/tablespaces.new"  > "${WORK_DIR}/tablespaces.join"

phase_out "Toast protection"
while read DBNAME DBID_OLD DBTBSPC_OLD DBID_NEW DBTBSPC_NEW;
do
  phase_out "... database ${DBNAME}"
  toasting "${WORK_DIR}/map_${DBID_OLD}.old" "${WORK_DIR}/tablespaces.new" "${DBID_NEW}" TOUCH
done < "${WORK_DIR}/dblist.join" 

phase_out "Restore metadata part 2"
"${PG_PSQL}" -h "${WORK_DIR}" -q -f "${WORK_DIR}/schema_dump_part2.sql" template1
  > "${WORK_DIR}/restore_2.out" 2>&1
if [ $? -ne 0 ]; then
  phase_errout "Cannot restore metadata. See ${WORK_DIR}/restore_2.out for detail."
  exit 1
fi
create_mapping toast NO || exit 1

# Remove fake files which protects TOAST OIDs and remove newly created TOAST files
# which they are have wrong OIDs
phase_out "Remove toast protection"
while read DBNAME DBID_OLD DBTBSPC_OLD DBID_NEW DBTBSPC_NEW;
do
  phase_out "... database ${DBNAME}"
  toasting "${WORK_DIR}/map_${DBID_OLD}.old" "${WORK_DIR}/tablespaces.new" "${DBID_NEW}" REMOVE
  toasting "${WORK_DIR}/map_${DBID_OLD}.toast" "${WORK_DIR}/tablespaces.new" "${DBID_NEW}" REMOVE
done < "${WORK_DIR}/dblist.join" 

phase_out "Retoasting"
while read DBNAME DBID_OLD DBTBSPC_OLD DBID_NEW DBTBSPC_NEW;
do
  phase_out "... database ${DBNAME}"
  retoasting "${WORK_DIR}/map_${DBID_OLD}_toast.old" "${DBNAME}"
  if [ $? -ne 0 ]; then
    phase_errout "Retoasting of database [${DBNAME}] failed."
    exit 1
  fi
done < "${WORK_DIR}/dblist.join" 

# Create relation file map list with new TOAST oid
create_mapping new NO || exit 1

 
stop_postgres

phase_end
#####################################################
# Moving 
#####################################################
phase_start "Transfering" "Transfering data from old to new database cluster"

phase_out "Transfering data files"

while read DBNAME DBID_OLD DBTBSPC_OLD DBID_NEW DBTBSPC_NEW;
do
  phase_out "Processing database ${DBNAME}  [${DBID_OLD} -> ${DBID_NEW}]"

  FILE_OLD="${WORK_DIR}/map_${DBID_OLD}.old"
  FILE_NEW="${WORK_DIR}/map_${DBID_OLD}.new"  
  join -j 1 -t"|" "${FILE_OLD}" "${FILE_NEW}" > "${WORK_DIR}/map_${DBID_OLD}.join"

  move_database "${WORK_DIR}/map_${DBID_OLD}.join" "${DBID_OLD}" "${DBID_NEW}"

  if [ $? -ne 0 ]; then
    phase_err "Transfering process fails."
    exit 1
  fi

  printf " done \n"
done < "${WORK_DIR}/dblist.join"

# Adjusting table spaces
phase_out "Adjusting tablespaces"
while read TBSPC TBSPCOID_OLD TBSPCDIR_OLD TBSPCOID_NEW TBSPCDIR_NEW
do
  phase_out "... tablespace ${TBSPC}"
  mkdir "${TBSPCDIR_OLD}/backup"
  for FILE in "${TBSPCDIR_OLD}/"*; do
    if [ "${FILE}" != "${TBSPCDIR_OLD}/backup" -a "${FILE}" != "${TBSPCDIR_NEW}" ]; then
      mv "${FILE}" "${TBSPCDIR_OLD}/backup"
    fi
  done
  mv "${TBSPCDIR_NEW}/"* "${TBSPCDIR_OLD}"
  rmdir "${TBSPCDIR_NEW}"
  rm "${PG_NEW_DATADIR}/pg_tblspc/${TBSPCOID_NEW}"
  ln -s "${TBSPCDIR_OLD}" "${PG_NEW_DATADIR}/pg_tblspc/${TBSPCOID_NEW}"
  printf "UPDATE pg_tablespace SET spclocation='${TBSPCDIR_OLD}' WHERE OID='${TBSPCOID_NEW}';\n" >> "${WORK_DIR}/ts_adj.sql"
done < "${WORK_DIR}/tablespaces.join"

# If there tablespaces adjust them
if [ -r "${WORK_DIR}/ts_adj.sql" ]; then

  start_postgres "${PG_NEW_DATADIR}" "${PG_NEW_SERVER}"

  phase_out "Adjusting catalog pg_tablespace"
  "${PG_PSQL}" -h "${WORK_DIR}" -f "${WORK_DIR}/ts_adj.sql" template1 > /dev/null
  if [ $? -ne 0 ]; then
    phase_err "Adjusting tablespace path in database fails."
  fi
  stop_postgres
fi

phase_out "Copying configuration"
for FILE in  pg_hba.conf pg_ident.conf
do
  cp "${PG_OLD_DATADIR}/${FILE}" "${PG_NEW_DATADIR}/${FILE}"
done
cp "${PG_OLD_DATADIR}/postgresql.conf" "${PG_NEW_DATADIR}/postgresql.conf.old"

phase_end

cat << EOT

WARNING: Do not forget to setup postgreSQL. Old config file
is stored in:

	${PG_NEW_DATADIR}/postgresql.conf.old

EOT