sub require_postgres
{
return if ($require_postgres++);
$postgresql::use_global_login = 1;
&foreign_require("postgresql", "postgresql-lib.pl");
%qconfig = &foreign_config("postgresql");
}

# check_postgres_clash(&domain, [field])
# Returns 1 if some PostgreSQL database already exists
sub check_postgres_clash
{
if (!$_[1] || $_[1] eq 'db') {
	&require_postgres();
	local @dblist = &postgresql::list_databases();
	return 1 if (&indexof($_[0]->{'db'}, @dblist) >= 0);
	}
if (!$_[0]->{'parent'} && (!$_[1] || $_[1] eq 'user')) {
	# Check for user clash, or special user
	return 1 if (&postgres_user_exists($_[0]) ? 1 : 0);
	}
return 0;
}

# postgres_user_exists(&domain)
# Returns 1 if some user exists in PostgreSQL
sub postgres_user_exists
{
&require_postgres();
local $user = &postgres_user($_[0]);
local $s = &postgresql::execute_sql($qconfig{'basedb'}, "select * from pg_shadow where usename = '$user'");
return $s->{'data'}->[0] ? 1 : 0;
}

# setup_postgres(&domain, [no-dbs])
# Create a new PostgreSQL database and user
sub setup_postgres
{
&require_postgres();
local $tmpl = &get_template($_[0]->{'template'});
local $user = $_[0]->{'postgres_user'} = &postgres_user($_[0]);
if (!$_[0]->{'parent'}) {
	&$first_print($text{'setup_postgresuser'});
	local $pass = &postgres_pass($_[0]);
	&postgresql::execute_sql_logged($qconfig{'basedb'}, "create user \"$user\" with password $pass nocreatedb nocreateuser");
	&$second_print($text{'setup_done'});
	}
if (!$_[1] && $tmpl->{'mysql_mkdb'}) {
	# Create the initial DB
	my %opts;
	if ($tmpl->{'postgres_encoding'} &&
	    $tmpl->{'postgres_encoding'} ne 'none') {
		$opts{'encoding'} = $tmpl->{'postgres_encoding'};
		}
	&create_postgres_database($_[0], $_[0]->{'db'}, \%opts);
	}
else {
	# No DBs can exist
	$_[0]->{'db_postgres'} = "";
	}

# Save the initial password
if ($tmpl->{'postgres_nopass'}) {
	&set_postgres_pass(&postgres_pass($d, 1));
	}
}

# set_postgres_pass(&domain, [password])
# Updates a domain object to use the specified login for PostgreSQL. Does not
# actually change the database - that must be done by modify_postgres.
sub set_postgres_pass
{
local ($d, $pass) = @_;
if (defined($pass)) {
	$d->{'postgres_pass'} = $pass;
	}
else {
	delete($d->{'postgres_pass'});
	}
}

# postgres_pass(&domain, [neverquote])
sub postgres_pass
{
if ($_[0]->{'parent'}) {
	# Password comes from parent domain
	local $parent = &get_domain($_[0]->{'parent'});
	return &postgres_pass($parent);
	}
&require_postgres();
local $pass = defined($_[0]->{'postgres_pass'}) ? $_[0]->{'postgres_pass'}
						: $_[0]->{'pass'};
return !$_[1] && &postgresql::get_postgresql_version() >= 7 ? "'$pass'" : $pass;
}

