/*
 *	check.c
 *
 *	server checks and output routines
 */

#include "pg_upgrade.h"


static void set_locale_and_encoding(migratorContext *ctx, Cluster whichCluster);
static void check_new_db_is_empty(migratorContext *ctx);
static void check_locale_and_encoding(migratorContext *ctx, ControlData *oldctrl,
						  ControlData *newctrl);


void
output_check_banner(migratorContext *ctx, bool *live_check)
{
	if (ctx->check && is_server_running(ctx, ctx->old.pgdata))
	{
		*live_check = true;
		if (ctx->old.port == ctx->new.port)
			pg_log(ctx, PG_FATAL, "When checking a live server, "
				   "the old and new port numbers must be different.\n");
		pg_log(ctx, PG_REPORT, "PerForming Consistency Checks on Old Live Server\n");
		pg_log(ctx, PG_REPORT, "------------------------------------------------\n");
	}
	else
	{
		pg_log(ctx, PG_REPORT, "Performing Consistency Checks\n");
		pg_log(ctx, PG_REPORT, "-----------------------------\n");
	}
}


void
check_old_cluster(migratorContext *ctx, bool live_check,
				  char **sequence_script_file_name)
{
	/* -- OLD -- */

	if (!live_check)
		start_postmaster(ctx, CLUSTER_OLD, false);

	set_locale_and_encoding(ctx, CLUSTER_OLD);

	get_pg_database_relfilenode(ctx, CLUSTER_OLD);

	/* Extract a list of databases and tables from the old cluster */
	get_db_and_rel_infos(ctx, &ctx->old.dbarr, CLUSTER_OLD);

	init_tablespaces(ctx);

	get_loadable_libraries(ctx);


	/*
	 * Check for various failure cases
	 */

	old_8_3_check_for_isn_and_int8_passing_mismatch(ctx, CLUSTER_OLD);

	/* old = PG 8.3 checks? */
	if (GET_MAJOR_VERSION(ctx->old.major_version) <= 803)
	{
		old_8_3_check_for_name_data_type_usage(ctx, CLUSTER_OLD);
		old_8_3_check_for_tsquery_usage(ctx, CLUSTER_OLD);
		if (ctx->check)
		{
			old_8_3_rebuild_tsvector_tables(ctx, true, CLUSTER_OLD);
			old_8_3_invalidate_hash_gin_indexes(ctx, true, CLUSTER_OLD);
			old_8_3_invalidate_bpchar_pattern_ops_indexes(ctx, true, CLUSTER_OLD);
		}
		else

			/*
			 * While we have the old server running, create the script to
			 * properly restore its sequence values but we report this at the
			 * end.
			 */
			*sequence_script_file_name =
				old_8_3_create_sequence_script(ctx, CLUSTER_OLD);
	}

	/* Pre-PG 9.0 had no large object permissions */
	if (GET_MAJOR_VERSION(ctx->old.major_version) <= 804)
		new_9_0_populate_pg_largeobject_metadata(ctx, true, CLUSTER_OLD);

	/*
	 * While not a check option, we do this now because this is the only time
	 * the old server is running.
	 */
	if (!ctx->check)
	{
		generate_old_dump(ctx);
		split_old_dump(ctx);
	}

	if (!live_check)
		stop_postmaster(ctx, false, false);
}


void
check_new_cluster(migratorContext *ctx)
{
	set_locale_and_encoding(ctx, CLUSTER_NEW);

	check_new_db_is_empty(ctx);

	check_loadable_libraries(ctx);

	check_locale_and_encoding(ctx, &ctx->old.controldata, &ctx->new.controldata);

	if (ctx->transfer_mode == TRANSFER_MODE_LINK)
		check_hard_link(ctx);
}


