]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/support-scripts/make_concerto_from_evergreen_db.pl
LP#1901932 Wish List - Enhanced Concerto dataset
[Evergreen.git] / Open-ILS / src / support-scripts / make_concerto_from_evergreen_db.pl
1 #!/usr/bin/perl
2
3 # Copyright (C) 2022 MOBIUS
4 # Author: Blake Graham-Henderson <blake@mobiusconsortium.org>
5 #
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version.
10
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15 # ---------------------------------------------------------------
16
17 use Data::Dumper;
18 use XML::Simple;
19 use Getopt::Long;
20 use DBD::Pg;
21 use File::Path qw(make_path);
22 use Cwd qw/abs_path getcwd/;
23
24 our $CHUNKSIZE = 500;
25
26 our $cwd    = getcwd();
27 our %dbconf = ();
28 our $dbhost;
29 our $databaseName = 'postgres';
30 our $dbuser;
31 our $dbpass;
32 our $dbport;
33 our $seedDBName;
34 our $dbHandler;
35 our $dbHandlerSeed;
36 our $sample;
37 our $outputFolder;
38 our $debug                 = 0;
39 our $seedTableUsedRowCount = 0;
40 our $seedTableRowCount     = 100000;
41 our $doUpgrade             = 0;
42 our $doTestRestore         = 0;
43 our $doSeedOnly            = 0;
44 our $seedFrom              = 0;
45 our $nonInteractive        = 0;
46 our $egRepoPath;
47 our $egRepoDestBranch = 'master';
48 our @skipTables       = (
49     'auditor.*',
50     'search.*',
51     'reporter.*',
52     'metabib.*',
53     'actor.workstation_setting',
54     'acq.lineitem_attr',
55     'action_trigger.*',
56     'asset.copy_vis_attr_cache',
57     'authority.rec_descriptor',
58     'authority.simple_heading',
59     'authority.full_rec',
60     'authority.authority_linking',
61     'actor.org_unit_proximity',
62     'config.org_unit_setting_type_log',
63     'config.xml_transform',
64     'money.materialized_billable_xact_summary',
65     'serial.materialized_holding_code',
66     'vandelay.queued_bib_record_attr',
67         'config.print_template',
68         'config.workstation_setting_type',
69         'permission.grp_perm_map',
70 );
71
72 our @loadOrder = (
73     'actor.org_unit',
74     'actor.usr',
75     'acq.fund',
76     'acq.provider',
77
78     # call number data can include ##URI##,
79     # which biblio.record_entry triggers create, so, they go first
80     'asset.call_number',
81     'asset.uri',
82     'asset.uri_call_number_map',
83     'biblio.record_entry',
84     'biblio.monograph_part',
85     'acq.edi_account',
86     'acq.purchase_order',
87     'acq.lineitem',
88     'acq.lineitem_detail',
89     'acq.invoice',
90     'acq.invoice_entry',
91     'asset.copy_location',
92     'asset.copy',
93     'biblio.peer_type',
94     'authority.record_entry',
95     'money.grocery',
96     'money.billable_xact',
97     'money.billing',
98
99     # needs to come before actor.workstation
100     'money.bnm_desk_payment',
101 );
102
103 # a mechanism to call out special tables where we don't want the default behavior
104 # here we can teach this software which columns need to be used for comparison
105 # to seed data, and which columns we need to output into our SQL load file
106 our %tableColumnOverride = (
107
108     # the id column isn't important,
109     # we need to ensure that we abide by the unique constraints
110     # comp is "comparison", these columns are what we use to dedupe from seed data
111     # load is "load these columns only", any other columns in the table will receive PG defaults
112     'actor.org_unit_setting' => {
113         'comp' => [ 'org_unit', 'name' ],
114         'load' => [ 'org_unit', 'name', 'value' ]
115     },
116 );
117
118 our $help = "Usage: ./make_concerto_from_evergreen_db.pl [OPTION]...
119
120 This program automates the process of making a new dataset for the Evergreen code repository.
121 We need connection details to a postgres database. The provided database user needs to have
122 permissions to create databases.
123
124 This code will accept a pre-created seed database or it can create it's own. A blank \"seed\"
125 Evergreen database is needed for comparison reasons. It uses this as a reference to determine
126 which data is seed data and which data is not.
127
128 Mandatory arguments
129 --db-host           postgresql server hostname/IP
130 --db-user           Database Username to connect
131 --db-pass           Database password to connect
132 --db-port           Database port to connect
133 --evergreen-repo    Folder path to the root of the Evergreen git repository
134 --output-folder     Folder for our generated output
135
136 Optional
137 --perform-upgrade   This routine will restore previously generated dataset and upgrade it to match the
138                     provided Evergreen repository version.
139 --non-interactive   Suppress user input prompts
140 --db-name           Enhanced Concerto source postgres database name if you're generating a new dataset.
141 --test-restore      This option will cause the software to create a new database and populate it with the previously generated dataset
142 --debug             Set debug mode for more verbose output.
143 --create-seed-db    This option will create a new DB from the version of Evergreen that the dataset was from. Just the seed DB, no data.
144 --seed-from-egdbid  Supply the software the database ID number form which to create the seed database (used in conjunction with --create-seed-db)
145 --seed-db-name      Evergreen database name for the seed database, created with --create-database --create-schema, NOT --load-all-sample
146                     If you don't provide this, we will attempt to create one based upon a previously generated dataset located in
147                     --output-folder. However, this will be required if you do not have a previously generated dataset.
148
149 Examples:
150 Generate new dataset from existing DB:
151 ./make_concerto_from_evergreen_db.pl \
152 --db-host localhost \
153 --db-user evergreen \
154 --db-pass evergreen \
155 --db-port 5432 \
156 --db-name eg_enhanced \
157 --output-folder output \
158 --seed-db-name seed_from_1326 \
159 --evergreen-repo /home/opensrf/repos/Evergreen
160
161 If you don't have a seed database, you can omit it, and we'll make one based
162 upon the version we find in the file <output_folder>/config.upgrade_log.sql
163 ./make_concerto_from_evergreen_db.pl \
164 --db-host localhost \
165 --db-user evergreen \
166 --db-pass evergreen \
167 --db-port 5432 \
168 --db-name eg_enhanced \
169 --output-folder output \
170 --evergreen-repo /home/opensrf/repos/Evergreen
171
172 Or, you can have this software make a seed DB, and that's all it will do.
173 The version of Evergreen it will use will be found in <output_folder>/config.upgrade_log.sql
174 ./make_concerto_from_evergreen_db.pl \
175 --db-host localhost \
176 --db-user evergreen \
177 --db-pass evergreen \
178 --db-port 5432 \
179 --output-folder output \
180 --evergreen-repo /home/opensrf/repos/Evergreen \
181 --create-seed-db
182
183 Or, you can have this software make a seed DB based on your specified version of Evergreen
184 ./make_concerto_from_evergreen_db.pl \
185 --db-host localhost \
186 --db-user evergreen \
187 --db-pass evergreen \
188 --db-port 5432 \
189 --output-folder output \
190 --evergreen-repo /home/opensrf/repos/Evergreen \
191 --create-seed-db \
192 --seed-from-egdbid 1350
193
194 Upgrade a previously-created dataset. Use this when cutting new releases of Evergreen and you want to include
195 the enhanced dataset to match. It will use the current git branch found in the provided path to the EG repo.
196 ./make_concerto_from_evergreen_db.pl \
197 --db-host localhost \
198 --db-user evergreen \
199 --db-pass evergreen \
200 --db-port 5432 \
201 --output-folder output \
202 --evergreen-repo /home/opensrf/repos/Evergreen \
203 --perform-upgrade
204
205 Test the existing dataset. Create a new database and restore the dataset.
206 The software will first create a database that matches the version of Evergreen in the
207 dataset output folder, then restore the dataset into the newly created database.
208 ./make_concerto_from_evergreen_db.pl \
209 --db-host localhost \
210 --db-user evergreen \
211 --db-pass evergreen \
212 --db-port 5432 \
213 --output-folder output \
214 --evergreen-repo /home/opensrf/repos/Evergreen \
215 --test-restore
216
217 ";
218
219 GetOptions(
220     "db-host=s"          => \$dbhost,
221     "db-name=s"          => \$databaseName,
222     "db-user=s"          => \$dbuser,
223     "db-pass=s"          => \$dbpass,
224     "db-port=s"          => \$dbport,
225     "seed-db-name=s"     => \$seedDBName,
226     "output-folder=s"    => \$outputFolder,
227     "debug"              => \$debug,
228     "evergreen-repo=s"   => \$egRepoPath,
229     "perform-upgrade"    => \$doUpgrade,
230     "test-restore"       => \$doTestRestore,
231     "non-interactive"    => \$nonInteractive,
232     "create-seed-db"     => \$doSeedOnly,
233     "seed-from-egdbid=s" => \$seedFrom,
234 ) or printHelp();
235
236 checkCMDArgs();
237
238 setupDB();
239
240 createSeedDB() if $doSeedOnly;
241
242 start() if !$doUpgrade && !$doTestRestore;
243
244 upgrade() if $doUpgrade;
245
246 testRestore() if $doTestRestore;
247
248 sub start {
249
250     # make the output folder if it doesn't exist
251     make_path(
252         $outputFolder,
253         {
254             chmod => 0775,
255         }
256     ) if ( !( -e $outputFolder ) );
257
258     my $tempDB = checkSeed();
259
260     # Gather a list of Evergreen Tables to process
261     my @evergreenTables = @{ getSchemaTables() };
262     my $loadAll =
263         "BEGIN;\n\n-- stop on error\n\\set ON_ERROR_STOP on\n\n"
264       . "-- Ignore constraints until we're done\nSET CONSTRAINTS ALL DEFERRED;\n\n";
265     my @loadTables = ();
266     while ( $#evergreenTables > -1 ) {
267         my $thisTable = shift @evergreenTables;
268         my $columnRef = shift @evergreenTables;
269         if ( checkTableForInclusion($thisTable) ) {
270             my $thisFile = $outputFolder . "/$thisTable.sql";
271             print "Processing $thisTable > $thisFile\n";
272             unlink $thisFile if -e $thisFile;
273             my $thisFhandle;
274             open( $thisFhandle, '>> ' . $thisFile );
275             binmode( $thisFhandle, ":utf8" );
276             my $lines = tableHandler( $thisTable, $columnRef, $thisFhandle );
277             close($thisFhandle);
278             unlink $thisFile if ( -e $thisFile && $lines == 0 );
279
280             # upgrade_log is written as part of the dataset, but not loaded.
281             # it's used purely to figure out what version of Evergreen we are on
282             # during this execution. So, later, when we run the upgrade procedure, we
283             # can figure out where we were when this was generated.
284             push( @loadTables, $thisTable )
285               if ( -e $thisFile && $lines > 0 && !( $thisFile =~ /config\.upgrade_log/ ) );
286             undef $lines;
287         }
288         else {
289             print "Skipping: $thisTable\n" if $debug;
290         }
291     }
292     $loadAll = loadTableOrderMaker( $loadAll, \@loadTables );
293
294     $loadAll .= "SELECT SETVAL('money.billable_xact_id_seq', (SELECT MAX(id) FROM money.billing));\n\n";
295     $loadAll .= "SELECT SETVAL('config.remote_account_id_seq', (SELECT MAX(id) FROM config.remote_account));\n\n";
296     $loadAll .= "SELECT SETVAL('money.payment_id_seq', (SELECT MAX(id) FROM money.payment));\n\n";
297     $loadAll .= "SELECT SETVAL('asset.copy_id_seq', (SELECT MAX(id) FROM asset.copy));\n\n";
298     $loadAll .= "SELECT SETVAL('vandelay.queue_id_seq', (SELECT MAX(id) FROM vandelay.queue));\n\n";
299     $loadAll .= "SELECT SETVAL('vandelay.queued_record_id_seq', (SELECT MAX(id) FROM vandelay.queued_record));\n\n";
300
301     $loadAll .= "COMMIT;\n";
302     print "Writing loader > $outputFolder/load_all.sql\n";
303     open( OUT, "> $outputFolder/load_all.sql" );
304     binmode( OUT, ":utf8" );
305     print OUT $loadAll;
306     close(OUT);
307     $dbHandler->disconnect;
308     $dbHandlerSeed->disconnect;
309     dropDB($tempDB) if $tempDB;
310     exit if !$doUpgrade;
311 }
312
313 sub upgrade {
314     my $restoreDBName = getNextAvailableDBName();
315     print "Using database name: $restoreDBName\n" if $debug;
316     my $currentEGDBVersionNum = getLastEGDBVersionFromOutput();
317     userInput("Found this DB version from output: $currentEGDBVersionNum");
318     my $previousGitBranch = checkoutEGMatchingGitVersion($currentEGDBVersionNum);
319     populateDBFromCurrentGitBranch( $restoreDBName, 0 );
320
321     # now we have a temp database full of our concerto set
322     # created by the version of Evergreen that origainally made the dataset
323     # Now, swtich the repo back to the original branch that the user had
324     gitCheckoutBranch( $previousGitBranch, 0, 1 );
325     loadThisDataset($restoreDBName);
326     upgradeDB( $restoreDBName, $currentEGDBVersionNum );
327     $seedDBName = getNextAvailableDBName();
328     populateDBFromCurrentGitBranch( $seedDBName, 0 );
329
330     dbhandler_setupConnection( $restoreDBName, $dbconf{"dbhost"}, $dbconf{"dbuser"}, $dbconf{"dbpass"},
331         $dbconf{"port"} );
332     dbhandler_setupConnection( $seedDBName, $dbconf{"dbhost"}, $dbconf{"dbuser"}, $dbconf{"dbpass"}, $dbconf{"port"},
333         1 );
334     start();
335     userInput( "Done! If you'd like, you can pause here and take a peek at the generated databases before "
336           . "I drop them:\nFull DB: $restoreDBName\nSeed: $seedDBName" );
337     $dbHandler->disconnect;
338     $dbHandlerSeed->disconnect;
339     dropDB($restoreDBName);
340     dropDB($seedDBName);
341     exit;
342 }
343
344 sub testRestore {
345     my $restoreDB = checkSeed(1);
346     loadThisDataset($restoreDB);
347     print "Created database: $restoreDB from provided output folder: $outputFolder\n";
348     exit;
349 }
350
351 sub createSeedDB {
352     my $currentEGDBVersionNum = $seedFrom ? $seedFrom : getLastEGDBVersionFromOutput();
353     my $restoreDBName         = getNextAvailableDBName( "seed_db_$currentEGDBVersionNum" . "_" );
354     print "Using database name: $restoreDBName\n" if $debug;
355     my $previousGitBranch = checkoutEGMatchingGitVersion($currentEGDBVersionNum);
356     populateDBFromCurrentGitBranch( $restoreDBName, 0 );
357     gitCheckoutBranch( $previousGitBranch, 0, 1 );
358     print "Created a fresh seed DB from Evergreen Version: $currentEGDBVersionNum\n" . "DB name: $restoreDBName\n";
359     exit;
360 }
361
362 sub checkSeed {
363     my $forceNewDB = shift || 0;
364     my $valid      = 0;
365     my $createdDB  = 0;
366     if ($seedDBName) {
367         my $query = "";
368
369         # a sanity check to make sure we can connect to the database and run a query
370         my @res = @{ dbhandler_query( "SELECT MAX(id) FROM biblio.record_entry", undef, 1 ) };
371         $valid = 1 if ( $#res > -1 );
372     }
373
374     # Seed database is missing, let's create one
375     if ( !$valid || $forceNewDB ) {
376         $seedDBName = getNextAvailableDBName();
377         $createdDB  = $seedDBName;
378         populateDBFromCurrentGitBranch( $seedDBName, 0 );
379         dbhandler_setupConnection( $seedDBName, $dbconf{"dbhost"}, $dbconf{"dbuser"}, $dbconf{"dbpass"},
380             $dbconf{"port"}, 1 );
381     }
382     return $createdDB;
383 }
384
385 sub loadTableOrderMaker {
386     my $loadString        = shift;
387     my $includedTablesRef = shift;
388     my @includedTables    = @{$includedTablesRef};
389     my %used              = ();
390
391     # Loop through the pre-defined order, and check those off
392     foreach (@loadOrder) {
393         my $otable = $_;
394         my $pos    = 0;
395         foreach (@includedTables) {
396             if ( $includedTables[$pos] eq $otable ) {
397                 $loadString .= makeLoaderLine($_);
398                 $used{$pos} = 1;
399             }
400             $pos++;
401         }
402         undef $pos;
403     }
404
405     # include the rest
406     my $pos = 0;
407     foreach (@includedTables) {
408         if ( not defined $used{$pos} ) {
409             $loadString .= makeLoaderLine($_);
410             $used{$pos} = 1;
411         }
412         $pos++;
413     }
414     undef $pos;
415
416     return $loadString;
417 }
418
419 sub makeLoaderLine {
420     my $table = shift;
421     my $ret   = "\\echo loading $table\n";
422     $ret .= "\\i $table.sql\n\n";
423     return $ret;
424 }
425
426 sub tableHandler {
427     my $table          = shift;
428     my $tableColumnRef = shift;
429     my $fHandle        = shift;
430     my $funcHandler    = $table;
431     my $rowCount       = 0;
432     $funcHandler =~ s/\./_/g;
433     $funcHandler .= '_handler';
434
435     # if some tables need handled special, make a sub with the table name AKA sub biblio_record_entry_handler
436     if ( functionExists($funcHandler) ) {
437         my $perlcode = '$rowCount = ' . $funcHandler . '($table, $tableColumnRef, $fHandle);';
438         eval $perlcode;
439     }
440     else {
441         $rowCount = standardHandler( $table, $tableColumnRef, $fHandle );
442     }
443     return $rowCount;
444 }
445
446 sub columnOrder {
447     my $colRef  = shift;
448     my %columns = %{$colRef};
449     my @order   = ();
450     my @ret     = ();
451
452     while ( ( my $colname, my $colpos ) = each(%columns) ) {
453         push( @order, $colpos );
454     }
455     @order = sort { $a <=> $b } @order;
456     foreach (@order) {
457         my $thisPOS = $_;
458         while ( ( my $colname, my $colpos ) = each(%columns) ) {
459             if ( $colpos == $thisPOS ) {
460                 push( @ret, $colname );
461             }
462         }
463         undef $thisPOS;
464     }
465     return \@ret;
466 }
467
468 sub functionExists {
469
470     # no strict 'refs';
471     my $funcname = shift;
472     return \&{$funcname} if defined &{$funcname};
473     return;
474 }
475
476 sub getDataChunk {
477     my $query  = shift;
478     my $offset = shift;
479     $query .= "\nLIMIT $CHUNKSIZE OFFSET $offset\n";
480     print $query if $debug;
481     my @results = @{ dbhandler_query($query) };
482     return \@results;
483 }
484
485 sub standardHandler {
486     my $table          = shift;
487     my $tableColumnRef = shift;
488     my $fHandle        = shift;
489     my $omitColumnsRef = shift;
490     my %omitColumn     = %{$omitColumnsRef} if $omitColumnsRef;
491     my $query          = "SELECT ";
492     my $sqlOutTop      = "COPY $table (";
493     my $order          = "ORDER BY ";
494     my @colOrder       = @{ columnOrder($tableColumnRef) };
495     my $colCount       = 1;
496     my $rowCount       = 0;
497
498     foreach (@colOrder) {
499         my $include    = 0;
500         my $currentCol = $_;
501         if ( $tableColumnOverride{$table} ) {
502             foreach ( @{ $tableColumnOverride{$table}{'load'} } ) {
503                 $include = 1 if ( $_ eq $currentCol );
504             }
505         }
506         else {
507             # if the calling code wants to remove some columns, we skip them here
508             if ( ( !$omitColumnsRef ) || ( not defined $omitColumn{$currentCol} ) ) {
509                 $include = 1;
510             }
511             else {
512                 print "removing column: $table.$currentCol\n" if $debug;
513             }
514         }
515         if ($include) {
516             $query     .= "$currentCol, ";
517             $sqlOutTop .= "$currentCol, ";
518             $order     .= "$colCount, ";
519             $colCount++;
520         }
521         undef $include;
522     }
523     $query     = substr( $query,     0, -2 );    # remove the trailing comma+space
524     $sqlOutTop = substr( $sqlOutTop, 0, -2 );    # remove the trailing comma+space
525     $order     = substr( $order,     0, -2 );    # remove the trailing comma+space
526     $query .= " FROM ONLY $table \n$order";
527
528     # makes it possible to not have to quote strings, dates, etc.
529     $sqlOutTop .= ") FROM stdin;\n";
530
531     my $offset    = 0;
532     my @data      = @{ getDataChunk( $query, $offset ) };
533     my $firstTime = 1;
534     while ( $#data > 0 )                         #skipping column def metadata at the end of the array
535     {
536         my $sqlOut              = $sqlOutTop;
537         my @differencesFromSeed = @{ removeDuplicateStockData( \@data, $table, $firstTime ) };
538         $firstTime = 0;
539         my $outCount = 0;
540         foreach (@differencesFromSeed) {
541             $outCount++;
542             $rowCount++;
543             my $row = $_;
544             foreach ( @{$row} ) {
545                 if ( !defined $_ ) {
546                     $_ = '\N';
547                 }
548                 else {
549                     # escape reserved tokens
550                     $_ =~ s/\\/\\\\/g;    # all backslashes need escaped
551                     $_ =~ s/\n/\\n/g;     # newline
552                     $_ =~ s/\r/\\r/g;     # carriage return
553                     $_ =~ s/\t/\\t/g;     # tab
554                     $_ =~ s/\v/\\v/g;     # vertical tab
555                     $_ =~ s/\f/\\f/g;     # form feed
556                 }
557                 $sqlOut .= "$_\t";
558             }
559             $sqlOut = substr( $sqlOut, 0, -1 );
560             $sqlOut .= "\n";
561         }
562         print $fHandle $sqlOut if $outCount > 0;
563
564         # postgres sql syntax for finish of stdin
565         print $fHandle "\\.\n\n" if $outCount > 0;
566
567         undef $sqlOut;
568         undef $outCount;
569         $offset += $CHUNKSIZE;
570         @data = @{ getDataChunk( $query, $offset ) };
571     }
572     print $fHandle injectSequenceUpdate($table);
573
574     undef @data;
575     undef $sqlOutTop;
576     undef $query;
577
578     return $rowCount;
579 }
580
581 sub biblio_record_entry_handler {
582     my $table          = shift;
583     my $tableColumnRef = shift;
584     my $fHandle        = shift;
585     my %omitColumns    = ( 'vis_attr_vector' => 1 );
586     return standardHandler( $table, $tableColumnRef, $fHandle, \%omitColumns );
587 }
588
589 sub actor_workstation_handler {
590     my $table          = shift;
591     my $tableColumnRef = shift;
592     my $fHandle        = shift;
593     my $lines          = standardHandler( $table, $tableColumnRef, $fHandle, undef );
594     print $fHandle <<'splitter';
595
596 -- a case where the deleted workstation had payments
597 INSERT INTO actor.workstation(id,name,owning_lib)
598 SELECT missingworkstation.id, aou.shortname||FLOOR(RANDOM() * 100 + 1)::INT, 1
599     FROM
600     (
601         SELECT
602         DISTINCT mbdp.cash_drawer AS id
603         FROM
604         money.bnm_desk_payment mbdp
605         LEFT JOIN actor.workstation aw ON (mbdp.cash_drawer = aw.id)
606         WHERE
607         aw.id IS NULL
608     ) missingworkstation
609 JOIN actor.org_unit aou ON (aou.id=1);
610
611 -- anonymize workstation names
612 UPDATE
613 actor.workstation aw
614     SET name=aou.shortname||'-'||aw.id
615 FROM actor.org_unit aou
616 WHERE
617     aou.id=aw.owning_lib;
618 splitter
619
620     return $lines;
621 }
622
623 sub injectSequenceUpdate {
624     my $table      = shift;
625     my @schema     = split( /\./, $table );
626     my $schemaName = @schema[0];
627     my $ret        = '';
628     my $query      = <<'splitter';
629 SELECT * FROM
630 (
631 SELECT t.oid::regclass AS table_name,
632        a.attname AS column_name,
633        s.relname AS sequence_name
634 FROM pg_class AS t
635    JOIN pg_attribute AS a
636       ON a.attrelid = t.oid
637    JOIN pg_depend AS d
638       ON d.refobjid = t.oid
639          AND d.refobjsubid = a.attnum
640    JOIN pg_class AS s
641       ON s.oid = d.objid
642 WHERE d.classid = 'pg_catalog.pg_class'::regclass
643   AND d.refclassid = 'pg_catalog.pg_class'::regclass
644   AND d.deptype IN ('i', 'a')
645   AND t.relkind IN ('r', 'P')
646   AND s.relkind = 'S'
647 ) AS a
648 WHERE
649     a.table_name = '!!tbname!!'::regclass
650 splitter
651
652     $query =~ s/!!tbname!!/$table/g;
653     my @results = @{ dbhandler_query($query) };
654     while ( $#results > 0 ) {
655         my $this    = shift @results;
656         my @row     = @{$this};
657         my $colname = @row[1];
658         my $seqname = @row[2];
659         $ret .= "\\echo sequence update column: !!colname!!\n";
660         $ret .= "SELECT SETVAL('!!seqname!!', (SELECT MAX(!!colname!!) FROM !!tbname!!));\n";
661         $ret =~ s/!!tbname!!/$table/g;
662         $ret =~ s/!!colname!!/$colname/g;
663         $ret =~ s/!!seqname!!/$schemaName.$seqname/g;
664         undef $colname;
665         undef $seqname;
666     }
667
668     return $ret;
669 }
670
671 sub removeDuplicateStockData {
672     my $resultsRef = shift;
673     my $table      = shift;
674     my $firstTime  = shift;
675     $seedTableRowCount = getTableRowCount( $table, 1 ) if ($firstTime);
676     $seedTableUsedRowCount = 0 if ($firstTime);
677     my @ret         = ();
678     my @results     = @{$resultsRef};
679     my $colRef      = @results[$#results];
680     my %columns     = %{$colRef};
681     my $resultsPOS  = 0;
682     my $removeCount = 0;
683
684     foreach (@results) {
685         my $rowRef = $_;
686         last if $resultsPOS == $#results;
687         $resultsPOS++;
688
689         # don't bother if we know we've already used up the seed data table
690         if ( $seedTableUsedRowCount < $seedTableRowCount ) {
691             my @row    = @{$rowRef};
692             my @vals   = ();
693             my $pos    = 0;
694             my $select = "SELECT ";
695             my $where  = "WHERE 1=1";
696
697             # special handler for some tables
698             if ( $tableColumnOverride{$table} ) {
699                 foreach ( @{ $tableColumnOverride{$table}{'comp'} } ) {
700                     my $colname = $_;
701                     my $colpos  = $columns{$colname};
702                     $select .= "$colname, ";
703                     if ( defined @row[$colpos] ) {
704                         $pos++;
705                         $where .= " AND $colname = \$$pos";
706                         push( @vals, @row[$colpos] );
707                     }
708                     else {
709                         $where .= " AND $colname is null";
710                     }
711                 }
712             }
713             else {
714                 while ( ( my $colname, my $colpos ) = each(%columns) ) {
715
716                     # compare ID numbers when there is an ID column, otherwise, compare the rest of the columns
717                     if ( ( $colname ne 'id' && not defined $columns{'id'} ) || $colname eq 'id' ) {
718
719                         $select .= "$colname, ";
720
721                         # if it's null data, the SQL needs to be "is null", not "="
722                         if ( defined @row[$colpos] ) {
723                             $pos++;
724                             $where .= " AND $colname = \$$pos";
725                             push( @vals, @row[$colpos] );
726                         }
727                         else {
728                             $where .= " AND $colname is null";
729                         }
730                     }
731                 }
732             }
733
734             # remove the trailing comma+space
735             $select = substr( $select, 0, -2 );
736             $select .= "\nFROM ONLY $table\n$where\n";
737             print $select if $debug;
738             print Dumper( \@vals ) if $debug;
739             my @res = @{ dbhandler_query( $select, \@vals, 1 ) };
740
741             # seed data doesn't have a match, want this row for our new dataset
742             if ( $#res == 0 ) {
743                 push( @ret, $rowRef );
744             }
745             else {
746                 $removeCount++;
747
748                 # Each time we match seed data, we count. If the number of rows found equals the
749                 # number of total rows, we don't need to keep checking back on the seed database
750                 $seedTableUsedRowCount++;
751             }
752             undef @res;
753             undef $select;
754             undef $where;
755             undef $pos;
756             undef @vals;
757         }
758         else {
759             # exhausted the seed database table rows, this data can just blindly get added to the
760             # result set
761             push( @ret, $rowRef );
762         }
763     }
764
765     print "Removed $removeCount rows (exists in seed data)\n" if $removeCount;
766     return \@ret;
767 }
768
769 sub getSchemaTables {
770     my @ret       = ();
771     my @tableTest = ();
772     my $query     = <<'splitter';
773 SELECT schemaname||'.'||tablename
774 FROM pg_catalog.pg_tables
775 WHERE
776 schemaname NOT IN('pg_catalog','information_schema')
777 ORDER BY 1
778
779 splitter
780
781     my @results   = @{ dbhandler_query($query) };
782     my $resultPos = 0;
783     foreach (@results) {
784         my $row = $_;
785         my @row = @{$row};
786         if ( getTableRowCount( @row[0] ) > 0 ) {
787             push( @ret, lc @row[0] );
788             push( @ret, getTableColumnNames( @row[0] ) );
789         }
790         else {
791             print "no rows in @row[0]\n" if $debug;
792         }
793         $resultPos++;
794         last if $#results == $resultPos;    # ignore the column header metadata
795     }
796
797     undef $resultPos;
798
799     return \@ret;
800 }
801
802 sub getTableRowCount {
803     my $table   = shift;
804     my $seed    = shift;
805     my $ret     = 0;
806     my $query   = "SELECT count(*) FROM ONLY $table";
807     my @results = @{ dbhandler_query( $query, undef, $seed ) };
808     foreach (@results) {
809         my $row = $_;
810         my @row = @{$row};
811         $ret = @row[0];
812         last;    # ignore column header metadata
813     }
814
815     return $ret;
816 }
817
818 sub getTableColumnNames {
819     my $table   = shift;
820     my $ret     = 0;
821     my $query   = "SELECT * FROM $table LIMIT 1";
822     my @results = @{ dbhandler_query($query) };
823     $ret = pop @results;
824     return $ret;
825 }
826
827 sub checkTableForInclusion {
828     my $table  = shift;
829     my @schema = split( /\./, $table );
830     foreach (@skipTables) {
831         return 0 if ( lc $table eq lc $_ );
832         if ( $_ =~ /\*$/ ) {
833             my @thisSchema = split( /\./, $_ );
834             return 0 if ( lc @schema[0] eq lc @thisSchema[0] );
835         }
836     }
837     return 1;
838 }
839
840 sub logfile_readFile {
841     my $file   = shift;
842     my $trys   = 0;
843     my $failed = 0;
844     my @lines;
845
846     #print "Attempting open\n";
847     if ( -e $file ) {
848         my $worked = open( inputfile, '< ' . $file );
849         if ( !$worked ) {
850             print "******************Failed to read file*************\n";
851         }
852         binmode( inputfile, ":utf8" );
853         while ( !( open( inputfile, '< ' . $file ) ) && $trys < 100 ) {
854             print "Trying again attempt $trys\n";
855             $trys++;
856             sleep(1);
857         }
858         if ( $trys < 100 ) {
859
860             #print "Finally worked... now reading\n";
861             @lines = <inputfile>;
862             close(inputfile);
863         }
864         else {
865             print "Attempted $trys times. COULD NOT READ FILE: $file\n";
866         }
867         close(inputfile);
868     }
869     else {
870         print "File does not exist: $file\n";
871     }
872     return \@lines;
873 }
874
875 sub upgradeDB {
876     my $restoreDBName         = shift;
877     my $currentEGDBVersionNum = shift;
878     print "Upgrading DB: $restoreDBName, starting from stamp: $currentEGDBVersionNum\n" if $debug;
879     my @upgradeScripts = @{ findAllUpgradeScriptsAfterSpecifiedVersion($currentEGDBVersionNum) };
880     foreach (@upgradeScripts) {
881         execPSQLCMD( "-t -v eg_version=\"'enhanced_concerto_script'\" -f '$_'", $restoreDBName );
882     }
883 }
884
885 sub dropDB {
886     my $dbname = shift;
887     execPSQLCMD( "-c 'DROP DATABASE IF EXISTS $dbname'", "postgres" );
888 }
889
890 sub getNextAvailableDBName {
891     my $prefDBNamePrefix = shift || "concertoscript";
892     my $query            = "SELECT datname FROM pg_database WHERE datistemplate = false";
893     my @results          = @{ dbhandler_query($query) };
894
895     # remove column defs, we don't need them here
896     pop @results;
897     my %names = ();
898     foreach (@results) {
899         my @row = @{$_};
900         $names{ @row[0] } = 1;
901     }
902     my $loop = 0;
903     $loop++ while ( $names{ $prefDBNamePrefix . $loop } );
904     return $prefDBNamePrefix . $loop;
905 }
906
907 sub getLastEGDBVersionFromOutput {
908     print "Reading previously generated data from $outputFolder/config.upgrade_log.sql\n" if $debug;
909     my $file       = $outputFolder . "/config.upgrade_log.sql";
910     my @lines      = @{ logfile_readFile($file) };
911     my $highestNum = 0;
912     my $tableLine  = shift @lines;
913     $tableLine =~ s/^[^\(]*?\(([^\)]*?)\).*$/$1/g;
914
915     #Hunt down the column for "version"
916     my @cols          = split( /,/, $tableLine );
917     my $versionColPOS = 0;
918     my $pos           = 0;
919     foreach (@cols) {
920         my $test = $_;
921         $test =~ s/\s+$//g;
922         $versionColPOS = $pos if ( lc $test eq 'version' );
923         undef $test;
924         $pos++;
925     }
926     foreach (@lines) {
927         my @cols        = split( /\t/, $_ );
928         my $thisVersion = @cols[$versionColPOS];
929         next if ( $thisVersion =~ /\D/ );    # ignore if there are non-numerics
930         $highestNum = $thisVersion if ( $thisVersion + 0 > $highestNum );
931     }
932     print "Result: $highestNum\n" if $debug;
933     return $highestNum;
934 }
935
936 sub checkoutEGMatchingGitVersion {
937     my $versionNum  = shift;
938     my $versionFile = findMatchingUpgradeFile($versionNum);
939     print $versionFile . "\n";
940     my $commit = findGitCommitForFile($versionFile);
941     userInput("Found this Evergreen git commit: $commit");
942     return gitCheckoutBranch( $commit, 1, 0 );    # stash anything pending, and don't apply after branch switch
943 }
944
945 sub findMatchingUpgradeFile {
946     my $upgradeNum      = shift;
947     my $egUpgradeFolder = "Open-ILS/src/sql/Pg/upgrade";
948     my $ret             = '';
949     my $folder          = $egRepoPath . '/' . $egUpgradeFolder;
950     opendir( my $dh, $folder ) || die "Can't open $folder: $!";
951     while ( readdir $dh ) {
952         if ( $_ =~ /^$upgradeNum.*?\.sql/ ) {
953             $ret = $egUpgradeFolder . '/' . $_;
954         }
955     }
956     closedir $dh;
957     return $ret;
958 }
959
960 sub findAllUpgradeScriptsAfterSpecifiedVersion {
961     my $upgradeNum      = shift;
962     my $egUpgradeFolder = "Open-ILS/src/sql/Pg/upgrade";
963     my %map             = ();
964     my @ret             = ();
965     my @sortme          = ();
966     my $folder          = $egRepoPath . '/' . $egUpgradeFolder;
967     opendir( my $dh, $folder ) || die "Can't open $folder: $!";
968     while ( readdir $dh ) {
969         if ( $_ ne '.' && $_ ne '..' && $_ =~ /^\d+\..*/ ) {
970             my $thisStamp = $_;
971             $thisStamp =~ s/^([^\.]*)\..*$/$1/g;
972             if ( $thisStamp + 0 > $upgradeNum + 0 ) {
973                 push( @sortme, $thisStamp );
974                 $map{$thisStamp} = $egRepoPath . '/' . $egUpgradeFolder . '/' . $_;
975             }
976         }
977     }
978     closedir $dh;
979     @sortme = sort { $a <=> $b } @sortme;
980     push( @ret, $map{$_} ) foreach (@sortme);
981     return \@ret;
982 }
983
984 sub findGitCommitForFile {
985     my $file = shift;
986     my $ret  = '';
987     my $exec = "cd '$egRepoPath' && git log $file";
988
989     my $return   = execSystemCMDWithReturn($exec);
990     my @retLines = split( /\n/, $return );
991     foreach (@retLines) {
992         if ( $_ =~ /^\s*commit\s+[^\s]*$/ ) {
993             $ret = $_;
994             $ret =~ s/^\s*commit\s+([^\s]*)$/$1/g;
995         }
996     }
997     print "Found commit: $ret\n" if $debug;
998     return $ret;
999 }
1000
1001 sub gitCheckoutBranch {
1002     print "Headed into gitCheckoutBranch()\n" if $debug;
1003     my $branch       = shift;
1004     my $stash        = shift || 0;
1005     my $restoreStash = shift || 0;
1006
1007     # get the current branch so we can switch back
1008     my $exec = "cd '$egRepoPath' && git rev-parse --abbrev-ref HEAD";
1009     my $ret  = execSystemCMDWithReturn($exec);
1010     $exec = "cd '$egRepoPath'";
1011     $exec .= " && git stash" if $stash;
1012     $exec .= " && git checkout $branch";
1013     $exec .= " && git stash apply" if $restoreStash;
1014     userInput("Executing: '$exec'");
1015     execSystemCMD( $exec, 1 );
1016     userInput("Done Executing: '$exec'");
1017     print "Done with gitCheckoutBranch()\n" if $debug;
1018     return $ret;
1019 }
1020
1021 sub populateDBFromCurrentGitBranch {
1022     my $db                 = shift;
1023     my $loadConcerto       = shift || 0;
1024     my $eg_db_config_stock = "Open-ILS/src/support-scripts/eg_db_config.in";
1025     my $eg_db_config_temp  = "Open-ILS/src/support-scripts/eg_db_config";
1026     my $eg_config_stock    = "Open-ILS/src/extras/eg_config.in";
1027     my $eg_config_temp     = "Open-ILS/src/extras/eg_config";
1028     fix_eg_config( $egRepoPath . "/$eg_db_config_stock", $egRepoPath . "/$eg_db_config_temp" );
1029     fix_eg_config( $egRepoPath . "/$eg_config_stock",    $egRepoPath . "/$eg_config_temp" );
1030     my $exec = "cd '$egRepoPath' && perl '$eg_db_config_temp'";
1031     $exec .= " --create-database --create-schema";
1032     $exec .= " --user " . $dbconf{"dbuser"};
1033     $exec .= " --password " . $dbconf{"dbpass"};
1034     $exec .= " --hostname " . $dbconf{"dbhost"};
1035     $exec .= " --port " . $dbconf{"port"};
1036     $exec .= " --database $db";
1037     execSystemCMD($exec);
1038     loadThisDataset($db) if $loadConcerto;
1039 }
1040
1041 sub loadThisDataset {
1042     my $db = shift;
1043     chdir($outputFolder);
1044     print "LOADING DATA\nThis can take a few minutes...\n";
1045     execPSQLCMD( "-f load_all.sql", $db );
1046     chdir($cwd);
1047 }
1048
1049 sub fix_eg_config {
1050     my $inFile     = shift;
1051     my $outputFile = shift;
1052
1053     unlink $outputFile if -e $outputFile;
1054     my $outHandle;
1055     open( $outHandle, '>> ' . $outputFile );
1056     binmode( $outHandle, ":utf8" );
1057
1058     my @lines      = @{ logfile_readFile($inFile) };
1059     my %replaceMap = (
1060         '\@prefix\@'                => '/openils',
1061         '\@datarootdir\@'           => '${prefix}/share',
1062         '\@BUILDILSCORE_TRUE\@'     => '',
1063         '\@BUILDILSWEB_TRUE\@'      => '',
1064         '\@BUILDILSREPORTER_TRUE\@' => '',
1065         '\@BUILDILSCLIENT_TRUE\@'   => '',
1066         '\@PACKAGE_STRING\@'        => '',
1067         '\@bindir\@'                => '${exec_prefix}/bin',
1068         '\@libdir\@'                => '${exec_prefix}/lib',
1069         '\@TMP\@'                   => '/tmp',
1070         '\@includedir\@'            => '${prefix}/include',
1071         '\@APXS2\@'                 => '',
1072         '\@sysconfdir\@'            => '/openils/conf',
1073         '\@LIBXML2_HEADERS\@'       => '',
1074         '\@APR_HEADERS\@'           => '',
1075         '\@APACHE2_HEADERS\@'       => '',
1076         '\@localstatedir\@'         => '',
1077         '\@docdir\@'                => '',
1078     );
1079
1080     foreach (@lines) {
1081         my $line = $_;
1082
1083         # this file has some placeholders. We're not going to make use of
1084         # this feature in the script, but it won't run unless those are populated
1085         while ( ( my $key, my $value ) = each(%replaceMap) ) {
1086             $line =~ s/$key/$value/g;
1087         }
1088         print $outHandle $line;
1089     }
1090     chmod( 0755, $outHandle );
1091     close($outHandle);
1092 }
1093
1094 sub execSystemCMD {
1095     my $cmd          = shift;
1096     my $ignoreErrors = shift;
1097     print "executing $cmd\n" if $debug;
1098     system($cmd) == 0;
1099     if ( !$ignoreErrors && ( $? == -1 ) ) {
1100         die "system '$cmd' failed: $?";
1101     }
1102     print "Done executing $cmd\n" if $debug;
1103 }
1104
1105 sub execSystemCMDWithReturn {
1106     my $cmd       = shift;
1107     my $dont_trim = shift;
1108     my $ret;
1109     print "executing $cmd\n" if $debug;
1110     open( DATA, $cmd . '|' );
1111     my $read;
1112     while ( $read = <DATA> ) {
1113         $ret .= $read;
1114     }
1115     close(DATA);
1116     return 0 unless $ret;
1117     $ret = substr( $ret, 0, -1 ) unless $dont_trim;    #remove the last character of output.
1118     print "Done executing $cmd\n" if $debug;
1119     return $ret;
1120 }
1121
1122 sub execPSQLCMD {
1123     my $cmd = shift;
1124     my $db  = shift;
1125     $ENV{'PGUSER'}     = $dbconf{"dbuser"};
1126     $ENV{'PGPASSWORD'} = $dbconf{"dbpass"};
1127     $ENV{'PGPORT'}     = $dbconf{"port"};
1128     $ENV{'PGHOST'}     = $dbconf{"dbhost"};
1129     $ENV{'PGDATABASE'} = $db;
1130     my $pcmd = "psql $cmd";    #2>&1";
1131     print "Running:\n$pcmd\n";
1132     `$pcmd`;
1133 }
1134
1135 sub dbhandler_setupConnection {
1136     my $dbname = shift;
1137     my $host   = shift;
1138     my $login  = shift;
1139     my $pass   = shift;
1140     my $port   = shift;
1141     my $seed   = shift;
1142     if ($seed) {
1143         undef $dbHandlerSeed;
1144         our $dbHandlerSeed = DBI->connect(
1145             "DBI:Pg:dbname=$dbname;host=$host;port=$port",
1146             $login, $pass,
1147             {
1148                 AutoCommit       => 1,
1149                 post_connect_sql => "SET CLIENT_ENCODING TO 'UTF8'",
1150                 pg_utf8_strings  => 1
1151             }
1152         );
1153     }
1154     else {
1155         undef $dbHandler;
1156         our $dbHandler = DBI->connect(
1157             "DBI:Pg:dbname=$dbname;host=$host;port=$port",
1158             $login, $pass,
1159             {
1160                 AutoCommit       => 1,
1161                 post_connect_sql => "SET CLIENT_ENCODING TO 'UTF8'",
1162                 pg_utf8_strings  => 1
1163             }
1164         );
1165     }
1166 }
1167
1168 sub dbhandler_query {
1169     my $querystring = shift;
1170     my $valuesRef   = shift;
1171     my $seed        = shift;
1172     my @values      = $valuesRef ? @{$valuesRef} : ();
1173     my @ret;
1174
1175     my $query;
1176     $query = $dbHandler->prepare($querystring)     if ( !$seed );
1177     $query = $dbHandlerSeed->prepare($querystring) if ($seed);
1178     my $i = 1;
1179     foreach (@values) {
1180         $query->bind_param( $i, $_ );
1181         $i++;
1182     }
1183     $query->execute();
1184     my @columnNames = @{ $query->{NAME} };
1185     my %colPos      = ();
1186     my $pos         = 0;
1187     foreach (@columnNames) {
1188         $colPos{$_} = $pos;
1189         $pos++;
1190     }
1191     undef @columnNames;
1192
1193     while ( my $row = $query->fetchrow_arrayref() ) {
1194         my @pushData = ();
1195         foreach ( @{$row} ) {
1196             my $thisCol = $_;
1197             if ( ref $thisCol eq 'ARRAY' )    # handle [] datatypes
1198             {
1199                 my $t = join( ',', @{$thisCol} );
1200                 if ( isStringArray($thisCol) == 1 ) {
1201                     $t = join( "','", @{$thisCol} );
1202                 }
1203                 push( @pushData, "{$t}" );
1204                 undef $t;
1205             }
1206             else {
1207                 push( @pushData, $thisCol );
1208             }
1209             undef $thisCol;
1210         }
1211         push( @ret, \@pushData );
1212     }
1213     undef($querystring);
1214     push( @ret, \%colPos );
1215
1216     return \@ret;
1217 }
1218
1219 sub isStringArray {
1220     my $arrayRef = shift;
1221     my @array    = @{$arrayRef};
1222     foreach (@array) {
1223         if ( $_ =~ m/[^\-^0-9^\.]/g ) {
1224             return 1;
1225         }
1226     }
1227     return 0;
1228 }
1229
1230 sub setupDB {
1231     %dbconf = (
1232         'dbuser' => $dbuser,
1233         'dbpass' => $dbpass,
1234         'port'   => $dbport,
1235         'dbhost' => $dbhost,
1236         'db'     => $databaseName,
1237     );
1238     dbhandler_setupConnection( $dbconf{"db"}, $dbconf{"dbhost"}, $dbconf{"dbuser"}, $dbconf{"dbpass"},
1239         $dbconf{"port"} );
1240
1241     dbhandler_setupConnection( $seedDBName, $dbconf{"dbhost"}, $dbconf{"dbuser"}, $dbconf{"dbpass"}, $dbconf{"port"},
1242         1 );
1243 }
1244
1245 sub checkCMDArgs {
1246     print "Checking command line arguments...\n" if ($debug);
1247
1248     if ( !$dbhost ) {
1249         print "Please provide the postgres database hostname/IP via --db-host\n";
1250         exit 1;
1251     }
1252
1253     if ( !$dbuser ) {
1254         print "Please provide the postgres database username via --db-user\n";
1255         exit 1;
1256     }
1257
1258     if ( !$dbpass ) {
1259         print "Please provide the postgres database password via --db-pass\n";
1260         exit 1;
1261     }
1262
1263     if ( !$dbport ) {
1264         print "Please provide the postgres database port via --db-port\n";
1265         exit 1;
1266     }
1267
1268     if ( !$databaseName && ( !$doUpgrade || !$doTestRestore ) ) {
1269         print "Please provide the postgres database name via --db-name\n";
1270         exit 1;
1271     }
1272
1273     if ( $outputFolder eq '' ) {
1274         print "Output folder not provided. Please pass in a command line path argument with --output-folder\n";
1275         exit 1;
1276     }
1277     if ( !$egRepoPath ) {
1278         print "You didn't include a path to the Evergreen repository --evergreen-repo\n";
1279         exit 1;
1280     }
1281     if ( !-e $egRepoPath ) {
1282         print "The path to the Evergreen repository --evergreen-repo does not exist\n";
1283         exit 1;
1284     }
1285     if ( !-e ( $egRepoPath . '/.git' ) ) {
1286         print "The path to the Evergreen repository is not a git repository\n";
1287         exit 1;
1288     }
1289     if ( $doUpgrade && ( !-e ( $outputFolder . '/config.upgrade_log.sql' ) ) ) {
1290         print
1291           "You've spcified the upgrade option but the output folder doesn't contain a previously generated dataset. "
1292           . "I need to know what version of Evergreen this dataset was created from. I use 'config.upgrade_log.sql' to figure that out\n";
1293         exit 1;
1294     }
1295     if ( !$seedDBName && ( !-e ( $outputFolder . '/config.upgrade_log.sql' ) ) ) {
1296         print
1297 "Please provide the name of the Evergreen seed database and/or an output folder that contains a previously generated dataset. "
1298           . "Please pass in a command line path argument with --seed-db-name\n";
1299         exit 1;
1300     }
1301     if ($doUpgrade) {
1302         userInput(
1303 "You've chosen to perform an upgrade. FYI, the output folder SQL files will get overwritten with the upgraded version\n"
1304         );
1305     }
1306
1307     # Trim any trailing / on path
1308     $outputFolder =~ s/\/$//g;
1309     $egRepoPath   =~ s/\/$//g;
1310 }
1311
1312 sub userInput {
1313     my $prompt = shift;
1314     my $answer;
1315     if ( !$nonInteractive ) {
1316         print $prompt. "\n";
1317         print "Press Enter to continue or CTRL+C to stop now\n";
1318         $answer = <STDIN>;
1319     }
1320     return $answer;
1321 }
1322
1323 sub printHelp {
1324     print $help;
1325     exit 0;
1326 }
1327
1328 exit;