# modify_postgres(&domain, &olddomain)
# Change the PostgreSQL user's password if needed
sub modify_postgres
{
&require_postgres();
local $changeduser = $_[0]->{'user'} eq $_[1]->{'user'} ? 0 : 1;
local $user = &postgres_user($_[0], $changeduser);
local $olduser = &postgres_user($_[1]);
local $tmpl = &get_template($_[0]->{'template'});

local $pass = &postgres_pass($_[0]);
local $oldpass = &postgres_pass($_[1]);
if ($pass ne $oldpass && !$_[0]->{'parent'} &&
    (!$tmpl->{'mysql_nopass'} || $_[0]->{'mysql_pass'})) {
	# Change PostgreSQL password ..
	&$first_print($text{'save_postgrespass'});
	if (&postgres_user_exists($_[1])) {
		&postgresql::execute_sql_logged($qconfig{'basedb'}, "alter user \"$olduser\" with password $pass");
		&$second_print($text{'setup_done'});
		}
	else {
		&$second_print($text{'save_nopostgres'});
		}
	}
if (!$_[0]->{'parent'} && $_[1]->{'parent'}) {
	# Server has been converted to a parent .. need to create user, and
	# change database ownerships
	local $user = $_[0]->{'postgres_user'} = &postgres_user($_[0]);
	&$first_print($text{'setup_postgresuser'});
	local $pass = &postgres_pass($_[0]);
	&postgresql::execute_sql_logged($qconfig{'basedb'}, "create user \"$user\" with password $pass nocreatedb nocreateuser");
	if (&postgresql::get_postgresql_version() >= 8.0) {
		foreach my $db (&domain_databases($_[0], [ "postgres" ])) {
			&postgresql::execute_sql_logged($qconfig{'basedb'}, "alter database \"$db->{'name'}\" owner to \"$user\"");
			}
		}
	&$second_print($text{'setup_done'});
	}
elsif ($_[0]->{'parent'} && !$_[1]->{'parent'}) {
	# Server has changed from parent to sub-server .. need to remove the
	# old user and update all DB permissions
	&$first_print($text{'save_postgresuser'});
	if (&postgresql::get_postgresql_version() >= 8.0) {
		foreach my $db (&domain_databases($_[0], [ "postgres" ])) {
			&postgresql::execute_sql_logged($qconfig{'basedb'}, "alter database \"$db->{'name'}\" owner to \"$user\"");
			}
		}
	if (&postgres_user_exists($_[1])) {
		&postgresql::execute_sql_logged($qconfig{'basedb'}, "drop user \"$olduser\"");
		}
	&$second_print($text{'setup_done'});
	}
elsif ($user ne $olduser && !$_[0]->{'parent'}) {
	# Rename PostgreSQL user ..
	&$first_print($text{'save_postgresuser'});
	if (&postgres_user_exists($_[1])) {
		if (&postgresql::get_postgresql_version() >= 7.4) {
			# Can use proper rename command
			&postgresql::execute_sql_logged($qconfig{'basedb'}, "alter user \"$olduser\" rename to \"$user\"");
			&postgresql::execute_sql_logged($qconfig{'basedb'}, "alter user \"$user\" with password $pass");
			$_[0]->{'postgres_user'} = $user;
			&$second_print($text{'setup_done'});
			}
		else {
			# Cannot
			&$second_print($text{'save_norename'});
			}
		}
	else {
		&$second_print($text{'save_nopostgres'});
		}
	}
elsif ($user ne $olduser && $_[0]->{'parent'}) {
	# Change owner of PostgreSQL databases
	&$first_print($text{'save_postgresuser2'});
	local $user = &postgres_user($_[0]);
	if (&postgresql::get_postgresql_version() >= 8.0) {
		foreach my $db (&domain_databases($_[0], [ "mysql" ])) {
			&postgresql::execute_sql_logged($qconfig{'basedb'}, "alter database \"$db->{'name'}\" owner to \"$user\"");
			}
		&$second_print($text{'setup_done'});
		}
	else {
		&$second_print($text{'save_nopostgresuser2'});
		}
	}
}

# delete_postgres(&domain)
# Delete the PostgreSQL database and user
sub delete_postgres
{
# Delete all databases
&require_postgres();
&delete_postgres_database($_[0], &unique(split(/\s+/, $_[0]->{'db_postgres'})))
	if ($_[0]->{'db_postgres'});
local $user = &postgres_user($_[0]);

if (!$_[0]->{'parent'}) {
	# Delete the user
	&$first_print($text{'delete_postgresuser'});
	if (&postgres_user_exists($_[0])) {
		&postgresql::execute_sql_logged($qconfig{'basedb'}, "drop user \"$user\"");
		&$second_print($text{'setup_done'});
		}
	else {
		&$second_print($text{'save_nopostgres'});
		}
	}
}

