# postgresql-lib.pl
# Common PostgreSQL functions

do '../web-lib.pl';
&init_config();
if ($config{'plib'}) {
	$ENV{$gconfig{'ld_env'}} .= ':' if ($ENV{$gconfig{'ld_env'}});
	$ENV{$gconfig{'ld_env'}} .= $config{'plib'};
	}
if ($config{'psql'} =~ /^(.*)\/bin\/psql$/ && $1 ne '' && $1 ne '/usr') {
	$ENV{$gconfig{'ld_env'}} .= ':' if ($ENV{$gconfig{'ld_env'}});
	$ENV{$gconfig{'ld_env'}} .= "$1/lib";
	}

%access = &get_module_acl();

if (!$config{'nodbi'}) {
	# Check if we have DBD::Pg
	eval <<EOF;
use DBI;
\$driver_handle = DBI->install_driver("Pg");
EOF
	}

# is_postgresql_running()
# Returns 1 if yes, 0 if no, -1 if the login is invalid, -2 if there
# is a library problem
sub is_postgresql_running
{
local $temp = &tempname();
local $host = $config{'host'} ? "-h $config{'host'}" : "";
local $cmd;
if ($config{'login'}) {
	open(TEMP, ">$temp");
	print TEMP "$config{'login'}\n$config{'pass'}\n";
	close(TEMP);
	local $out;
	$cmd = "$config{'psql'} -u -c '' $host $config{'basedb'} <$temp";
	}
else {
	$cmd = "$config{'psql'} -c '' $host $config{'basedb'}";
	}
if ($config{'unix'} && getpwnam($config{'unix'})) {
	$cmd = "su $config{'unix'} -c \"$cmd\"";
	}
open(OUT, "$cmd 2>&1 |");
while(<OUT>) { $out .= $_; }
close(OUT);
unlink($temp);
if ($out =~ /setuserid:/i || $out =~ /no\s+password\s+supplied/i ||
    $out =~ /no\s+postgres\s+username/i || $out =~ /authentication\s+failed/i ||
    $out =~ /password:.*password:/i || $out =~ /database.*does.*not/i) {
	return -1;
	}
elsif ($out =~ /connect.*failed/i || $out =~ /could not connect to server:/) {
	return 0;
	}
elsif ($out =~ /lib\S+\.so/i) {
	return -2;
	}
else {
	return 1;
	}
}

# get_postgresql_version()
sub get_postgresql_version
{
local $v = &execute_sql($config{'basedb'}, 'select version()');
$v = $v->{'data'}->[0]->[0];
if ($v =~ /postgresql\s+([0-9\.]+)/i) {
	return $1;
	}
else {
	return undef;
	}
}

# list_databases()
# Returns a list of all databases
sub list_databases
{
local $t = &execute_sql($config{'basedb'}, 'select * from pg_database order by datname');
return map { $_->[0] } @{$t->{'data'}};
}

# list_tables(database)
# Returns a list of tables in some database
sub list_tables
{
#local $t = &execute_sql($_[0], 'select relname from pg_class where relkind = \'r\' and relname not like \'pg_%\' and relhasrules = \'f\' order by relname');
local $t = &execute_sql($_[0], 'select tablename from pg_tables where tablename not like \'pg_%\' order by tablename');
return map { $_->[0] } @{$t->{'data'}};
}

# list_types()
# Returns a list of all available field types
sub list_types
{
local $t = &execute_sql($config{'basedb'}, 'select typname from pg_type where typrelid = 0 and typname !~ \'^_.*\' order by typname');
return map { $_->[0] } @{$t->{'data'}};
}

