+ return $self;
+}
+
+sub process_query {
+ $::lxdebug->enter_sub();
+
+ my ($self, $dbh, $filename, $version_or_control) = @_;
+
+ my $form = $self->{form};
+ my $fh = IO::File->new($filename, "<:encoding(UTF-8)");
+ my $query = "";
+ my $sth;
+ my @quote_chars;
+
+ if (!$fh) {
+ return "No such file: $filename" if $self->{return_on_error};
+ $form->error("$filename : $!\n");
+ }
+
+ $dbh->begin_work();
+
+ while (<$fh>) {
+ # Remove DOS and Unix style line endings.
+ chomp;
+
+ for (my $i = 0; $i < length($_); $i++) {
+ my $char = substr($_, $i, 1);
+
+ # Are we inside a string?
+ if (@quote_chars) {
+ if ($char eq $quote_chars[-1]) {
+ pop(@quote_chars);
+ } elsif (length $quote_chars[-1] > 1
+ && substr($_, $i, length $quote_chars[-1]) eq $quote_chars[-1]) {
+ $i += length($quote_chars[-1]) - 1;
+ $char = $quote_chars[-1];
+ pop(@quote_chars);
+ }
+ $query .= $char;
+
+ } else {
+ my ($tag, $tag_end);
+ if (($char eq "'") || ($char eq "\"")) {
+ push(@quote_chars, $char);
+
+ } elsif ($char eq '$' # start of dollar quoting
+ && ($tag_end = index($_, '$', $i + 1)) > -1 # ends on same line
+ && (do { $tag = substr($_, $i + 1, $tag_end - $i - 1); 1 }) # extract tag
+ && $tag =~ /^ (?= [A-Za-z_] [A-Za-z0-9_]* | ) $/x) { # tag is identifier
+ push @quote_chars, $char = '$' . $tag . '$';
+ $i = $tag_end;
+ } elsif ($char eq "-") {
+ if ( substr($_, $i+1, 1) eq "-") {
+ # found a comment outside quote
+ last;
+ }
+ } elsif ($char eq ";") {
+
+ # Query is complete. Send it.
+
+ $sth = $dbh->prepare($query);
+ if (!$sth->execute()) {
+ my $errstr = $dbh->errstr;
+ return $errstr // '<unknown database error>' if $self->{return_on_error};
+ $sth->finish();
+ $dbh->rollback();
+ if (!ref $version_or_control || ref $version_or_control ne 'HASH' || !$version_or_control->{may_fail}) {
+ $form->dberror("The database update/creation did not succeed. " .
+ "The file ${filename} containing the following " .
+ "query failed:<br>${query}<br>" .
+ "The error message was: ${errstr}<br>" .
+ "All changes in that file have been reverted.")
+ }
+ }
+ $sth->finish();
+
+ $char = "";
+ $query = "";
+ }
+
+ $query .= $char;
+ }
+ }
+
+ # Insert a space at the end of each line so that queries split
+ # over multiple lines work properly.
+ if ($query ne '') {
+ $query .= @quote_chars ? "\n" : ' ';
+ }
+ }
+
+ if (ref($version_or_control) eq "HASH") {
+ $dbh->do("INSERT INTO " . $self->{schema} . "schema_info (tag, login) VALUES (" . $dbh->quote($version_or_control->{"tag"}) . ", " . $dbh->quote($form->{"login"}) . ")");
+ } elsif ($version_or_control) {
+ $dbh->do("UPDATE defaults SET version = " . $dbh->quote($version_or_control));
+ }
+ $dbh->commit();
+
+ $fh->close();
+
+ $::lxdebug->leave_sub();
+
+ # Signal "no error"
+ return undef;
+}
+
+# Process a Perl script which updates the database.
+# If the script returns 1 then the update was successful.
+# Return code "2" means "needs more interaction; unlock
+# the system and end current request".
+# All other return codes are fatal errors.
+sub process_perl_script {
+ $::lxdebug->enter_sub();
+
+ my ($self, $dbh, $filename, $version_or_control) = @_;
+
+ my %form_values = %$::form;
+
+ $dbh->begin_work;
+
+ # setup dbup_ export vars & run script
+ my %dbup_myconfig = map { ($_ => $::form->{$_}) } qw(dbname dbuser dbpasswd dbhost dbport dbconnect);
+ my $result = eval {
+ SL::DBUpgrade2::Base::execute_script(
+ file_name => $filename,
+ tag => $version_or_control->{tag},
+ dbh => $dbh,
+ myconfig => \%dbup_myconfig,
+ );
+ };
+
+ my $error = $EVAL_ERROR;
+
+ $dbh->rollback if 1 != ($result // -1);
+
+ return $error if $self->{return_on_error} && (1 != ($result // -1));
+
+ if (!defined($result)) {
+ print $::form->parse_html_template("dbupgrade/error", { file => $filename, error => $error });
+ $::dispatcher->end_request;
+ } elsif (1 != $result) {
+ SL::System::InstallationLock->unlock if 2 == $result;
+ $::dispatcher->end_request;
+ }
+
+ if (ref($version_or_control) eq "HASH") {
+ $dbh->do("INSERT INTO " . $self->{schema} . "schema_info (tag, login) VALUES (" . $dbh->quote($version_or_control->{tag}) . ", " . $dbh->quote($::form->{login}) . ")");
+ } elsif ($version_or_control) {
+ $dbh->do("UPDATE defaults SET version = " . $dbh->quote($version_or_control));
+ }
+
+ $dbh->commit if !$dbh->{AutoCommit} || $dbh->{BegunWork};
+
+ # Clear $::form of values that may have been set so that following
+ # Perl upgrade scripts won't have to work with old data (think of
+ # the usual 'continued' mechanism that's used for determining
+ # whether or not the upgrade form must be displayed).
+ delete @{ $::form }{ keys %{ $::form } };
+ $::form->{$_} = $form_values{$_} for keys %form_values;
+
+ $::lxdebug->leave_sub();
+
+ return undef;
+}
+
+sub process_file {
+ my ($self, $dbh, $filename, $version_or_control) = @_;
+
+ my $result = $filename =~ m/sql$/ ? $self->process_query( $dbh, $filename, $version_or_control)
+ : $self->process_perl_script($dbh, $filename, $version_or_control);
+
+ $::lxdebug->log_time("DB upgrade script '${filename}' finished");
+
+ return $result;
+}
+
+sub unapplied_upgrade_scripts {
+ my ($self, $dbh) = @_;
+
+ my @all_scripts = map { $_->{applied} = 0; $_ } $self->sort_dbupdate_controls;
+
+ my $query = qq|SELECT tag FROM | . $self->{schema} . qq|schema_info|;
+ my $sth = $dbh->prepare($query);
+ $sth->execute || $self->{form}->dberror($query);
+ while (my ($tag) = $sth->fetchrow_array()) {
+ $self->{all_controls}->{$tag}->{applied} = 1 if defined $self->{all_controls}->{$tag};
+ }
+ $sth->finish;
+
+ return grep { !$_->{applied} } @all_scripts;
+}
+
+sub apply_admin_dbupgrade_scripts {
+ my ($self, $called_from_admin) = @_;
+
+ return 0 if !$self->{auth};
+
+ my $dbh = $::auth->dbconnect;
+ my @unapplied_scripts = $self->unapplied_upgrade_scripts($dbh);
+
+ return 0 if !@unapplied_scripts;
+
+ $self->{form}->{login} ||= 'admin';
+
+ if ($called_from_admin) {
+ $self->{form}->{title} = $::locale->text('Dataset upgrade');
+ $self->{form}->header;
+ }
+
+ print $self->{form}->parse_html_template("dbupgrade/header", { dbname => $::auth->{DB_config}->{db} });
+
+ $::lxdebug->log_time("DB upgrades commencing");
+
+ foreach my $control (@unapplied_scripts) {
+ $::lxdebug->message(LXDebug->DEBUG2(), "Applying Update $control->{file}");
+ print $self->{form}->parse_html_template("dbupgrade/upgrade_message2", $control);
+
+ $self->process_file($dbh, "sql/Pg-upgrade2-auth/$control->{file}", $control);
+ }
+
+ $::lxdebug->log_time("DB upgrades finished");
+
+ print $self->{form}->parse_html_template("dbupgrade/footer", { is_admin => 1 }) if $called_from_admin;
+
+ return 1;