# validate_postgres(&domain)
# Make sure all PostgreSQL databases exist
sub validate_postgres
{
local ($d) = @_;
&require_postgres();
local %got = map { $_, 1 } &postgresql::list_databases();
foreach my $db (&domain_databases($d, [ "postgres" ])) {
	$got{$db->{'name'}} || return &text('validate_epostgres',$db->{'name'});
	}
if (!&postgres_user_exists($d)) {
	return &text('validate_epostgresuser', &mysql_user($d));
	}
return undef;
}

# disable_postgres(&domain)
# Invalidate the domain's PostgreSQL user
sub disable_postgres
{
&$first_print($text{'disable_postgres'});
local $user = &postgres_user($_[0]);
if ($_[0]->{'parent'}) {
	&$second_print($text{'save_nopostgrespar'});
	}
elsif (&postgres_user_exists($_[0])) {
	&require_postgres();
	local $date = localtime(0);
	&postgresql::execute_sql_logged($qconfig{'basedb'}, "alter user \"$user\" valid until '$date'");
	&$second_print($text{'setup_done'});
	}
else {
	&$second_print($text{'save_nopostgres'});
	}
}

# enable_postgres(&domain)
# Validate the domain's PostgreSQL user
sub enable_postgres
{
&$first_print($text{'enable_postgres'});
local $user = &postgres_user($_[0]);
if ($_[0]->{'parent'}) {
	&$second_print($text{'save_nopostgrespar'});
	}
elsif (&postgres_user_exists($_[0])) {
	&require_postgres();
	&postgresql::execute_sql_logged($qconfig{'basedb'}, "alter user \"$user\" valid until 'Jan 1 2038'");
	&$second_print($text{'setup_done'});
	}
else {
	&$second_print($text{'save_nopostgres'});
	}
}

# backup_postgres(&domain, file)
# Dumps this domain's postgreSQL database to a backup file
sub backup_postgres
{
&require_postgres();

# Find all the domains's databases
local @dbs = split(/\s+/, $_[0]->{'db_postgres'});

# Create empty 'base' backup file
&open_tempfile(EMPTY, ">$_[1]");
&close_tempfile(EMPTY);

# Back them all up
local $db;
local $ok = 1;
foreach $db (@dbs) {
	&$first_print(&text('backup_postgresdump', $db));
	local $dbfile = $_[1]."_".$db;
	local $destfile = $dbfile;
	if ($postgresql::postgres_sameunix) {
		# For a backup done as the postgres user, create an empty file
		# owned by him first
		local @uinfo = getpwnam($postgresql::postgres_login);
		if (@uinfo) {
			$destfile = &transname();
			&open_tempfile(EMPTY, ">$destfile", 0, 1);
			&close_tempfile(EMPTY);
			&set_ownership_permissions($uinfo[2], $uinfo[3],
						   undef, $destfile);
			}
		}
	local $err = &postgresql::backup_database($db, $destfile, 'c', undef);
	if ($err) {
		&$second_print(&text('backup_postgresdumpfailed',
				     "<pre>$err</pre>"));
		$ok = 0;
		}
	else {
		if ($destfile ne $dbfile) {
			&copy_source_dest($destfile, $dbfile);
			&unlink_file($destfile);
			}
		&$second_print($text{'setup_done'});
		}
	}
return $ok;
}