void
report_clusters_compatible(migratorContext *ctx)
{
	if (ctx->check)
	{
		pg_log(ctx, PG_REPORT, "\n*Clusters are compatible*\n");
		/* stops new cluster */
		stop_postmaster(ctx, false, false);
		exit_nicely(ctx, false);
	}

	pg_log(ctx, PG_REPORT, "\n"
		   "| If pg_upgrade fails after this point, you must\n"
		   "| re-initdb the new cluster before continuing.\n"
		   "| You will also need to remove the \".old\" suffix\n"
		   "| from %s/global/pg_control.old.\n", ctx->old.pgdata);
}


void
issue_warnings(migratorContext *ctx, char *sequence_script_file_name)
{
	/* old = PG 8.3 warnings? */
	if (GET_MAJOR_VERSION(ctx->old.major_version) <= 803)
	{
		start_postmaster(ctx, CLUSTER_NEW, true);

		/* restore proper sequence values using file created from old server */
		if (sequence_script_file_name)
		{
			prep_status(ctx, "Adjusting sequences");
			exec_prog(ctx, true,
					SYSTEMQUOTE "\"%s/%s\" --set ON_ERROR_STOP=on --port %d "
					  "-f \"%s\" --dbname template1 >> \"%s\"" SYSTEMQUOTE,
					  ctx->new.bindir, ctx->new.psql_exe, ctx->new.port,
					  sequence_script_file_name, ctx->logfile);
			unlink(sequence_script_file_name);
			pg_free(sequence_script_file_name);
			check_ok(ctx);
		}

		old_8_3_rebuild_tsvector_tables(ctx, false, CLUSTER_NEW);
		old_8_3_invalidate_hash_gin_indexes(ctx, false, CLUSTER_NEW);
		old_8_3_invalidate_bpchar_pattern_ops_indexes(ctx, false, CLUSTER_NEW);
		stop_postmaster(ctx, false, true);
	}

	/* Create dummy large object permissions for old < PG 9.0? */
	if (GET_MAJOR_VERSION(ctx->old.major_version) <= 804)
	{
		start_postmaster(ctx, CLUSTER_NEW, true);
		new_9_0_populate_pg_largeobject_metadata(ctx, false, CLUSTER_NEW);
		stop_postmaster(ctx, false, true);
	}
}


void
output_completion_banner(migratorContext *ctx, char *deletion_script_file_name)
{
	/* Did we migrate the free space files? */
	if (GET_MAJOR_VERSION(ctx->old.major_version) >= 804)
		pg_log(ctx, PG_REPORT,
			   "| Optimizer statistics is not transferred by pg_upgrade\n"
			   "| so consider running:\n"
			   "| \tvacuumdb --all --analyze-only\n"
			   "| on the newly-upgraded cluster.\n\n");
	else
		pg_log(ctx, PG_REPORT,
			   "| Optimizer statistics and free space information\n"
			   "| are not transferred by pg_upgrade so consider\n"
			   "| running:\n"
			   "| \tvacuumdb --all --analyze\n"
			   "| on the newly-upgraded cluster.\n\n");

	pg_log(ctx, PG_REPORT,
		   "| Running this script will delete the old cluster's data files:\n"
		   "| \t%s\n",
		   deletion_script_file_name);
}


void
check_cluster_versions(migratorContext *ctx)
{
	/* get old and new cluster versions */
	ctx->old.major_version = get_major_server_version(ctx, &ctx->old.major_version_str, CLUSTER_OLD);
	ctx->new.major_version = get_major_server_version(ctx, &ctx->new.major_version_str, CLUSTER_NEW);

	/* We allow migration from/to the same major version for beta upgrades */

	if (GET_MAJOR_VERSION(ctx->old.major_version) < 803)
		pg_log(ctx, PG_FATAL, "This utility can only upgrade from PostgreSQL version 8.3 and later.\n");

	/* Only current PG version is supported as a target */
	if (GET_MAJOR_VERSION(ctx->new.major_version) != GET_MAJOR_VERSION(PG_VERSION_NUM))
		pg_log(ctx, PG_FATAL, "This utility can only upgrade to PostgreSQL version %s.\n",
				PG_MAJORVERSION);

	/*
	 * We can't allow downgrading because we use the target pg_dumpall, and
	 * pg_dumpall cannot operate on new datbase versions, only older versions.
	 */
	if (ctx->old.major_version > ctx->new.major_version)
		pg_log(ctx, PG_FATAL, "This utility cannot be used to downgrade to older major PostgreSQL versions.\n");
}