# table_structure(database, table)
# Returns a list of hashes detailing the structure of a table
sub table_structure
{
local $t = &execute_sql($_[0], "select a.attnum, a.attname, t.typname, a.attlen, a.atttypmod, a.attnotnull, a.atthasdef FROM pg_class c, pg_attribute a, pg_type t WHERE c.relname = '$_[1]'    and a.attnum > 0     and a.attrelid = c.oid     and a.atttypid = t.oid order by attnum");
local (@rv, $r);
foreach $r (@{$t->{'data'}}) {
	local $arr;
	$arr++ if ($r->[2] =~ s/^_//);
	local $sz = $r->[4] - 4;
	if ($sz >= 65536) {
		$sz = int($sz/65536).",".($sz%65536);
		}
	push(@rv, { 'field' => $r->[1],
		    'arr' => $arr ? 'YES' : 'NO',
		    'type' => $r->[4] < 0 ? $r->[2]
					  : $r->[2]."($sz)",
		    'null' => $r->[5] =~ /f|0/ ? 'YES' : 'NO' } );
	}
return @rv;
}

# execute_sql(database, sql)
sub execute_sql
{
if ($driver_handle && !$config{'unix'} &&
    $_[1] !~ /^(create|drop)\s+database/ && $_[1] !~ /^\\/) {
	# Use the DBI interface
	local $cstr = "dbname=$_[0]";
	$cstr .= ";host=$config{'host'}" if ($config{'host'});
	local $dbh = $driver_handle->connect($cstr,
			$config{'login'}, $config{'pass'});
	$dbh || &error("DBI connect failed : ",$DBI::errstr);
	$dbh->{'AutoCommit'} = 0;
	local $cmd = $dbh->prepare($_[1]);
	if (!$cmd->execute()) {
		&error(&text('esql', "<tt>".&html_escape($_[1])."</tt>",
			     "<tt>".&html_escape($dbh->errstr)."</tt>"));
		}
	local (@data, @row);
	local @titles = @{$cmd->{'NAME'}};
	while(@row = $cmd->fetchrow()) {
		push(@data, [ @row ]);
		}
	$cmd->finish();
	$dbh->commit();
	$dbh->disconnect();
	return { 'titles' => \@titles,
		 'data' => \@data };
	}
else {
	# Call the psql program
	local $temp = &tempname();
	open(TEMP, ">$temp");
	print TEMP "$config{'login'}\n$config{'pass'}\n";
	close(TEMP);
	local $host = $config{'host'} ? "-h $config{'host'}" : "";
	local $cmd = "$config{'psql'} -u -c \"$_[1]\" $host $_[0] <$temp";
	if ($config{'unix'}) {
		$cmd =~ s/"/\\"/g;
		$cmd = "su $config{'unix'} -c \"$cmd\"";
		}
	open(OUT, "$cmd <$temp 2>&1 |");
	local ($line, $rv, @data);
	do {
		$line = <OUT>;
		} while($line =~ /^(username|password|user name):/i ||
			$line =~ /(warning|notice):/i || $line !~ /\S/);
	if ($line =~ /^ERROR:\s+(.*)/ || $line =~ /FATAL.*:\s+(.*)/) {
		&error(&text('esql', "<tt>$_[1]</tt>", "<tt>$1</tt>"));
		}
	else {
		local $dash = <OUT>;
		if ($dash =~ /^\s*\+\-/) {
			# mysql-style output
			$line = <OUT>;
			$line =~ s/^[\s\|]+//; $line =~ s/[\s\|]+$//;
			local @titles = split(/\|/, $line);
			map { s/^\s+//; s/\s+$// } @titles;
			$line = <OUT>;	# skip useless dashes
			while(1) {
				$line = <OUT>;
				last if (!$line || $line =~ /^\s*\+/);
				$line =~ s/^[\s\|]+//; $line =~ s/[\s\|]+$//;
				local @row = split(/\|/, $line);
				map { s/^\s+//; s/\s+$// } @row;
				push(@data, \@row);
				}
			$rv = { 'titles' => \@titles, 'data' => \@data };
			}
		elsif ($dash !~ /^-/) {
			# no output, such as from an insert
			$rv = undef;
			}
		else {
			# psql-style output
			local @titles = split(/\|/, $line);
			map { s/^\s+//; s/\s+$// } @titles;
			while(1) {
				$line = <OUT>;
				last if (!$line || $line =~ /^\(\d+\s+\S+\)/);
				local @row = split(/ \| /, $line);
				map { s/^\s+//; s/\s+$// } @row;
				push(@data, \@row);
				}
			$rv = { 'titles' => \@titles, 'data' => \@data };
			}
		}
	close(OUT);
	unlink($temp);
	return $rv;
	}
}

# execute_sql_logged(database, command)
sub execute_sql_logged
{
&additional_log('sql', $_[0], $_[1]);
return &execute_sql(@_);
}

# run_as_postgres(command)
sub run_as_postgres
{
pipe(OUTr, OUTw);
local $pid = fork();
if (!$pid) {
	untie(*STDIN);
	untie(*STDOUT);
	untie(*STDERR);
	close(STDIN);
	open(STDOUT, ">&OUTw");
	open(STDERR, ">&OUTw");

	local @u = getpwnam($config{'user'});
	$( = $u[3]; $) = "$u[3] $u[3]";
	($>, $<) = ($u[2], $u[2]);

	exec(@_);
	print "Exec failed : $!\n";
	exit 1;
	}
close(OUTw);
return OUTr;
}

sub can_edit_db
{
local $d;
return 1 if ($access{'dbs'} eq '*');
foreach $d (split(/\s+/, $access{'dbs'})) {
	return 1 if ($d eq $_[0]);
	}
return 0;
}

# get_hba_config()
# Parses the postgres host access config file
sub get_hba_config
{
local $lnum = 0;
open(HBA, $config{'hba_conf'});
while(<HBA>) {
	s/\r|\n//g;
	s/^\s*#.*$//g;
	if (/^\s*host\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)(\s+(\S+))?/) {
		push(@rv, { 'type' => 'host',
			    'index' => scalar(@rv),
			    'line' => $lnum,
			    'db' => $1,
			    'address' => $2,
			    'netmask' => $3,
			    'auth' => $4,
			    'arg' => $6 } );
		}
	elsif (/^\s*local\s+(\S+)\s+(\S+)(\s+(\S+))?/) {
		push(@rv, { 'type' => 'local',
			    'index' => scalar(@rv),
			    'line' => $lnum,
			    'db' => $1,
			    'auth' => $2,
			    'arg' => $4 } );
		}
	$lnum++;
	}
close(HBA);
return @rv;
}

# create_hba(&hba)
sub create_hba
{
local $lref = &read_file_lines($config{'hba_conf'});
push(@$lref, &hba_line($_[0]));
&flush_file_lines();
}

# delete_hba(&hba)
sub delete_hba
{
local $lref = &read_file_lines($config{'hba_conf'});
splice(@$lref, $_[0]->{'line'}, 1);
&flush_file_lines();
}

# modify_hba(&hba)
sub modify_hba
{
local $lref = &read_file_lines($config{'hba_conf'});
splice(@$lref, $_[0]->{'line'}, 1, &hba_line($_[0]));
&flush_file_lines();
}

# swap_hba(&hba1, &hba2)
sub swap_hba
{
local $lref = &read_file_lines($config{'hba_conf'});
local $line0 = $lref->[$_[0]->{'line'}];
local $line1 = $lref->[$_[1]->{'line'}];
$lref->[$_[1]->{'line'}] = $line0;
$lref->[$_[0]->{'line'}] = $line1;
&flush_file_lines();
}

sub hba_line
{
if ($_[0]->{'type'} eq 'host') {
	return join(" ", 'host', $_[0]->{'db'}, $_[0]->{'address'},
		         $_[0]->{'netmask'}, $_[0]->{'auth'},
			 $_[0]->{'arg'} ? ( $_[0]->{'arg'} ) : () );
	}
else {
	return join(" ", 'local', $_[0]->{'db'}, $_[0]->{'auth'},
			 $_[0]->{'arg'} ? ( $_[0]->{'arg'} ) : () );
	}
}

# split_array(value)
sub split_array
{
if ($_[0] =~ /^\{(.*)\}$/) {
	local @a = split(/,/, $1);
	return @a;
	}
else {
	return ( $_[0] );
	}
}

# join_array(values ..)
sub join_array
{
local $alpha;
map { $alpha++ if (!/^-?[0-9\.]+/) } @_;
return $alpha ? '{'.join(',', map { "'$_'" } @_).'}'
	      : '{'.join(',', @_).'}';
}

1;