# restore_postgres(&domain, file,  &opts, &allopts, homeformat, &oldd, asowner)
# Restores this domain's postgresql database from a backup file, and re-creates
# the postgresql user.
sub restore_postgres
{
&require_postgres();
&foreign_require("proc", "proc-lib.pl");
&$first_print($text{'restore_postgresdrop'});
	{
	local $first_print = \&null_print;	# supress messages
	local $second_print = \&null_print;
	&require_mysql();

	# First clear out the databases
	&delete_postgres($_[0]);

	# Now re-set up the user only
	&setup_postgres($_[0], 1);
	}
&$second_print($text{'setup_done'});

# Work out which databases are in backup
local ($dbfile, @dbs);
push(@dbs, [ $_[0]->{'db'}, $_[1] ]) if (-s $_[1]);
foreach $dbfile (glob("$_[1]_*")) {
	if (-r $dbfile) {
		$dbfile =~ /\Q$_[1]\E_(.*)$/;
		push(@dbs, [ $1, $dbfile ]);
		}
	}

# Finally, import the data
local $db;
foreach $db (@dbs) {
	&$first_print(&text('restore_postgresload', $db->[0]));
	if (&check_postgres_clash($_[0], $db->[0])) {
		&$second_print(&text('restore_postgresclash'));
		return 0;
		}
	&$indent_print();
	&create_postgres_database($_[0], $db->[0]);
	&$outdent_print();
	if ($postgresql::postgres_sameunix) {
		# Restore is running as the postgres user - make the backup
		# file owned by him, and the parent directory world-accessible
		local @uinfo = getpwnam($postgresql::postgres_login);
		if (@uinfo) {
			&set_ownership_permissions($uinfo[2], $uinfo[3],
						   undef, $db->[1]);
			local $dir = $_[1];
			$dir =~ s/\/[^\/]+$//;
			&set_ownership_permissions(undef, undef, 0711, $dir);
			}
		}
	local $err;
	if ($_[6]) {
		# As domain owner
		local $postgresql::postgres_login = &postgres_user($_[0]);
		local $postgresql::postgres_pass = &postgres_pass($_[0], 1);
		$err = &postgresql::restore_database($db->[0], $db->[1], 0, 0);
		}
	else {
		# As master admin
		$err = &postgresql::restore_database($db->[0], $db->[1], 0, 0);
		}
	if ($err) {
		&$second_print(&text('restore_mysqlloadfailed', "<pre>$err</pre>"));
		return 0;
		}
	else {
		&$second_print($text{'setup_done'});
		}
	}
return 1;
}

# postgres_user(&domain, [always-new])
sub postgres_user
{
if ($_[0]->{'parent'}) {
	# Get from parent domain
	return &postgres_user(&get_domain($_[0]->{'parent'}), $_[1]);
	}
return defined($_[0]->{'postgres_user'}) && !$_[1] ?
	$_[0]->{'postgres_user'} : &postgres_username($_[0]->{'user'}); 
}

# set_postgres_user(&domain, newuser)
# Updates a domain object with a new PostgreSQL username
sub set_postgres_user
{
$_[0]->{'postgres_user'} = $_[1];
}

sub postgres_username
{
return $_[0];
}

# postgres_size(&domain, db, [size-only])
sub postgres_size
{
&require_postgres();
local $size;
local @tables;
eval {
	# Make sure DBI errors don't cause a total failure
	local $main::error_must_die = 1;
	local $postgresql::force_nodbi = 1;
	local $d = &postgresql::execute_sql($_[1], "select sum(relpages) from pg_class where relname not like 'pg_%'");
	$size = $d->{'data'}->[0]->[0]*1024*2;
	if (!$_[2]) {
		@tables = &postgresql::list_tables($_[1], 1);
		}
	};
return ($size, scalar(@tables));
}

# check_postgres_database_clash(&domain, db)
# Returns 1 if some database name is already in use
sub check_postgres_database_clash
{
&require_postgres();
local @dblist = &postgresql::list_databases();
return 1 if (&indexof($_[1], @dblist) >= 0);
}

# create_postgres_database(&domain, db, &opts)
# Create one PostgreSQL database
sub create_postgres_database
{
&$first_print(&text('setup_postgresdb', $_[1]));
&require_postgres();

# Build and run creation command
local $user = &postgres_user($_[0]);
local $sql = "create database ".&postgresql::quote_table($_[1]);
local $withs;
if (&postgresql::get_postgresql_version() >= 7) {
	$withs .= " owner=\"$user\"";
	}
if ($_[2]->{'encoding'}) {
	$withs .= " encoding '$_[2]->{'encoding'}'";
	}
if ($withs) {
	$sql .= " with".$withs;
	}
&postgresql::execute_sql_logged($qconfig{'basedb'}, $sql);

# Make sure nobody else can access it
eval {
	local $main::error_must_die = 1;
	&postgresql::execute_sql_logged($qconfig{'basedb'},
		"revoke all on database ".&postgresql::quote_table($_[1]).
		" from public");
	};
local @dbs = split(/\s+/, $_[0]->{'db_postgres'});
push(@dbs, $_[1]);
$_[0]->{'db_postgres'} = join(" ", @dbs);
&$second_print($text{'setup_done'});
}