void
check_cluster_compatibility(migratorContext *ctx, bool live_check)
{
	char		libfile[MAXPGPATH];
	FILE	   *lib_test;

	/*
	 * Test pg_upgrade_support.so is in the proper place.	 We cannot copy it
	 * ourselves because install directories are typically root-owned.
	 */
	snprintf(libfile, sizeof(libfile), "%s/pg_upgrade_support%s", ctx->new.libpath,
			 DLSUFFIX);

	if ((lib_test = fopen(libfile, "r")) == NULL)
		pg_log(ctx, PG_FATAL,
			   "\npg_upgrade_support%s must be created and installed in %s\n", DLSUFFIX, libfile);
	else
		fclose(lib_test);

	/* get/check pg_control data of servers */
	get_control_data(ctx, &ctx->old, live_check);
	get_control_data(ctx, &ctx->new, false);
	check_control_data(ctx, &ctx->old.controldata, &ctx->new.controldata);

	/* Is it 9.0 but without tablespace directories? */
	if (GET_MAJOR_VERSION(ctx->new.major_version) == 900 &&
		ctx->new.controldata.cat_ver < TABLE_SPACE_SUBDIRS)
		pg_log(ctx, PG_FATAL, "This utility can only upgrade to PostgreSQL version 9.0 after 2010-01-11\n"
			   "because of backend API changes made during development.\n");
}


/*
 * set_locale_and_encoding()
 *
 * query the database to get the template0 locale
 */
static void
set_locale_and_encoding(migratorContext *ctx, Cluster whichCluster)
{
	PGconn	   *conn;
	PGresult   *res;
	int			i_encoding;
	ControlData *ctrl = (whichCluster == CLUSTER_OLD) ?
	&ctx->old.controldata : &ctx->new.controldata;
	int			cluster_version = (whichCluster == CLUSTER_OLD) ?
	ctx->old.major_version : ctx->new.major_version;

	conn = connectToServer(ctx, "template1", whichCluster);

	/* for pg < 80400, we got the values from pg_controldata */
	if (cluster_version >= 80400)
	{
		int			i_datcollate;
		int			i_datctype;

		res = executeQueryOrDie(ctx, conn,
								"SELECT datcollate, datctype "
								"FROM 	pg_catalog.pg_database "
								"WHERE	datname = 'template0' ");
		assert(PQntuples(res) == 1);

		i_datcollate = PQfnumber(res, "datcollate");
		i_datctype = PQfnumber(res, "datctype");

		ctrl->lc_collate = pg_strdup(ctx, PQgetvalue(res, 0, i_datcollate));
		ctrl->lc_ctype = pg_strdup(ctx, PQgetvalue(res, 0, i_datctype));

		PQclear(res);
	}

	res = executeQueryOrDie(ctx, conn,
							"SELECT pg_catalog.pg_encoding_to_char(encoding) "
							"FROM 	pg_catalog.pg_database "
							"WHERE	datname = 'template0' ");
	assert(PQntuples(res) == 1);

	i_encoding = PQfnumber(res, "pg_encoding_to_char");
	ctrl->encoding = pg_strdup(ctx, PQgetvalue(res, 0, i_encoding));

	PQclear(res);

	PQfinish(conn);
}


/*
 * check_locale_and_encoding()
 *
 *	locale is not in pg_controldata in 8.4 and later so
 *	we probably had to get via a database query.
 */
