postgis/regress/run_test.pl

2131 lines
56 KiB
Perl
Executable File

#!/usr/bin/env perl
#
# PostGIS - Spatial Types for PostgreSQL
# http://postgis.net
#
# Copyright (C) 2012-2014 Sandro Santilli <strk@kbt.io>
# Copyright (C) 2014-2015 Regina Obe <lr@pcorp.us>
# Copyright (C) 2012-2013 Paul Ramsey <pramsey@cleverelephant.ca>
#
# This is free software; you can redistribute and/or modify it under
# the terms of the GNU General Public Licence. See the COPYING file.
#
#$| = 1;
use File::Basename;
use File::Temp 'tempdir';
use Time::HiRes qw(time);
use File::Copy;
use File::Path;
use Cwd 'abs_path';
use Getopt::Long;
use strict;
##################################################################
# Add . to @INC if removed for CVE-2016-1238
##################################################################
BEGIN {
push @INC, "." if(!grep /^\.$/, @INC);
}
##################################################################
# Usage ./run_test.pl <testname> [<testname>]
#
# Create the spatial database 'postgis_reg' (or whatever $DB
# is set to) if it doesn't already exist.
#
# Run the <testname>.sql script
# Check output against <testname>_expected
#
# Use `-` as the testname to drop into an interactive shell
#
##################################################################
##################################################################
# Global configuration items
##################################################################
our $DB = $ENV{"POSTGIS_REGRESS_DB"} || "postgis_reg";
our $REGDIR = $ENV{"POSTGIS_REGRESS_DIR"} || abs_path(dirname($0));
our $TOP_SOURCEDIR = ${REGDIR} . '/..';
our $ABS_TOP_SOURCEDIR = abs_path(${TOP_SOURCEDIR});
our $TOP_BUILDDIR = $ENV{"POSTGIS_TOP_BUILD_DIR"} || ${TOP_SOURCEDIR};
our $sysdiff = !system("diff --strip-trailing-cr $0 $0 2> /dev/null");
##################################################################
# Set up some global variables
##################################################################
my $RUN = 0;
my $FAIL = 0;
my $SKIP = 0;
our $TEST = "";
##################################################################
# Parse command line opts
##################################################################
my $OPT_CLEAN = 0;
my $OPT_NODROP = 0;
my $OPT_NOCREATE = 0;
my $OPT_UPGRADE = 0;
my $OPT_DUMPRESTORE = 0;
my $OPT_WITH_TIGER = 0;
my $OPT_WITH_TOPO = 0;
my $OPT_WITH_RASTER = 0;
my $OPT_WITH_SFCGAL = 0;
my $OPT_EXPECT = 0;
my $OPT_EXTENSIONS = 0;
my @OPT_HOOK_AFTER_CREATE;
my @OPT_HOOK_AFTER_RESTORE;
my @OPT_HOOK_BEFORE_DUMP;
my @OPT_HOOK_BEFORE_TEST;
my @OPT_HOOK_AFTER_TEST;
my @OPT_HOOK_BEFORE_UNINSTALL;
my @OPT_HOOK_BEFORE_UPGRADE;
my @OPT_HOOK_AFTER_UPGRADE;
my $OPT_EXTVERSION = '';
my $OPT_UPGRADE_PATH = '';
our $OPT_UPGRADE_FROM = '';
my $OPT_UPGRADE_TO = '';
our $VERBOSE = 0;
our $OPT_SCHEMA = 'public';
GetOptions (
'verbose+' => \$VERBOSE,
'clean' => \$OPT_CLEAN,
'nodrop' => \$OPT_NODROP,
'upgrade' => \$OPT_UPGRADE,
'upgrade-path=s' => \$OPT_UPGRADE_PATH,
'dumprestore' => \$OPT_DUMPRESTORE,
'nocreate' => \$OPT_NOCREATE,
'tiger' => \$OPT_WITH_TIGER,
'topology' => \$OPT_WITH_TOPO,
'raster' => \$OPT_WITH_RASTER,
'sfcgal' => \$OPT_WITH_SFCGAL,
'expect' => \$OPT_EXPECT,
'extensions' => \$OPT_EXTENSIONS,
'schema=s' => \$OPT_SCHEMA,
'build-dir=s' => \$TOP_BUILDDIR,
'after-create-script=s' => \@OPT_HOOK_AFTER_CREATE,
'after-test-script=s' => \@OPT_HOOK_AFTER_TEST,
'before-uninstall-script=s' => \@OPT_HOOK_BEFORE_UNINSTALL,
'before-test-script=s' => \@OPT_HOOK_BEFORE_TEST,
'before-upgrade-script=s' => \@OPT_HOOK_BEFORE_UPGRADE,
'before-dump-script=s' => \@OPT_HOOK_BEFORE_DUMP,
'after-upgrade-script=s' => \@OPT_HOOK_AFTER_UPGRADE,
'after-restore-script=s' => \@OPT_HOOK_AFTER_RESTORE
);
if ( @ARGV < 1 )
{
usage();
}
sub findOrDie
{
my $exec = shift;
my $verbose = shift;
printf "Checking for %s ... ", $exec if $verbose;
foreach my $d ( split /:/, $ENV{PATH} )
{
my $path = $d . '/' . $exec;
if ( -x $path ) {
if ( $verbose ) {
print "found";
print " ($path)" if $verbose gt 1;
print "\n";
}
return $path
}
}
print STDERR "Unable to find $exec executable.\n";
print STDERR "PATH is " . $ENV{"PATH"} . "\n";
die "HINT: use POSTGIS_TOP_BUILD_DIR env or --build-dir switch the specify top build dir.\n";
}
# Prepend scripts' build dirs to path
# TODO: make this conditional ?
$ENV{PATH} = $TOP_BUILDDIR . '/loader:' .
$TOP_BUILDDIR . '/raster/loader:' .
$TOP_BUILDDIR . '/utils:' .
$ENV{PATH};
our $SHP2PGSQL;
sub shp2pgsql
{
$SHP2PGSQL = findOrDie 'shp2pgsql', @_ unless defined($SHP2PGSQL);
return $SHP2PGSQL;
}
our $PGSQL2SHP;
sub pgsql2shp
{
$PGSQL2SHP = findOrDie 'pgsql2shp', @_ unless defined($PGSQL2SHP);
return $PGSQL2SHP;
}
our $RASTER2PGSQL;
sub raster2pgsql
{
$RASTER2PGSQL = findOrDie 'raster2pgsql', @_ unless defined($RASTER2PGSQL);
return $RASTER2PGSQL;
}
our $POSTGIS_RESTORE;
sub postgis_restore
{
$POSTGIS_RESTORE = findOrDie 'postgis_restore.pl', @_ unless defined($POSTGIS_RESTORE);
}
if ( $OPT_UPGRADE_PATH )
{
$OPT_UPGRADE = 1; # implied
my @path = split ('--', $OPT_UPGRADE_PATH);
$OPT_UPGRADE_FROM = $path[0]
|| die "Malformed upgrade path, <from>--<to> expected, $OPT_UPGRADE_PATH given";
$OPT_UPGRADE_TO = $path[1]
|| die "Malformed upgrade path, <from>--<to> expected, $OPT_UPGRADE_PATH given";
print "Upgrade path: ${OPT_UPGRADE_FROM} --> ${OPT_UPGRADE_TO}\n";
}
# Split-raster extension was introduced in PostGIS-3.0.0
sub has_split_raster_ext
{
my $fullver = shift;
# unpackaged is always current, so does have
# split raster already.
return 1 if $fullver =~ /^unpackaged$/;
$fullver =~ s/unpackaged//;
my @ver = split(/\./, $fullver);
return 0 if ( $ver[0] < 3 );
return 1;
}
##################################################################
# Set the locale to "C" so error messages match
# Save original locale to set back
##################################################################
my $ORIG_LC_ALL = $ENV{"LC_ALL"};
my $ORIG_LANG = $ENV{"LANG"};
$ENV{"LC_ALL"} = "C";
$ENV{"LANG"} = "C";
# Add locale info to the psql options
# Add pg12 precision suppression
my $PGOPTIONS = $ENV{"PGOPTIONS"};
$PGOPTIONS .= " -c lc_messages=C";
$PGOPTIONS .= " -c client_min_messages=NOTICE";
$PGOPTIONS .= " -c extra_float_digits=0";
$ENV{"PGOPTIONS"} = $PGOPTIONS;
# Calculate the regression directory locations
my $STAGED_INSTALL_DIR = $TOP_BUILDDIR . "/regress/00-regress-install";
my $STAGED_SCRIPTS_DIR = $STAGED_INSTALL_DIR . "/share/contrib/postgis";
my $OBJ_COUNT_PRE = 0;
my $OBJ_COUNT_POST = 0;
##################################################################
# Set up the temporary directory
##################################################################
# Pre-flight to check if we need
# to find shp2pgsql/pgsql2shp/raster2pgsql
foreach $TEST (@ARGV)
{
if ( -r "${TEST}.dbf" )
{
shp2pgsql($VERBOSE);
pgsql2shp($VERBOSE);
}
elsif ( -r "${TEST}.tif" )
{
raster2pgsql($VERBOSE);
}
}
##################################################################
# Set up the temporary directory
##################################################################
my $TMPDIR;
if ( $ENV{'PGIS_REG_TMPDIR'} )
{
$TMPDIR = $ENV{'PGIS_REG_TMPDIR'};
}
elsif ( -d "/tmp/" && -w "/tmp/" )
{
$TMPDIR = "/tmp/pgis_reg";
}
else
{
$TMPDIR = tempdir( CLEANUP => 0 );
}
mkpath $TMPDIR; # make sure tmp dir exists
# Set log name
my $REGRESS_LOG = "${TMPDIR}/regress_log";
# Report
print "TMPDIR is $TMPDIR\n" if $VERBOSE gt 1;
##################################################################
# Prepare the database
##################################################################
my @dblist = grep(/1/, split(/\n/, `
psql -tAc "
SELECT 1 FROM pg_catalog.pg_database
WHERE datname = '${DB}'
" template1
`));
my $defextver = `
psql -XtAc "
SELECT default_version
FROM pg_catalog.pg_available_extensions
WHERE name = 'postgis'
" template1
`;
chop $defextver;
my $dbcount = @dblist;
if ( $dbcount == 0 )
{
if ( $OPT_NOCREATE )
{
print "Database $DB does not exist.\n";
print "Run without the --nocreate flag to create it.\n";
exit(1);
}
else
{
create_spatial();
}
}
else
{
if ( $OPT_NOCREATE )
{
print "Using existing database $DB\n";
}
else
{
print STDERR "Database $DB already exists, dropping.\n";
`dropdb $DB`;
create_spatial();
}
}
my $pgvernum = sql("SELECT current_setting('server_version_num')");
my $libver = sql("select postgis_lib_version()");
if ( ! $libver )
{
`dropdb $DB`;
print "\nSomething went wrong (no PostGIS installed in $DB).\n";
print "For details, check $REGRESS_LOG\n\n";
exit(1);
}
sub staged_scripts_dir
{
unless ( -d $STAGED_SCRIPTS_DIR ) {
print STDERR "Unable to find $STAGED_SCRIPTS_DIR directory.\n";
print STDERR "TOP_BUILDDIR is $TOP_BUILDDIR.\n";
die "HINT: use POSTGIS_TOP_BUILD_DIR env or --build-dir switch the specify top build dir.\n";
}
$STAGED_SCRIPTS_DIR;
}
sub scriptdir
{
my ( $version, $systemwide ) = @_;
my $scriptdir;
if ( $systemwide or ( $version and $version ne $libver ) ) {
my $pgis_majmin = $version;
$pgis_majmin =~ s/^([1-9]*\.[0-9]*).*/\1/;
$scriptdir = `pg_config --sharedir`;
chop $scriptdir;
$scriptdir .= "/contrib/postgis-" . $pgis_majmin;
} else {
$scriptdir = staged_scripts_dir()
}
#print "XXX: scriptdir: $scriptdir\n";
return $scriptdir;
}
sub semver_lessthan
{
my ($a,$b) = @_;
my @acomp = split(/\./, $a);
my @bcomp = split(/\./, $b);
foreach my $ac (@acomp) {
my $bc = shift(@bcomp);
return 0 if not defined($bc); # $b has less components
return 1 if ( $ac < $bc ); # $a is less than $b
return 0 if ( $ac > $bc ); # $a is not less than $b
}
# $a is equal to $b so far, if $b has more elements,
# it is bigger
return @bcomp ? 1 : 0;
}
if ( $OPT_DUMPRESTORE )
{
my $DBDUMP = dump_db();
die unless defined $DBDUMP;
print "Dropping db '${DB}'\n";
my $rv = system("dropdb ${DB} >> $REGRESS_LOG 2>&1");
if ( $rv ) {
fail("Could not drop ${DB}", $REGRESS_LOG);
die;
}
print "Creating db '${DB}'\n";
$rv = create_db();
if ( ! $rv ) {
fail("Could not create db ${DB}", $REGRESS_LOG);
die;
}
die unless restore_db($DBDUMP);
# Update libver
$libver = sql("select postgis_lib_version()");
unlink($DBDUMP);
}
if ( $OPT_UPGRADE )
{
print "Upgrading from postgis $libver\n";
foreach my $hook (@OPT_HOOK_BEFORE_UPGRADE)
{
print "Running before-upgrade-script $hook\n";
die unless load_sql_file($hook, 1);
}
if ( $OPT_EXTENSIONS )
{
die unless upgrade_spatial_extensions();
}
else
{
die unless upgrade_spatial();
}
foreach my $hook (@OPT_HOOK_AFTER_UPGRADE)
{
print "Running after-upgrade-script $hook\n";
die unless load_sql_file($hook, 1);
}
# Update libver
$libver = sql("select postgis_lib_version()");
}
##################################################################
# Report PostGIS environment
##################################################################
my $geosver = sql("select postgis_geos_version()");
my $projver = sql("select postgis_proj_version()");
my $libbuilddate = sql("select postgis_lib_build_date()");
my $pgsqlver = sql("select version()");
my $gdalver = sql("select postgis_gdal_version()") if $OPT_WITH_RASTER;
my $sfcgalver = sql("select postgis_sfcgal_version()") if $OPT_WITH_SFCGAL;
my $scriptver = sql("select postgis_scripts_installed()");
# postgis_lib_revision was introduced in 3.1.0
my $librev = semver_lessthan($scriptver, "3.1.0") ?
sql("select postgis_svn_version()") :
sql("select postgis_lib_revision()");
my $raster_scriptver = sql("select postgis_raster_scripts_installed()")
if ( $OPT_WITH_RASTER );
print "$pgsqlver\n";
print " Postgis $libver - (${librev}) - $libbuilddate\n";
print " scripts ${scriptver}\n";
print " raster scripts ${raster_scriptver}\n" if ( $OPT_WITH_RASTER );
print " GEOS: $geosver\n" if $geosver;
print " PROJ: $projver\n" if $projver;
print " SFCGAL: $sfcgalver\n" if $sfcgalver;
print " GDAL: $gdalver\n" if $gdalver;
##################################################################
# Run the tests
##################################################################
print "\nRunning tests\n\n";
foreach my $hook (@OPT_HOOK_AFTER_CREATE)
{
start_test("after-create-script $hook");
show_progress();
pass() if load_sql_file($hook, 1);
}
foreach $TEST (@ARGV)
{
my $TEST_OBJ_COUNT_PRE;
my $TEST_OBJ_COUNT_POST;
my $TEST_START_TIME;
if ( "${TEST}" eq '-' )
{
my $scriptdir = scriptdir($libver, $OPT_EXTENSIONS);
print "-- Entering interactive shell --\n";
# TODO: add more variables?
my $cmd = "psql -Xq"
. " -v \"regdir=$REGDIR\""
. " -v \"top_builddir=$TOP_BUILDDIR\""
. " -v \"scriptdir=$scriptdir\""
. " -v \"schema=$OPT_SCHEMA.\""
# TODO: inject search_path somehow
#. " -c \"SET search_path TO public,$OPT_SCHEMA,topology\""
. " ${DB}"
;
my $rv = system($cmd);
print "-- Moving on with tests, if any --\n";
next;
}
# catch a common mistake (strip trailing .sql)
$TEST =~ s/.sql$//;
foreach my $hook (@OPT_HOOK_BEFORE_TEST)
{
print " Running before-test-script $hook\n" if $VERBOSE > 1;
die unless load_sql_file($hook, 1);
}
start_test($TEST);
$TEST_OBJ_COUNT_PRE = count_postgis_objects();
# Check for a "-pre.pl" file in case there are setup commands
unless ( eval_file("${TEST}-pre.pl") )
{
chop($@);
fail("Failed evaluating ${TEST}-pre.pl: $@");
next;
}
# Check for a "-pre.sql" file in case there is setup SQL needed before
# the test can be run.
if ( -r "${TEST}-pre.sql" )
{
run_simple_sql("${TEST}-pre.sql");
show_progress();
}
# Check .dbf *before* .sql as loader test could
# create the .sql
# Check for .dbf not just .shp since the loader can load
# .dbf files without a .shp.
$TEST_START_TIME = time;
if ( -r "${TEST}.dbf" )
{
pass("in ".int(1000*(time-$TEST_START_TIME))." ms") if ( run_loader_test() );
}
elsif ( -r "${TEST}.tif" )
{
my $rv = run_raster_loader_test("${TEST}.tif");
pass("in ".int(1000*(time-$TEST_START_TIME))." ms") if $rv;
}
elsif ( -r "${TEST}.tif.ref" )
{
open(REF, "${TEST}.tif.ref");
my $raster_ref = <REF>;
close(REF);
chop $raster_ref;
#print "Raster ref: [$raster_ref]\n";
# Resolve raster_ref relative to ${TEST} dirname
my $raster_path = dirname(${TEST}) . '/' . $raster_ref;
#print "Raster path: [$raster_path]\n";
my $rv = run_raster_loader_test($raster_path);
pass("in ".int(1000*(time-$TEST_START_TIME))." ms") if $rv;
}
elsif ( -r "${TEST}.sql" )
{
my $rv = run_simple_test("${TEST}.sql", "${TEST}_expected");
pass("in ".int(1000*(time-$TEST_START_TIME))." ms") if $rv;
}
elsif ( -r "${TEST}.dmp" )
{
pass("in ".int(1000*(time-$TEST_START_TIME))." ms") if run_dumper_test();
}
else
{
print " skipped (can't read any ${TEST}.{sql,dbf,tif,dmp})\n";
$SKIP++;
# Even though we skipped this test, we will still run the cleanup
# scripts
}
if ( -r "${TEST}-post.sql" )
{
my $rv = run_simple_sql("${TEST}-post.sql");
if ( ! $rv )
{
print " ... but cleanup sql failed!";
}
}
# Check for a "-post.pl" file in case there are teardown commands
eval_file("${TEST}-post.pl");
$TEST_OBJ_COUNT_POST = count_postgis_objects();
if ( $TEST_OBJ_COUNT_POST != $TEST_OBJ_COUNT_PRE )
{
fail("PostGIS object count pre-test ($TEST_OBJ_COUNT_POST) != post-test ($TEST_OBJ_COUNT_PRE)");
}
foreach my $hook (@OPT_HOOK_AFTER_TEST)
{
print " Running after-test-script $hook\n" if $VERBOSE > 1;
die unless load_sql_file($hook, 1);
}
}
foreach my $hook (@OPT_HOOK_BEFORE_UNINSTALL)
{
start_test("before-uninstall-script $hook");
show_progress();
pass() if load_sql_file($hook, 1);
}
###################################################################
# Uninstall postgis (serves as an uninstall test)
##################################################################
# We only test uninstall if we've been asked to drop
# and we did create
# and nobody requested raster or topology
# (until they have an uninstall script themself)
if ( (! $OPT_NODROP) && $OBJ_COUNT_PRE > 0 )
{
uninstall_spatial();
}
##################################################################
# Summary report
##################################################################
print "\nRun tests: $RUN\n";
print "Failed: $FAIL\n";
if ( $OPT_CLEAN )
{
rmtree($TMPDIR);
}
if ( ! ($OPT_NODROP || $OPT_NOCREATE) )
{
system("dropdb $DB");
}
else
{
print "Drop database ${DB} manually\n";
}
# Set the locale back to the original
$ENV{"LC_ALL"} = $ORIG_LC_ALL;
$ENV{"LANG"} = $ORIG_LANG;
exit($FAIL);
##################################################################
# Utility functions
#
sub usage
{
die qq{
Usage: $0 [<options>] <testname> [<testname>]
Options:
-v, --verbose be verbose about failures
--nocreate do not create the regression database on start
--upgrade source the upgrade scripts on start
--upgrade-path upgrade path, format <from>--<to>.
<from> can be specified as "unpackaged<version>"
to specify a script version to start from.
<to> can be specified as ":auto" to request
upgrades to default version, and be appended
an exclamation mark (ie: ":auto!" or "3.0.0!") to
request upgrade via postgis_extensions_upgrade()
if available.
--dumprestore dump and (after upgrade, if --upgrade is given)
restore spatially-enabled db before running tests
--nodrop do not drop the regression database on exit
--schema where to install/find PostGIS (relocatable) PostGIS
(defaults to "public")
--raster load also raster extension
--tiger load also tiger_geocoder extension
--topology load also topology extension
--sfcgal use also sfcgal backend
--clean cleanup test logs on exit
--expect save obtained output as expected
--extension load using extensions
--build-dir <path>
specify where to find the top build dir of PostGIS,
to find binaries and scripts
--after-create-script <path>
script to load after spatial db creation
(multiple switches supported, to be run in given order)
--before-uninstall-script <path>
script to load before spatial extension uninstall
(multiple switches supported, to be run in given order)
--before-dump-script <path>
script to load before dump, if --dumprestore is given
(multiple switches supported, to be run in given order)
--after-restore-script <path>
script to load after restore, if --dumprestore is given
(multiple switches supported, to be run in given order)
--before-upgrade-script <path>
script to load before upgrade
(multiple switches supported, to be run in given order)
--after-upgrade-script <path>
script to load after upgrade
(multiple switches supported, to be run in given order)
--before-test-script <path>
script to load before each test run
(multiple switches supported, to be run in given order)
--after-test-script <path>
script to load after each test run
(multiple switches supported, to be run in given order)
};
}
# start_test <name>
sub start_test
{
my $test = shift;
my $abstest = abs_path($test);
if ( defined($abstest) )
{
$test = $abstest;
}
$test =~ s|${ABS_TOP_SOURCEDIR}/||;
print " $test ";
$RUN++;
show_progress();
}
# Print a entry
sub echo_inline
{
my $msg = shift;
print $msg;
}
# Print a single dot
sub show_progress
{
print ".";
}
# pass <msg>
sub pass
{
my $msg = shift;
printf(" ok %s\n", $msg);
}
# fail <msg> <log>
sub fail
{
my $msg = shift;
my $log = shift;
if ( ! $log )
{
printf(" failed (%s)\n", $msg);
}
elsif ( $VERBOSE )
{
printf(" failed (%s: %s)\n", $msg, $log);
print "-----------------------------------------------------------------------------\n";
open(LOG, "$log") or die "Cannot open log file $log\n";
print while(<LOG>);
close(LOG);
print "-----------------------------------------------------------------------------\n";
}
else
{
printf(" failed (%s: %s)\n", $msg, $log);
}
$FAIL++;
}
##################################################################
# run_simple_sql
# Run an sql script and hide results unless it fails.
# SQL input file name is $1
##################################################################
sub run_simple_sql
{
my $sql = shift;
if ( ! -r $sql )
{
fail("can't read $sql");
return 0;
}
# Dump output to a temp file.
my $tmpfile = sprintf("%s/test_%s_tmp", $TMPDIR, $RUN);
my $cmd = "psql -v \"VERBOSITY=terse\" "
. " -v \"regdir=$REGDIR\""
. " -v \"top_builddir=$TOP_BUILDDIR\""
. " -tXAq $DB < $sql > $tmpfile 2>&1";
#print($cmd);
my $rv = system($cmd);
# Check if psql errored out.
if ( $rv != 0 )
{
fail("Unable to run sql script $sql", $tmpfile);
return 0;
}
# Check for ERROR lines
open FILE, "$tmpfile";
my @lines = <FILE>;
close FILE;
my @errors = grep(/^ERROR/, @lines);
if ( @errors > 0 )
{
fail("Errors while running sql script $sql", $tmpfile);
return 0;
}
unlink $tmpfile;
return 1;
}
sub drop_table
{
my $tblname = shift;
my $cmd = "psql -tXAq -d $DB -c \"DROP TABLE IF EXISTS $tblname\" >> $REGRESS_LOG 2>&1";
my $rv = system($cmd);
die "Could not run: $cmd\n" if $rv;
}
sub sql
{
my $sql = shift;
my $result = `psql -qtXA -d $DB -c 'SET search_path TO public,$OPT_SCHEMA' -c "$sql" | sed '/^SET\$/d'`;
$result =~ s/[\n\r]*$//;
$result;
}
sub eval_file
{
my $file = shift;
my $pl;
if ( -r $file )
{
do $file or return 0;
#system($^X, $file) == 0 or return 0;
}
1;
}
##################################################################
# run_simple_test
# Run an sql script and compare results with the given expected output
# SQL input is ${TEST}.sql, expected output is {$TEST}_expected
##################################################################
sub run_simple_test
{
my $sql = shift;
my $expected = shift;
my $msg = shift;
if ( ! -r "$sql" )
{
fail("can't read $sql");
return 0;
}
if ( ! $OPT_EXPECT )
{
if ( ! -r "$expected" )
{
fail("can't read $expected");
return 0;
}
}
show_progress();
my $outfile = sprintf("%s/test_%s_out", $TMPDIR, $RUN);
my $betmpdir = sprintf("%s/pgis_reg_tmp/", $TMPDIR);
my $tmpfile = sprintf("%s/test_%s_tmp", $betmpdir, $RUN);
my $diffile = sprintf("%s/test_%s_diff", $TMPDIR, $RUN);
mkpath($betmpdir);
chmod 0777, $betmpdir;
my $scriptdir = scriptdir($libver, $OPT_EXTENSIONS);
my ($sqlfile,$sqldir) = fileparse($sql);
my $cmd = "cd $sqldir; psql -v \"VERBOSITY=terse\""
. " -v \"tmpfile='$tmpfile'\""
. " -v \"scriptdir=$scriptdir\""
. " -v \"regdir=$REGDIR\""
. " -v \"top_builddir=$TOP_BUILDDIR\""
. " -v \"schema=$OPT_SCHEMA.\""
. " -c \"SET search_path TO public,$OPT_SCHEMA,topology\""
. " -tXAq -f $sqlfile $DB > $outfile 2>&1";
my $rv = system($cmd);
if ( $rv ) {
fail "psql exited with an error", $outfile;
die;
}
# Check for ERROR lines
open(FILE, "$outfile");
my @lines = <FILE>;
close(FILE);
# Strip the lines we don't care about
@lines = grep(!/^\s+$/, @lines);
# Morph values into expected forms
for ( my $i = 0; $i < @lines; $i++ )
{
$lines[$i] =~ s/Infinity/inf/g;
$lines[$i] =~ s/Inf/inf/g;
$lines[$i] =~ s/1\.#INF/inf/g;
$lines[$i] =~ s/[eE]([+-])0+(\d+)/e$1$2/g;
$lines[$i] =~ s/Self-intersection .*/Self-intersection/;
$lines[$i] =~ s/^ROLLBACK/COMMIT/;
$lines[$i] =~ s/^psql.*(NOTICE|WARNING|ERROR):/\1:/g;
}
# Write out output file
open(FILE, ">$outfile");
foreach my $l (@lines)
{
print FILE $l;
}
close(FILE);
# Clean up interim stuff
#remove_tree($betmpdir);
if ( $OPT_EXPECT )
{
print " (expected)";
copy($outfile, $expected);
}
else
{
my $diff = diff($expected, $outfile);
if ( $diff )
{
open(FILE, ">$diffile");
print FILE $diff;
close(FILE);
fail("${msg}diff expected obtained", $diffile);
return 0;
}
else
{
unlink $outfile;
return 1;
}
}
return 1;
}
##################################################################
# This runs the loader once and checks the output of it.
# It will NOT run if neither the expected SQL nor the expected
# select results file exists, unless you pass true for the final
# parameter.
#
# $1 - Description of this run of the loader, used for error messages.
# $2 - Table name to load into.
# $3 - The name of the file containing what the
# SQL generated by shp2pgsql should look like.
# $4 - The name of the file containing the expected results of
# SELECT geom FROM _tblname should look like.
# $5 - Command line options for shp2pgsql.
# $6 - If you pass true, this will run the loader even if neither
# of the expected results files exists (though of course
# the results won't be compared with anything).
##################################################################
sub run_loader_and_check_output
{
my $description = shift;
my $tblname = shift;
my $expected_sql_file = shift;
my $expected_select_results_file = shift;
my $loader_options = shift;
my $run_always = shift;
my ( $cmd, $rv );
my $outfile = "${TMPDIR}/loader.out";
my $errfile = "${TMPDIR}/loader.err";
# ON_ERROR_STOP is used by psql to return non-0 on an error
my $psql_opts = " --quiet --no-psqlrc --variable ON_ERROR_STOP=true";
if ( $run_always || -r $expected_sql_file || -r $expected_select_results_file )
{
show_progress();
# Produce the output SQL file.
$cmd = shp2pgsql() . " $loader_options -g the_geom ${TEST}.shp $tblname > $outfile 2> $errfile";
$rv = system($cmd);
if ( $rv )
{
fail(" $description: running $cmd", "$errfile");
return 0;
}
# Compare the output SQL file with the expected if there is one.
if ( -r $expected_sql_file )
{
show_progress();
my $diff = diff($expected_sql_file, $outfile);
if ( $diff )
{
fail(" $description: actual SQL does not match expected.", "$outfile");
return 0;
}
}
# Run the loader SQL script.
show_progress();
$cmd = "psql $psql_opts -f $outfile $DB > $errfile 2>&1";
$rv = system($cmd);
if ( $rv )
{
fail(" $description: running shp2pgsql output","$errfile");
return 0;
}
# Run the select script (if there is one)
if ( -r "${TEST}.select.sql" )
{
$rv = run_simple_test("${TEST}.select.sql",$expected_select_results_file, $description);
return 0 if ( ! $rv );
}
}
return 1;
}
##################################################################
# This runs the dumper once and checks the output of it.
# It will NOT run if the expected shp file does not exist, unless
# you pass true for the final parameter.
#
# $1 - Description of this run of the dumper, used for error messages.
# $2 - Table name to dump from.
# $3 - "Expected" .shp file to compare with.
# $4 - If you pass true, this will run the loader even if neither
# of the expected results files exists (though of course
# the results won't be compared with anything).
##################################################################
sub run_dumper_and_check_output
{
my $description = shift;
my $tblname = shift;
my $expected_shp_file = shift;
my $run_always = shift;
my ($cmd, $rv);
my $errfile = "${TMPDIR}/dumper.err";
if ( $run_always || -r $expected_shp_file )
{
show_progress();
$cmd = pgsql2shp() . " -f ${TMPDIR}/dumper $DB $tblname > $errfile 2>&1";
$rv = system($cmd);
if ( $rv )
{
fail("$description: dumping loaded table", $errfile);
return 0;
}
# Compare with expected output if there is any.
if ( -r $expected_shp_file )
{
show_progress();
my $diff = diff($expected_shp_file, "$TMPDIR/dumper.shp");
if ( $diff )
{
# ls -lL "${TMPDIR}"/dumper.shp "$_expected_shp_file" > "${TMPDIR}"/dumper.diff
fail("$description: dumping loaded table", "${TMPDIR}/dumper.diff");
return 0;
}
}
}
return 1;
}
##################################################################
# This runs the loader once and checks the output of it.
# It will NOT run if neither the expected SQL nor the expected
# select results file exists, unless you pass true for the final
# parameter.
#
# $1 - Description of this run of the loader, used for error messages.
# $2 - Table name to load into.
# $3 - The name of the file containing what the
# SQL generated by shp2pgsql should look like.
# $4 - The name of the file containing the expected results of
# SELECT rast FROM _tblname should look like.
# $5 - Command line options for raster2pgsql.
# $6 - If you pass true, this will run the loader even if neither
# of the expected results files exists (though of course
# the results won't be compared with anything).
##################################################################
sub run_raster_loader_and_check_output
{
my $description = shift;
my $raster_file = shift;
my $tblname = shift;
my $expected_sql_file = shift;
my $expected_select_results_file = shift;
my $loader_options = shift;
my $run_always = shift;
# ON_ERROR_STOP is used by psql to return non-0 on an error
my $psql_opts="--no-psqlrc --variable ON_ERROR_STOP=true";
my ($cmd, $rv);
my $outfile = "${TMPDIR}/loader.out";
my $errfile = "${TMPDIR}/loader.err";
if ( $run_always || -r $expected_sql_file || -r $expected_select_results_file )
{
show_progress();
# Produce the output SQL file.
$cmd = raster2pgsql() . " $loader_options $raster_file $tblname > $outfile 2> $errfile";
$rv = system($cmd);
if ( $rv )
{
fail("$description: running raster2pgsql", $errfile);
return 0;
}
if ( -r $expected_sql_file )
{
show_progress();
my $diff = diff($expected_sql_file, $outfile);
if ( $diff )
{
fail(" $description: actual SQL does not match expected.", "$outfile");
return 0;
}
}
# Run the loader SQL script.
show_progress();
$cmd = "psql $psql_opts -f $outfile $DB > $errfile 2>&1";
$rv = system($cmd);
if ( $rv )
{
fail(" $description: running raster2pgsql output","$errfile");
return 0;
}
# Run the select script (if there is one)
if ( -r "${TEST}.select.sql" )
{
$rv = run_simple_test("${TEST}.select.sql",$expected_select_results_file, $description);
return 0 if ( ! $rv );
}
}
return 1;
}
##################################################################
# run_loader_test
#
# Load a shapefile with different methods, create a 'select *' SQL
# test and run simple test with provided expected output.
#
# SHP input is ${TEST}.shp, expected output is {$TEST}.expected
##################################################################
sub run_loader_test
{
# See if there is a custom command-line options file
my $opts_file = "${TEST}.opts";
my $custom_opts="";
if ( -r $opts_file )
{
open(FILE, $opts_file);
my @opts;
while (<FILE>) {
next if /^\s*#/;
chop;
s/{regdir}/$REGDIR/;
push @opts, $_;
}
close(FILE);
$custom_opts = join(" ", @opts);
}
my $tblname="loadedshp";
# If we have some expected files to compare with, run in wkt mode.
if ( ! run_loader_and_check_output("wkt test", $tblname, "${TEST}-w.sql.expected", "${TEST}-w.select.expected", "-w $custom_opts") )
{
drop_table($tblname) unless $OPT_NODROP;
return 0;
}
drop_table($tblname);
# If we have some expected files to compare with, run in geography mode.
if ( ! run_loader_and_check_output("geog test", $tblname, "${TEST}-G.sql.expected", "${TEST}-G.select.expected", "-G $custom_opts") )
{
drop_table($tblname) unless $OPT_NODROP;
return 0;
}
# If we have some expected files to compare with, run the dumper and compare shape files.
if ( ! run_dumper_and_check_output("dumper geog test", $tblname, "${TEST}-G.shp.expected") )
{
drop_table($tblname) unless $OPT_NODROP;
return 0;
}
drop_table($tblname);
# Always run in wkb ("normal") mode, even if there are no expected files to compare with.
if( ! run_loader_and_check_output("wkb test", $tblname, "${TEST}.sql.expected", "${TEST}.select.expected", "$custom_opts", "true") )
{
drop_table($tblname) unless $OPT_NODROP;
return 0;
}
# If we have some expected files to compare with, run the dumper and compare shape files.
if( ! run_dumper_and_check_output("dumper wkb test", $tblname, "${TEST}.shp.expected") )
{
drop_table($tblname) unless $OPT_NODROP;
return 0;
}
drop_table($tblname);
# Some custom parameters can be incompatible with -D.
if ( $custom_opts )
{
# If we have some expected files to compare with, run in wkt dump mode.
if ( ! run_loader_and_check_output("wkt dump test", $tblname, "${TEST}-wD.sql.expected") )
{
drop_table($tblname) unless $OPT_NODROP;
return 0;
}
drop_table($tblname);
# If we have some expected files to compare with, run in wkt dump mode.
if ( ! run_loader_and_check_output("geog dump test", $tblname, "${TEST}-GD.sql.expected") )
{
drop_table($tblname) unless $OPT_NODROP;
return 0;
}
drop_table($tblname);
# If we have some expected files to compare with, run in wkb dump mode.
if ( ! run_loader_and_check_output("wkb dump test", $tblname, "${TEST}-D.sql.expected") )
{
drop_table($tblname) unless $OPT_NODROP;
return 0;
}
drop_table($tblname);
}
return 1;
}
##################################################################
# run_dumper_test
#
# Run dumper and compare output with various expectances
# test and run simple test with provided expected output.
#
# input is ${TEST}.dmp, where first line is considered to be the
# [table|query] argument for pgsql2shp and all the next lines,
# if any are options.
#
##################################################################
sub run_dumper_test
{
my $dump_file = "${TEST}.dmp";
# ON_ERROR_STOP is used by psql to return non-0 on an error
my $psql_opts="--no-psqlrc --variable ON_ERROR_STOP=true";
my $shpfile = "${TMPDIR}/dumper-" . basename(${TEST}) . "-shp";
my $outfile = "${TMPDIR}/dumper-" . basename(${TEST}) . ".out";
my $errfile = "${TMPDIR}/dumper-" . basename(${TEST}) . ".err";
# Produce the output SHP file.
open DUMPFILE, "$dump_file" or die "Cannot open dump file $dump_file\n";
my @dumplines = <DUMPFILE>;
close DUMPFILE;
chomp(@dumplines);
my $dumpstring = shift @dumplines;
@dumplines = map { local $_ = $_; s/{regdir}/$REGDIR/; $_ } @dumplines;
my @cmd = ( pgsql2shp(), "-f", ${shpfile});
push @cmd, @dumplines;
push @cmd, ${DB};
push @cmd, $dumpstring;
#print "CMD: " . join (' ', @cmd) . "\n";
open my $stdout_save, '>&', *STDOUT or die "Cannot dup stdout\n";
open my $stderr_save, '>&', *STDERR or die "Cannot dup stdout\n";
open STDOUT, ">${outfile}" or die "Cannot write to ${outfile}\n";
open STDERR, ">${errfile}" or die "Cannot write to ${errfile}\n";
my $rv = system(@cmd);
open STDERR, '>&', $stderr_save;
open STDOUT, '>&', $stdout_save;
show_progress();
if ( $rv )
{
fail("dumping", "$errfile");
return 0;
}
my $numtests = 0;
foreach my $ext ("shp","prj","dbf","shx") {
my $obtained = ${shpfile}.".".$ext;
my $expected = ${TEST}."_expected.".$ext;
if ( $OPT_EXPECT )
{
copy($obtained, $expected);
}
elsif ( -r ${expected} ) {
show_progress();
$numtests++;
my $diff = diff($expected, $obtained);
if ( $diff )
{
my $diffile = sprintf("%s/dumper_test_%s_diff", $TMPDIR, $ext);
open(FILE, ">$diffile");
print FILE $diff;
close(FILE);
fail("diff expected obtained", $diffile);
return 0;
}
}
}
#show_progress();
if ( $OPT_EXPECT ) {
print " (expected)";
}
elsif ( ! $numtests ) {
fail("no expectances!");
return 0;
}
return 1;
}
##################################################################
# run_raster_loader_test
##################################################################
sub run_raster_loader_test
{
my $raster_file = shift;
# See if there is a custom command-line options file
my $opts_file = "${TEST}.opts";
my $custom_opts="";
if ( -r $opts_file )
{
my $regdir = abs_path(dirname(${TEST}));
open(FILE, $opts_file);
my @opts;
while (<FILE>) {
next if /^\s*#/;
chop;
s/{regdir}/$regdir/;
push @opts, $_;
}
close(FILE);
$custom_opts = join(" ", @opts);
}
my $tblname="loadedrast";
# If we have some expected files to compare with, run in geography mode.
if ( ! run_raster_loader_and_check_output("test", $raster_file, $tblname, "${TEST}.sql.expected", "${TEST}.select.expected", $custom_opts, "true") )
{
return 0;
}
drop_table($tblname);
return 1;
}
##################################################################
# Count database objects
##################################################################
sub count_db_objects
{
my $count = sql("WITH counts as (
select count(*) from pg_type union all
select count(*) from pg_proc union all
select count(*) from pg_cast union all
select count(*) from pg_aggregate union all
select count(*) from pg_operator union all
select count(*) from pg_opclass union all
select count(*) from pg_namespace
where nspname NOT LIKE 'pg_%'
and nspname != '${OPT_SCHEMA}'
union all
select count(*) from pg_opfamily
)
select sum(count) from counts");
return $count;
}
##################################################################
# Count postgis objects
##################################################################
sub count_postgis_objects
{
my $count = sql("WITH counts as (
select count(*) from spatial_ref_sys
)
select sum(count) from counts");
return $count;
}
##################################################################
# Create the spatial database
##################################################################
sub create_db
{
my $createcmd = "createdb --encoding=UTF-8 --template=template0 --lc-collate=C $DB > $REGRESS_LOG";
return not system($createcmd);
}
sub create_spatial
{
my ($cmd, $rv);
print "Creating database '$DB' \n";
$rv = create_db();
# Count database objects before installing anything
$OBJ_COUNT_PRE = count_db_objects();
if ( $OPT_EXTENSIONS )
{
exit($FAIL) unless prepare_spatial_extensions();
}
else
{
if ( ! $OPT_UPGRADE_FROM )
{
exit($FAIL) unless prepare_spatial();
return 1;
}
if ( $OPT_UPGRADE_FROM !~ /^unpackaged(.*)/ )
{
die "--upgrade-path without --extension is only supported with source unpackaged*";
}
if ( $OPT_UPGRADE_TO != ':auto' )
{
die "--upgrade-path without --extension is only supported with target :auto";
}
exit($FAIL) unless prepare_spatial($1);
}
return 1;
}
sub load_sql_file
{
my $file = shift;
my $strict = shift;
if ( $strict && ! -e $file )
{
fail "Unable to find $file";
return 0;
}
if ( -e $file )
{
# ON_ERROR_STOP is used by psql to return non-0 on an error
my $psql_opts = "--quiet --no-psqlrc --variable ON_ERROR_STOP=true";
my $cmd = "psql $psql_opts -c 'CREATE SCHEMA IF NOT EXISTS $OPT_SCHEMA' ";
$cmd .= "-c 'SET search_path TO $OPT_SCHEMA,topology'";
$cmd .= " -v \"opt_dumprestore=${OPT_DUMPRESTORE}\"";
$cmd .= " -Xf $file $DB >> $REGRESS_LOG 2>&1";
#print " $file\n" if $VERBOSE;
my $rv = system($cmd);
if ( $rv )
{
fail "Error encountered loading $file", $REGRESS_LOG;
#exit 1;
return 0;
}
}
return 1;
}
# Prepare the database for spatial operations (extension method)
sub prepare_spatial_extensions
{
# ON_ERROR_STOP is used by psql to return non-0 on an error
my $psql_opts = "--no-psqlrc --variable ON_ERROR_STOP=true";
my $sql = "CREATE SCHEMA IF NOT EXISTS ${OPT_SCHEMA}";
my $cmd = "psql $psql_opts -c \"". $sql . "\" $DB >> $REGRESS_LOG 2>&1";
my $rv = system($cmd);
if ( $rv ) {
fail "Error encountered creating target schema ${OPT_SCHEMA}", $REGRESS_LOG;
return 0;
}
my $sql = "CREATE EXTENSION postgis";
if ( $OPT_UPGRADE_FROM ) {
if ( $OPT_UPGRADE_FROM =~ /^unpackaged(.*)/ ) {
return prepare_spatial($1);
}
$sql .= " VERSION '" . $OPT_UPGRADE_FROM . "'";
}
$sql .= " SCHEMA " . $OPT_SCHEMA;
print "Preparing db '${DB}' using: ${sql}\n";
my $cmd = "psql $psql_opts -c \"". $sql . "\" $DB >> $REGRESS_LOG 2>&1";
my $rv = system($cmd);
if ( $rv ) {
fail "Error encountered creating EXTENSION POSTGIS", $REGRESS_LOG;
return 0;
}
if ( $OPT_WITH_TOPO )
{
my $sql = "CREATE EXTENSION postgis_topology";
if ( $OPT_UPGRADE_FROM ) {
$sql .= " VERSION '" . $OPT_UPGRADE_FROM . "'";
}
print "Preparing db '${DB}' using: ${sql}\n";
$cmd = "psql $psql_opts -c \"" . $sql . "\" $DB >> $REGRESS_LOG 2>&1";
$rv = system($cmd);
if ( $rv ) {
fail "Error encountered creating EXTENSION POSTGIS_TOPOLOGY", $REGRESS_LOG;
return 0;
}
}
if ( $OPT_WITH_TIGER )
{
my $sql = "CREATE EXTENSION postgis_tiger_geocoder CASCADE";
if ( $OPT_UPGRADE_FROM ) {
$sql .= " VERSION '" . $OPT_UPGRADE_FROM . "'";
}
print "Preparing db '${DB}' using: ${sql}\n";
$cmd = "psql $psql_opts -c \"" . $sql . "\" $DB >> $REGRESS_LOG 2>&1";
$rv = system($cmd);
if ( $rv ) {
fail "Error encountered creating EXTENSION POSTGIS_TIGER_GEOCODER", $REGRESS_LOG;
return 0;
}
}
my $extver = $OPT_UPGRADE_FROM ? $OPT_UPGRADE_FROM : $OPT_UPGRADE_TO ? $OPT_UPGRADE_TO : $defextver;
if ( $OPT_WITH_RASTER && has_split_raster_ext($extver) )
{
my $sql = "CREATE EXTENSION postgis_raster";
if ( $OPT_UPGRADE_FROM ) {
$sql .= " VERSION '" . $OPT_UPGRADE_FROM . "'";
}
$sql .= " SCHEMA " . $OPT_SCHEMA;
print "Preparing db '${DB}' using: ${sql}\n";
$cmd = "psql $psql_opts -c \"" . $sql . "\" $DB >> $REGRESS_LOG 2>&1";
$rv = system($cmd);
if ( $rv ) {
fail "Error encountered creating EXTENSION POSTGIS_RASTER", $REGRESS_LOG;
return 0;
}
}
if ( $OPT_WITH_SFCGAL )
{
{
my $sql = "CREATE EXTENSION postgis_sfcgal";
if ( $OPT_UPGRADE_FROM ) {
if ( semver_lessthan($OPT_UPGRADE_FROM, "2.2.0") )
{
print "NOTICE: skipping SFCGAL extension create "
. "as not available in version '$OPT_UPGRADE_FROM'\n";
last;
}
$sql .= " VERSION '" . $OPT_UPGRADE_FROM . "'";
}
$sql .= " SCHEMA " . $OPT_SCHEMA;
print "Preparing db '${DB}' using: ${sql}\n";
$cmd = "psql $psql_opts -c \"" . $sql . "\" $DB >> $REGRESS_LOG 2>&1";
$rv = system($cmd);
if ( $rv ) {
fail "Error encountered creating EXTENSION POSTGIS_SFCGAL", $REGRESS_LOG;
return 0;
}
}
}
return 1;
}
# Prepare the database for spatial operations (old method)
sub prepare_spatial
{
my $version = shift;
my $scriptdir = scriptdir($version);
print "Loading unpackaged components from $scriptdir\n";
print "Loading PostGIS into '${DB}' \n";
# Load postgis.sql into the database
return 0 unless load_sql_file("${scriptdir}/postgis.sql", 1);
return 0 unless load_sql_file("${scriptdir}/postgis_comments.sql", 0);
return 0 unless load_sql_file("${scriptdir}/spatial_ref_sys.sql", 0);
if ( $OPT_WITH_TOPO )
{
print "Loading Topology into '${DB}'\n";
return 0 unless load_sql_file("${scriptdir}/topology.sql", 1);
return 0 unless load_sql_file("${scriptdir}/topology_comments.sql", 0);
}
if ( $OPT_WITH_RASTER )
{
print "Loading Raster into '${DB}'\n";
return 0 unless load_sql_file("${scriptdir}/rtpostgis.sql", 1);
return 0 unless load_sql_file("${scriptdir}/raster_comments.sql", 0);
}
if ( $OPT_WITH_SFCGAL )
{
print "Loading SFCGAL into '${DB}'\n";
return 0 unless load_sql_file("${scriptdir}/sfcgal.sql", 1);
return 0 unless load_sql_file("${scriptdir}/sfcgal_comments.sql", 0);
}
return 1;
}
sub upgrade_extension_sql
{
my ($extname, $from, $to) = @_;
my $sql = '';
if ( "${libver}" eq "${to}" ) {
if ( semver_lessthan($to, "3.3.0") ) {
$sql .= "ALTER EXTENSION $extname UPDATE TO '${to}next'; ";
} else {
$sql .= "ALTER EXTENSION $extname UPDATE TO 'ANY'; ";
}
}
$sql .= "ALTER EXTENSION $extname UPDATE TO '${to}'";
return $sql;
}
sub package_extension_sql
{
my ($extname, $extver) = @_;
my $sql;
if ( $pgvernum lt 130000 ) {
$sql = "CREATE EXTENSION ${extname} VERSION '${extver}' FROM unpackaged;";
} else {
$sql = "CREATE EXTENSION ${extname} VERSION unpackaged;";
$sql .= "ALTER EXTENSION ${extname} UPDATE TO '${extver}'";
}
return $sql;
}
# Upgrade an existing database (soft upgrade)
sub upgrade_spatial
{
my $version = shift;
my $scriptdir = scriptdir($version);
print "Upgrading PostGIS in '${DB}' using scripts from $scriptdir\n" ;
my $script = "${STAGED_SCRIPTS_DIR}/postgis_upgrade.sql";
print "Upgrading core\n";
return 0 unless load_sql_file($script, 1);
if ( $OPT_WITH_TOPO )
{
$script = "${STAGED_SCRIPTS_DIR}/topology_upgrade.sql";
print "Upgrading topology\n";
return 0 unless load_sql_file($script, 1);
}
if ( $OPT_WITH_RASTER )
{
$script = "${STAGED_SCRIPTS_DIR}/rtpostgis_upgrade.sql";
print "Upgrading raster\n";
return 0 unless load_sql_file($script, 1);
}
if ( $OPT_WITH_SFCGAL )
{
$script = "${STAGED_SCRIPTS_DIR}/sfcgal_upgrade.sql";
print "Upgrading sfcgal\n";
return 0 unless load_sql_file($script, 1);
}
return 1;
}
# Upgrade an existing database (soft upgrade, extension method)
sub upgrade_spatial_extensions
{
# ON_ERROR_STOP is used by psql to return non-0 on an error
my $psql_opts = "--no-psqlrc --variable ON_ERROR_STOP=true";
my $sql;
my $upgrade_via_function = 0;
if ( $OPT_UPGRADE_TO =~ /!$/ )
{
$OPT_UPGRADE_TO =~ s/!$//;
my $from = $OPT_UPGRADE_FROM;
$from =~ s/^unpackaged//;
if ( ! $from || ! semver_lessthan($from, "3.0.0") )
{
$upgrade_via_function = 1;
}
else
{
print "WARNING: postgis_extensions_upgrade()".
" not available or functional in version $from.".
" We'll use manual upgrade.\n";
}
}
if ( $OPT_UPGRADE_TO =~ /^:auto/ )
{
$OPT_UPGRADE_TO = $defextver;
}
my $nextver = $OPT_UPGRADE_TO ? "${OPT_UPGRADE_TO}" : "${libver}";
if ( $upgrade_via_function )
{
# TODO: pass ${nextver} if supported by OPT_UPGRADE_FROM ?
$sql = "SELECT postgis_extensions_upgrade()";
}
elsif ( $OPT_UPGRADE_FROM =~ /^unpackaged/ )
{
$sql = package_extension_sql('postgis', ${nextver});
}
else
{
$sql = upgrade_extension_sql('postgis', ${libver}, ${nextver});
}
print "Upgrading PostGIS in '${DB}' using: ${sql}\n" ;
my $cmd = "psql $psql_opts -c \"" . $sql . "\" $DB >> $REGRESS_LOG 2>&1";
#print "CMD: " . $cmd . "\n";
my $rv = system($cmd);
if ( $rv ) {
fail "Error encountered updating EXTENSION POSTGIS", $REGRESS_LOG;
return 0;
}
# Handle raster split if coming from pre-split extension
# and going to splitted raster
if ( $OPT_UPGRADE_FROM &&
( not $OPT_UPGRADE_FROM =~ /^unpackaged/ ) &&
has_split_raster_ext($OPT_UPGRADE_TO) &&
not has_split_raster_ext($OPT_UPGRADE_FROM) )
{
# upgrade of postgis must have unpackaged raster, so
# we create it again here
my $sql = package_extension_sql('postgis_raster', ${nextver});
print "Packaging PostGIS Raster in '${DB}' using: ${sql}\n" ;
my $cmd = "psql $psql_opts -c \"" . $sql . "\" $DB >> $REGRESS_LOG 2>&1";
my $rv = system($cmd);
if ( $rv ) {
fail "Error encountered creating EXTENSION POSTGIS_RASTER from unpackaged on upgrade", $REGRESS_LOG;
return 0;
}
if ( ! $OPT_WITH_RASTER )
{
print "Dropping PostGIS Raster in '${DB}' using: ${sql}\n" ;
$sql = "DROP EXTENSION postgis_raster";
$cmd = "psql $psql_opts -c \"" . $sql . "\" $DB >> $REGRESS_LOG 2>&1";
$rv = system($cmd);
if ( $rv ) {
fail "Error encountered dropping EXTENSION POSTGIS_RASTER on upgrade", $REGRESS_LOG;
return 0;
}
}
}
if ( $upgrade_via_function )
{
# The function does everything
return 1;
}
if ( $OPT_WITH_RASTER && has_split_raster_ext(${nextver}) )
{
my $sql;
if ( $OPT_UPGRADE_FROM =~ /^unpackaged/ ) {
$sql = package_extension_sql('postgis_raster', ${nextver});
}
else {
$sql = upgrade_extension_sql('postgis_raster', ${libver}, ${nextver});
}
print "Upgrading PostGIS Raster in '${DB}' using: ${sql}\n" ;
my $cmd = "psql $psql_opts -c \"" . $sql . "\" $DB >> $REGRESS_LOG 2>&1";
my $rv = system($cmd);
if ( $rv ) {
fail "Error encountered updating EXTENSION POSTGIS_RASTER", $REGRESS_LOG;
return 0;
}
}
if ( $OPT_WITH_TOPO )
{
my $sql;
if ( $OPT_UPGRADE_FROM =~ /^unpackaged/ ) {
$sql = package_extension_sql('postgis_topology', ${nextver});
}
else {
$sql = upgrade_extension_sql('postgis_topology', ${libver}, ${nextver});
}
print "Upgrading PostGIS Topology in '${DB}' using: ${sql}\n";
my $cmd = "psql $psql_opts -c \"" . $sql . "\" $DB >> $REGRESS_LOG 2>&1";
my $rv = system($cmd);
if ( $rv ) {
fail "Error encountered updating EXTENSION POSTGIS_TOPOLOGY", $REGRESS_LOG;
return 0;
}
}
if ( $OPT_WITH_SFCGAL )
{
my $sql;
if ( $OPT_UPGRADE_FROM =~ /^unpackaged/ ) {
$sql = package_extension_sql('postgis_sfcgal', ${nextver});
}
elsif ( $OPT_UPGRADE_FROM && semver_lessthan($OPT_UPGRADE_FROM, "2.2.0") )
{
print "NOTICE: installing SFCGAL extension on upgrade "
. "as it was not available in version '$OPT_UPGRADE_FROM'\n";
$sql = "CREATE EXTENSION postgis_sfcgal VERSION '${nextver}'";
}
else
{
$sql = upgrade_extension_sql('postgis_sfcgal', ${libver}, ${nextver});
}
$cmd = "psql $psql_opts -c \"" . $sql . "\" $DB >> $REGRESS_LOG 2>&1";
print "Upgrading PostGIS SFCGAL in '${DB}' using: ${sql}\n" ;
$rv = system($cmd);
if ( $rv ) {
fail "Error encountered creating EXTENSION POSTGIS_SFCGAL", $REGRESS_LOG;
return 0;
}
}
return 1;
}
sub drop_spatial
{
my $ok = 1;
if ( $OPT_WITH_TOPO )
{
load_sql_file("${STAGED_SCRIPTS_DIR}/uninstall_topology.sql");
}
if ( $OPT_WITH_RASTER )
{
load_sql_file("${STAGED_SCRIPTS_DIR}/uninstall_rtpostgis.sql");
}
if ( $OPT_WITH_SFCGAL )
{
load_sql_file("${STAGED_SCRIPTS_DIR}/uninstall_sfcgal.sql");
}
load_sql_file("${STAGED_SCRIPTS_DIR}/uninstall_postgis.sql");
return 1;
}
sub drop_spatial_extensions
{
# ON_ERROR_STOP is used by psql to return non-0 on an error
my $psql_opts="--no-psqlrc --variable ON_ERROR_STOP=true";
my ($cmd, $rv);
if ( $OPT_WITH_TOPO )
{
# NOTE: "manually" dropping topology schema as EXTENSION does not
# take care of that itself, see
# http://trac.osgeo.org/postgis/ticket/2138
$cmd = "psql $psql_opts -c \"DROP EXTENSION postgis_topology; DROP SCHEMA topology;\" $DB >> $REGRESS_LOG 2>&1";
$rv = system($cmd);
if ( $rv ) {
fail "Error encountered dropping EXTENSION postgis_topology", $REGRESS_LOG;
return 0;
}
}
if ( $OPT_WITH_SFCGAL )
{
$cmd = "psql $psql_opts -c \"DROP EXTENSION postgis_sfcgal;\" $DB >> $REGRESS_LOG 2>&1";
$rv = system($cmd);
if ( $rv ) {
fail "Error encountered dropping EXTENSION postgis_sfcgal", $REGRESS_LOG;
return 0;
}
}
if ( $OPT_WITH_RASTER )
{
$cmd = "psql $psql_opts -c \"DROP EXTENSION IF EXISTS postgis_raster;\" $DB >> $REGRESS_LOG 2>&1";
$rv = system($cmd);
if ( $rv ) {
fail "Error encountered dropping EXTENSION postgis_raster", $REGRESS_LOG;
return 0;
}
}
if ( $OPT_WITH_TIGER )
{
$cmd = "psql $psql_opts -c \"DROP EXTENSION IF EXISTS postgis_tiger_geocoder;
DROP EXTENSION IF EXISTS fuzzystrmatch;
DROP SCHEMA IF EXISTS tiger;
DROP SCHEMA IF EXISTS tiger_data;
\" $DB >> $REGRESS_LOG 2>&1";
$rv = system($cmd);
return 0 if $rv;
if ( $rv ) {
fail "Error encountered dropping EXTENSION postgis_tiger_geocoder", $REGRESS_LOG;
return 0;
}
}
$cmd = "psql $psql_opts -c \"DROP EXTENSION postgis\" $DB >> $REGRESS_LOG 2>&1";
$rv = system($cmd);
if ( $rv ) {
fail "Error encountered dropping EXTENSION POSTGIS", $REGRESS_LOG;
return 0;
}
return 1;
}
# Drop spatial from an existing database
sub uninstall_spatial
{
my $ok;
start_test("uninstall");
if ( $OPT_EXTENSIONS )
{
$ok = drop_spatial_extensions();
}
else
{
$ok = drop_spatial();
}
return $ok if ! $ok;
show_progress(); # on to objects count
$OBJ_COUNT_POST = count_db_objects();
if ( $OBJ_COUNT_POST != $OBJ_COUNT_PRE )
{
fail("Object count pre-install ($OBJ_COUNT_PRE) != post-uninstall ($OBJ_COUNT_POST)");
return 0;
}
pass("($OBJ_COUNT_PRE)");
return 1;
}
# Dump the database, return dump filename
sub dump_db
{
my $rv;
my $DBDUMP = $TMPDIR . '/' . $DB . '.dump';
foreach my $hook (@OPT_HOOK_BEFORE_DUMP)
{
print "Running before-dump-script $hook\n";
die unless load_sql_file($hook, 1);
}
print "Dumping database '${DB}'\n";
$rv = system("pg_dump -Fc -f${DBDUMP} ${DB} >> $REGRESS_LOG 2>&1");
if ( $rv ) {
fail("Could not dump ${DB}", $REGRESS_LOG);
return undef;
}
return ${DBDUMP};
}
# Restore the dump file passed as first argument
sub restore_db
{
my $rv;
my $DBDUMP = shift;
my $ext_dump = $OPT_EXTENSIONS && $OPT_UPGRADE_FROM !~ /^unpackaged/;
# If dump is not from extension-based database
# we need to prepare the target
unless ( $ext_dump )
{
if ( $OPT_UPGRADE_FROM =~ /^unpackaged(.*)/ ) {
die unless prepare_spatial($1);
} else {
die unless prepare_spatial();
}
}
if ( $ext_dump ) {
print "Restoring database '${DB}' using pg_restore\n";
$rv = system("pg_restore -d ${DB} ${DBDUMP} >> $REGRESS_LOG 2>&1");
} else {
print "Restoring database '${DB}' using postgis_restore.pl\n";
my $cmd = postgis_restore() . " ${DBDUMP} | psql --set ON_ERROR_STOP=1 -X ${DB} >> $REGRESS_LOG 2>&1";
$rv = system($cmd);
}
if ( $rv ) {
fail("Could not restore ${DB}", $REGRESS_LOG);
return 0;
}
if ( $OPT_WITH_TOPO )
{
# We need to re-add "topology" to the search_path as it is lost
# on dump/reload, see https://trac.osgeo.org/postgis/ticket/3454
my $psql_opts = "--no-psqlrc --variable ON_ERROR_STOP=true";
my $cmd = "psql $psql_opts -c \"SELECT topology.AddToSearchPath('topology')\" $DB >> $REGRESS_LOG 2>&1";
$rv = system($cmd);
if ( $rv ) {
fail("Error encountered adding topology to search path after restore", $REGRESS_LOG);
return 0;
}
}
foreach my $hook (@OPT_HOOK_AFTER_RESTORE)
{
print "Running after-restore-script $hook\n";
die unless load_sql_file($hook, 1);
}
return 1;
}
# Dump and restore the database
sub diff
{
my ($expected_file, $obtained_file) = @_;
my $diffstr = '';
if ( $sysdiff ) {
$diffstr = `diff --strip-trailing-cr -u $expected_file $obtained_file 2>&1`;
return $diffstr;
}
open(OBT, $obtained_file) || return "Cannot open $obtained_file\n";
open(EXP, $expected_file) || return "Cannot open $expected_file\n";
my $lineno = 0;
while (!eof(OBT) or !eof(EXP)) {
# TODO: check for premature end of one or the other ?
my $obtline=<OBT>;
my $expline=<EXP>;
$obtline =~ s/\r?\n$//; # Strip line endings
$expline =~ s/\r?\n$//; # Strip line endings
$lineno++;
if ( $obtline ne $expline ) {
my $diffln .= "$lineno.OBT: $obtline\n";
$diffln .= "$lineno.EXP: $expline\n";
$diffstr .= $diffln;
}
}
close(OBT);
close(EXP);
return $diffstr;
}