# grant_postgres_database(&domain, dbname)
# Alters the owner of a PostgreSQL database to some domain
sub grant_postgres_database
{
local ($d, $dbname) = @_;
&require_postgres();
if (&postgresql::get_postgresql_version() >= 8.0) {
	local $user = &postgres_user($d);
	&postgresql::execute_sql_logged($qconfig{'basedb'}, "alter database \"$dbname\" owner to \"$user\"");
	}
}

# delete_postgres_database(&domain, dbname, ...)
# Delete one PostgreSQL database
sub delete_postgres_database
{
&require_postgres();
local @dblist = &postgresql::list_databases();
&$first_print(&text('delete_postgresdb', join(", ", @_[1..$#_])));
local @dbs = split(/\s+/, $_[0]->{'db_postgres'});
local @missing;
foreach my $db (@_[1..$#_]) {
	if (&indexof($db, @dblist) >= 0) {
		&postgresql::execute_sql_logged($qconfig{'basedb'},
			"drop database ".&postgresql::quote_table($db));
		}
	else {
		push(@missing, $db);
		}
	@dbs = grep { $_ ne $db } @dbs;
	}
$_[0]->{'db_postgres'} = join(" ", @dbs);
if (@missing) {
	&$second_print(&text('delete_mysqlmissing', join(", ", @missing)));
	}
else {
	&$second_print($text{'setup_done'});
	}
}

# revoke_postgres_database(&domain, dbname)
# Takes away a domain's access to a PostgreSQL database, by setting the owner
# back to postgres
sub revoke_postgres_database
{
local ($d, $dbname) = @_;
&require_postgres();
if (&postgresql::get_postgresql_version() >= 8.0 &&
    &postgres_user_exists("postgres")) {
	&postgresql::execute_sql_logged($qconfig{'basedb'}, "alter database \"$dbname\" owner to \"postgres\"");
	}
}

# list_postgres_database_users(&domain, db)
# Returns a list of PostgreSQL users and passwords who can access some database
sub list_all_postgres_users
{
local ($d, $db) = @_;
return ( );	# XXX not possible
}

# list_all_postgres_users()
# Returns a list of all PostgreSQL users
sub list_postgres_database_users
{
local ($d, $db) = @_;
return ( );	# XXX not possible
}

# create_postgres_database_user(&domain, &dbs, username, password)
sub create_postgres_database_user
{
}

# list_postgres_tables(database)
# Returns a list of tables in the specified database
sub list_postgres_tables
{
&require_postgres();
return &postgresql::list_tables($_[0], 1);
}

# get_database_host_postgres()
# Returns the hostname of the server on which PostgreSQL is actually running
sub get_database_host_postgres
{
&require_postgres();
return $postgres::config{'host'} || 'localhost';
}

# sysinfo_postgres()
# Returns the PostgreSQL version
sub sysinfo_postgres
{
&require_postgres();
local @rv;
eval {
	# Protect against DBI errors
	local $main::error_must_die = 1;
	local $postgresql::force_nodbi = 1;
	local $ver = &postgresql::get_postgresql_version();
	@rv = ( [ $text{'sysinfo_postgresql'}, $ver ] );
	};
return @rv;
}

sub startstop_postgres
{
local ($typestatus) = @_;
&require_postgres();
return ( ) if (!&postgresql::is_postgresql_local());
local $r = defined($typestatus->{'postgresql'}) ?
                $typestatus->{'postgresql'} == 1 :
		&postgresql::is_postgresql_running();
local @links = ( { 'link' => '/postgresql/',
		   'desc' => $text{'index_pgmanage'},
		   'manage' => 1 } );
if ($r == 1) {
	return ( { 'status' => 1,
		   'name' => $text{'index_pgname'},
		   'desc' => $text{'index_pgstop'},
		   'restartdesc' => $text{'index_pgrestart'},
		   'longdesc' => $text{'index_pgstopdesc'},
		   'links' => \@links } );
	}
elsif ($r == 0) {
	return ( { 'status' => 0,
		   'name' => $text{'index_pgname'},
		   'desc' => $text{'index_pgstart'},
		   'longdesc' => $text{'index_pgstartdesc'},
		   'links' => \@links } );
	}
else {
	return ( );
	}
}

sub stop_service_postgres
{
&require_postgres();
local $rv = &postgresql::stop_postgresql();
sleep(5);
return $rv;
}

sub start_service_postgres
{
&require_postgres();
return &postgresql::start_postgresql();
}

# check_postgres_login(dbname, dbuser, dbpass)
# Tries to login to PostgreSQL with the given credentials, returning undef
# on failure
sub check_postgres_login
{
local ($dbname, $dbuser, $dbpass) = @_;
&require_postgres();
local $main::error_must_die = 1;
local $postgresql::postgres_login = $dbuser;
local $postgresql::postgres_pass = $dbpass;
eval { &postgresql::execute_sql($dbname, "select version()") };
local $err = $@;
if ($err) {
	$err =~ s/\s+at\s+.*\sline//g;
	return $err;
	}
return undef;
}

# creation_form_postgres(&domain)
# Returns options for a new PostgreSQL database
sub creation_form_postgres
{
&require_postgres();
if (&postgresql::get_postgresql_version() >= 7.4) {
	local $tmpl = &get_template($_[0]->{'template'});
	local $cs = $tmpl->{'postgres_encoding'};
	$cs = "" if ($cs eq "none");
	return &ui_table_row($text{'database_encoding'},
			     &ui_select("postgres_encoding", $cs,
					[ [ undef, "&lt;$text{'default'}&gt;" ],
					  &list_postgres_encodings() ]));
	}
}

# creation_parse_postgres(&domain, &in)
# Parse the form generated by creation_form_postgres, and return a structure
# for passing to create_postgres_database
sub creation_parse_postgres
{
local ($d, $in) = @_;
local $opts = { 'encoding' => $in->{'postgres_encoding'} };
return $opts;
}

# list_postgres_encodings()
# Returns a list of available PostgreSQL encodings for new DBs, each of which
# is a 2-element hash ref containing a code and description
sub list_postgres_encodings
{
if (!defined(@postgres_encodings_cache)) {
	@postgres_encodings_cache = ( );
	&open_readfile(ENCS, "$module_root_directory/postgres-encodings");
	while(<ENCS>) {
		s/\r|\n//g;
		local @w = split(/\t/, $_);
		if ($w[2] !~ /\Q$w[0]\E/i) {
			$w[2] .= " ($w[0])";
			}
		push(@postgres_encodings_cache, [ $w[0], $w[2] ]);
		}
	close(ENCS);
	@postgres_encodings_cache = sort { lc($a->[1]) cmp lc($b->[1]) }
					 @postgres_encodings_cache;
	}
return @postgres_encodings_cache;
}

# show_template_postgres(&tmpl)
# Outputs HTML for editing PostgreSQL related template options
sub show_template_postgres
{
local ($tmpl) = @_;
&require_postgres();

# Default encoding
print &ui_table_row(&hlink($text{'tmpl_postgres_encoding'},
			   "template_postgres_encoding"),
    &ui_select("postgres_encoding",  $tmpl->{'postgres_encoding'},
	[ $tmpl->{'default'} ? ( ) :
	    ( [ "", "&lt;$text{'tmpl_postgres_encodingdef'}&gt;" ] ),
	  [ "none", "&lt;$text{'tmpl_postgres_encodingnone'}&gt;" ],
	  &list_postgres_encodings() ]));
}

# parse_template_postgres(&tmpl)
# Updates PostgreSQL related template options from %in
sub parse_template_postgres
{
local ($tmpl) = @_;
&require_postgres();
$tmpl->{'postgres_encoding'} = $in{'postgres_encoding'};
}

$done_feature_script{'postgres'} = 1;

1;