static void
check_locale_and_encoding(migratorContext *ctx, ControlData *oldctrl,
						  ControlData *newctrl)
{
	if (strcmp(oldctrl->lc_collate, newctrl->lc_collate) != 0)
		pg_log(ctx, PG_FATAL,
			   "old and new cluster lc_collate values do not match\n");
	if (strcmp(oldctrl->lc_ctype, newctrl->lc_ctype) != 0)
		pg_log(ctx, PG_FATAL,
			   "old and new cluster lc_ctype values do not match\n");
	if (strcmp(oldctrl->encoding, newctrl->encoding) != 0)
		pg_log(ctx, PG_FATAL,
			   "old and new cluster encoding values do not match\n");
}


static void
check_new_db_is_empty(migratorContext *ctx)
{
	int			dbnum;
	bool		found = false;

	get_db_and_rel_infos(ctx, &ctx->new.dbarr, CLUSTER_NEW);

	for (dbnum = 0; dbnum < ctx->new.dbarr.ndbs; dbnum++)
	{
		int			relnum;
		RelInfoArr *rel_arr = &ctx->new.dbarr.dbs[dbnum].rel_arr;

		for (relnum = 0; relnum < rel_arr->nrels;
			 relnum++)
		{
			/* pg_largeobject and its index should be skipped */
			if (strcmp(rel_arr->rels[relnum].nspname, "pg_catalog") != 0)
			{
				found = true;
				break;
			}
		}
	}

	dbarr_free(&ctx->new.dbarr);

	if (found)
		pg_log(ctx, PG_FATAL, "New cluster is not empty; exiting\n");
}


/*
 * create_script_for_old_cluster_deletion()
 *
 *	This is particularly useful for tablespace deletion.
 */
void
create_script_for_old_cluster_deletion(migratorContext *ctx,
										char **deletion_script_file_name)
{
	FILE	   *script = NULL;
	int			tblnum;

	*deletion_script_file_name = pg_malloc(ctx, MAXPGPATH);

	prep_status(ctx, "Creating script to delete old cluster");

	snprintf(*deletion_script_file_name, MAXPGPATH, "%s/delete_old_cluster.%s",
			 ctx->output_dir, EXEC_EXT);

	if ((script = fopen(*deletion_script_file_name, "w")) == NULL)
		pg_log(ctx, PG_FATAL, "Could not create necessary file:  %s\n",
					*deletion_script_file_name);

#ifndef WIN32
	/* add shebang header */
	fprintf(script, "#!/bin/sh\n\n");
#endif

	/* delete old cluster's default tablespace */
	fprintf(script, RMDIR_CMD " %s\n", ctx->old.pgdata);

	/* delete old cluster's alternate tablespaces */
	for (tblnum = 0; tblnum < ctx->num_tablespaces; tblnum++)
	{
		/*
		 * Do the old cluster's per-database directories share a directory
		 * with a new version-specific tablespace?
		 */
		if (strlen(ctx->old.tablespace_suffix) == 0)
		{
			/* delete per-database directories */
			int			dbnum;

			fprintf(script, "\n");
			for (dbnum = 0; dbnum < ctx->new.dbarr.ndbs; dbnum++)
			{
				fprintf(script, RMDIR_CMD " %s%s/%d\n",
						ctx->tablespaces[tblnum], ctx->old.tablespace_suffix,
						ctx->old.dbarr.dbs[dbnum].db_oid);
			}
		}
		else
			/*
			 * Simply delete the tablespace directory, which might be ".old"
			 * or a version-specific subdirectory.
			 */
			fprintf(script, RMDIR_CMD " %s%s\n",
					ctx->tablespaces[tblnum], ctx->old.tablespace_suffix);
	}

	fclose(script);

#ifndef WIN32
	if (chmod(*deletion_script_file_name, S_IRWXU) != 0)
		pg_log(ctx, PG_FATAL, "Could not add execute permission to file:  %s\n",
				*deletion_script_file_name);
#endif

	check_ok(ctx);
}
