LP#1901932 Wish List - Enhanced Concerto dataset
[evergreen-equinox.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
23 our $CHUNKSIZE = 500;
24
25 our $xmlconf = "/openils/conf/opensrf.xml";
26 our $xmlconfseed;
27 our $dbHandler;
28 our $dbHandlerSeed;
29 our $sample;
30 our $outputFolder;
31 our $debug                 = 0;
32 our $seedTableUsedRowCount = 0;
33 our $seedTableRowCount     = 100000;
34 our @skipTables            = (
35     'auditor.*',
36     'search.*',
37     'reporter.*',
38     'metabib.*',
39     'actor.workstation_setting',
40     'acq.lineitem_attr',
41     'action_trigger.*',
42     'asset.copy_vis_attr_cache',
43     'authority.rec_descriptor',
44     'authority.simple_heading',
45     'authority.full_rec',
46     'authority.authority_linking',
47     'config.upgrade_log',
48     'actor.org_unit_proximity',
49     'config.org_unit_setting_type_log',
50     'config.xml_transform',
51     'money.materialized_billable_xact_summary',
52     'serial.materialized_holding_code',
53     'vandelay.queued_bib_record_attr',
54         'config.print_template',
55         'config.workstation_setting_type',
56 );
57
58 our @loadOrder = (
59     'actor.org_unit',
60     'actor.usr',
61     'acq.fund',
62     'acq.provider',
63
64     # call number data can include ##URI##,
65     # which biblio.record_entry triggers create, so, they go first
66     'asset.call_number',
67     'asset.uri',
68     'asset.uri_call_number_map',
69     'biblio.record_entry',
70     'biblio.monograph_part',
71     'acq.edi_account',
72     'acq.purchase_order',
73     'acq.lineitem',
74     'acq.lineitem_detail',
75     'acq.invoice',
76     'acq.invoice_entry',
77     'asset.copy_location',
78     'asset.copy',
79     'biblio.peer_type',
80     'authority.record_entry',
81     'money.grocery',
82     'money.billable_xact',
83     'money.billing',
84
85     # needs to come before actor.workstation
86     'money.bnm_desk_payment',
87 );
88
89 our $help = "Usage: ./make_concerto_from_evergreen_db.pl [OPTION]...
90
91 This program automates the process of making a new dataset for the Evergreen code repository.
92 We need connection details to the Evergreen database where the intended dataset lives AND
93 we need connection details to a stock Evergreen database that only has the seed data.
94 This code requires the second database for comparison reasons. It needs to know what data is
95 seed data and what data is not.
96
97 Mandatory arguments
98 --output-folder     Folder for our generated output
99 --xmlseed           path to Evergreen opensrf.xml file for DB connection details to the seed database, created with --create-database --create-schema, NOT --load-all-sample
100
101 Optional
102 --xmlconfig         path to Evergreen opensrf.xml file for DB connection details, default /openils/conf/opensrf.xml
103 --sample            Number of rows to fetch eg --sample 100 (not implemented)
104 --debug             Set debug mode for more verbose output.
105 ";
106
107 GetOptions(
108     "sample=s"        => \$sample,
109     "xmlconfig=s"     => \$xmlconf,
110     "xmlseed=s"       => \$xmlconfseed,
111     "output-folder=s" => \$outputFolder,
112     "debug"           => \$debug,
113 ) or printHelp();
114
115 checkCMDArgs();
116
117 setupDB();
118
119 start();
120
121 sub start {
122
123     # make the output folder if it doesn't exist
124     make_path(
125         $outputFolder,
126         {
127             chmod => 0775,
128         }
129     ) if ( !( -e $outputFolder ) );
130
131     # Gather a list of Evergreen Tables to process
132     my @evergreenTables = @{ getSchemaTables() };
133     my $loadAll =
134         "BEGIN;\n\n-- stop on error\n\\set ON_ERROR_STOP on\n\n"
135       . "-- Ignore constraints until we're done\nSET CONSTRAINTS ALL DEFERRED;\n\n";
136     my @loadTables = ();
137     while ( $#evergreenTables > -1 ) {
138         my $thisTable = shift @evergreenTables;
139         my $columnRef = shift @evergreenTables;
140         if ( checkTableForInclusion($thisTable) ) {
141             my $thisFile = $outputFolder . "/$thisTable.sql";
142             print "Processing $thisTable > $thisFile\n";
143             unlink $thisFile if -e $thisFile;
144             my $thisFhandle;
145             open( $thisFhandle, '>> ' . $thisFile );
146             binmode( $thisFhandle, ":utf8" );
147             my $lines = tableHandler( $thisTable, $columnRef, $thisFhandle );
148             close($thisFhandle);
149             unlink $thisFile if ( -e $thisFile && $lines == 0 );
150             push( @loadTables, $thisTable ) if ( -e $thisFile && $lines > 0 );
151             undef $lines;
152         }
153         else {
154             print "Skipping: $thisTable\n" if $debug;
155         }
156     }
157     $loadAll = loadTableOrderMaker( $loadAll, \@loadTables );
158     $loadAll .= "COMMIT;\n";
159     print "Writing loader > $outputFolder/load_all.sql\n";
160     open( OUT, "> $outputFolder/load_all.sql" );
161     binmode( OUT, ":utf8" );
162     print OUT $loadAll;
163     close(OUT);
164 }
165
166 sub loadTableOrderMaker {
167     my $loadString        = shift;
168     my $includedTablesRef = shift;
169     my @includedTables    = @{$includedTablesRef};
170     my %used              = ();
171
172     # Loop through the pre-defined order, and check those off
173     foreach (@loadOrder) {
174         my $otable = $_;
175         my $pos    = 0;
176         foreach (@includedTables) {
177             if ( $includedTables[$pos] eq $otable ) {
178                 $loadString .= makeLoaderLine($_);
179                 $used{$pos} = 1;
180             }
181             $pos++;
182         }
183         undef $pos;
184     }
185
186     # include the rest
187     my $pos = 0;
188     foreach (@includedTables) {
189         if ( not defined $used{$pos} ) {
190             $loadString .= makeLoaderLine($_);
191             $used{$pos} = 1;
192         }
193         $pos++;
194     }
195     undef $pos;
196
197     return $loadString;
198 }
199
200 sub makeLoaderLine {
201     my $table = shift;
202     my $ret   = "\\echo loading $table\n";
203     $ret .= "\\i $table.sql\n\n";
204     return $ret;
205 }
206
207 sub tableHandler {
208     my $table          = shift;
209     my $tableColumnRef = shift;
210     my $fHandle        = shift;
211     my $funcHandler    = $table;
212     my $rowCount       = 0;
213     $funcHandler =~ s/\./_/g;
214     $funcHandler .= '_handler';
215
216 # if some tables need handled special, make a sub with the table name AKA sub biblio_record_entry_handler
217     if ( functionExists($funcHandler) ) {
218         my $perlcode =
219             '$rowCount = '
220           . $funcHandler
221           . '($table, $tableColumnRef, $fHandle);';
222         eval $perlcode;
223     }
224     else {
225         $rowCount = standardHandler( $table, $tableColumnRef, $fHandle );
226     }
227     return $rowCount;
228 }
229
230 sub columnOrder {
231     my $colRef  = shift;
232     my %columns = %{$colRef};
233     my @order   = ();
234     my @ret     = ();
235
236     while ( ( my $colname, my $colpos ) = each(%columns) ) {
237         push( @order, $colpos );
238     }
239     @order = sort { $a <=> $b } @order;
240     foreach (@order) {
241         my $thisPOS = $_;
242         while ( ( my $colname, my $colpos ) = each(%columns) ) {
243             if ( $colpos == $thisPOS ) {
244                 push( @ret, $colname );
245             }
246         }
247         undef $thisPOS;
248     }
249     return \@ret;
250 }
251
252 sub functionExists {
253
254     # no strict 'refs';
255     my $funcname = shift;
256     return \&{$funcname} if defined &{$funcname};
257     return;
258 }
259
260 sub getDataChunk {
261     my $query  = shift;
262     my $offset = shift;
263     $query .= "\nLIMIT $CHUNKSIZE OFFSET $offset\n";
264     print $query if $debug;
265     my @results = @{ dbhandler_query($query) };
266     return \@results;
267 }
268
269 sub standardHandler {
270     my $table          = shift;
271     my $tableColumnRef = shift;
272     my $fHandle        = shift;
273     my $omitColumnsRef = shift;
274     my %omitColumn     = %{$omitColumnsRef} if $omitColumnsRef;
275     my $query          = "SELECT ";
276     my $sqlOutTop      = "COPY $table (";
277     my $order          = "ORDER BY ";
278     my @colOrder       = @{ columnOrder($tableColumnRef) };
279     my $colCount       = 1;
280     my $rowCount       = 0;
281
282     foreach (@colOrder) {
283
284         # if the calling code wants to remove some columns, we skip them here
285         if ( ( !$omitColumnsRef ) || ( not defined $omitColumn{$_} ) ) {
286             $query     .= "$_, ";
287             $sqlOutTop .= "$_, ";
288             $order     .= "$colCount, ";
289             $colCount++;
290         }
291         else {
292             print "removing column: $_\n";
293         }
294     }
295     $query     = substr( $query,     0, -2 );  # remove the trailing comma+space
296     $sqlOutTop = substr( $sqlOutTop, 0, -2 );  # remove the trailing comma+space
297     $order     = substr( $order,     0, -2 );  # remove the trailing comma+space
298     $query .= " FROM $table\n$order";
299
300     # makes it possible to not have to quote strings, dates, etc.
301     $sqlOutTop .= ") FROM stdin;\n";
302
303     my $offset    = 0;
304     my @data      = @{ getDataChunk( $query, $offset ) };
305     my $firstTime = 1;
306     while ( $#data > 0 )   #skipping column def metadata at the end of the array
307     {
308         my $sqlOut = $sqlOutTop;
309         my @differencesFromSeed =
310           @{ removeDuplicateStockData( \@data, $table, $firstTime ) };
311         $firstTime = 0;
312         my $outCount = 0;
313         foreach (@differencesFromSeed) {
314             $outCount++;
315             $rowCount++;
316             my $row = $_;
317             foreach ( @{$row} ) {
318                 $_ = '\N' if !defined $_;
319
320                 # escape reserved tokens
321                 $_ =~ s/\n/\\n/g;    # newline
322                 $_ =~ s/\r/\\r/g;    # carriage return
323                 $_ =~ s/\t/\\t/g;    # tab
324                 $_ =~ s/\v/\\v/g;    # vertical tab
325                 $_ =~ s/\f/\\f/g;    # form feed
326                 $sqlOut .= "$_\t";
327             }
328             $sqlOut = substr( $sqlOut, 0, -1 );
329             $sqlOut .= "\n";
330         }
331         print $fHandle $sqlOut if $outCount > 0;
332
333         # postgres sql syntax for finish of stdin
334         print $fHandle "\\.\n\n" if $outCount > 0;
335
336         undef $sqlOut;
337         undef $outCount;
338         $offset += $CHUNKSIZE;
339         @data = @{ getDataChunk( $query, $offset ) };
340     }
341     print $fHandle injectSequenceUpdate($table);
342
343     undef @data;
344     undef $sqlOutTop;
345     undef $query;
346
347     return $rowCount;
348 }
349
350 sub biblio_record_entry_handler {
351     my $table          = shift;
352     my $tableColumnRef = shift;
353     my $fHandle        = shift;
354     my %omitColumns    = ( 'vis_attr_vector' => 1 );
355     return standardHandler( $table, $tableColumnRef, $fHandle, \%omitColumns );
356 }
357
358 sub actor_workstation_handler {
359     my $table          = shift;
360     my $tableColumnRef = shift;
361     my $fHandle        = shift;
362     my $lines =
363       standardHandler( $table, $tableColumnRef, $fHandle, \%omitColumns );
364     print $fHandle <<'splitter';
365
366 -- a case where the deleted workstation had payments
367 INSERT INTO actor.workstation(id,name,owning_lib)
368 SELECT missingworkstation.id, aou.shortname||FLOOR(RANDOM() * 100 + 1)::INT, 1
369     FROM
370     (
371         SELECT
372         DISTINCT mbdp.cash_drawer AS id
373         FROM
374         money.bnm_desk_payment mbdp
375         LEFT JOIN actor.workstation aw ON (mbdp.cash_drawer = aw.id)
376         WHERE
377         aw.id IS NULL
378     ) missingworkstation
379 JOIN actor.org_unit aou ON (aou.id=1);
380
381 -- anonymize workstation names
382 UPDATE
383 actor.workstation aw
384     SET name=aou.shortname||'-'||aw.id
385 FROM actor.org_unit aou
386 WHERE
387     aou.id=aw.owning_lib;
388 splitter
389
390     return $lines;
391 }
392
393 sub injectSequenceUpdate {
394     my $table      = shift;
395     my @schema     = split( /\./, $table );
396     my $schemaName = @schema[0];
397     my $ret        = '';
398     my $query      = <<'splitter';
399 SELECT * FROM
400 (
401 SELECT t.oid::regclass AS table_name,
402        a.attname AS column_name,
403        s.relname AS sequence_name
404 FROM pg_class AS t
405    JOIN pg_attribute AS a
406       ON a.attrelid = t.oid
407    JOIN pg_depend AS d
408       ON d.refobjid = t.oid
409          AND d.refobjsubid = a.attnum
410    JOIN pg_class AS s
411       ON s.oid = d.objid
412 WHERE d.classid = 'pg_catalog.pg_class'::regclass
413   AND d.refclassid = 'pg_catalog.pg_class'::regclass
414   AND d.deptype IN ('i', 'a')
415   AND t.relkind IN ('r', 'P')
416   AND s.relkind = 'S'
417 ) AS a
418 WHERE
419     a.table_name = '!!tbname!!'::regclass
420 splitter
421
422     $query =~ s/!!tbname!!/$table/g;
423     my @results = @{ dbhandler_query($query) };
424     while ( $#results > 0 ) {
425         my $this    = shift @results;
426         my @row     = @{$this};
427         my $colname = @row[1];
428         my $seqname = @row[2];
429         $ret .= "\\echo sequence update column: !!colname!!\n";
430         $ret .=
431 "SELECT SETVAL('!!seqname!!', (SELECT MAX(!!colname!!) FROM !!tbname!!));\n";
432         $ret =~ s/!!tbname!!/$table/g;
433         $ret =~ s/!!colname!!/$colname/g;
434         $ret =~ s/!!seqname!!/$schemaName.$seqname/g;
435         undef $colname;
436         undef $seqname;
437     }
438
439     return $ret;
440 }
441
442 sub removeDuplicateStockData {
443     my $resultsRef = shift;
444     my $table      = shift;
445     my $firstTime  = shift;
446     $seedTableRowCount = getTableRowCount( $table, 1 ) if ($firstTime);
447     $seedTableUsedRowCount = 0 if ($firstTime);
448     my @ret         = ();
449     my @results     = @{$resultsRef};
450     my $colRef      = @results[$#results];
451     my %columns     = %{$colRef};
452     my $resultsPOS  = 0;
453     my $removeCount = 0;
454
455     foreach (@results) {
456         my $rowRef = $_;
457         last if $resultsPOS == $#results;
458         $resultsPOS++;
459
460         # don't bother if we know we've already used up the seed data table
461         if ( $seedTableUsedRowCount < $seedTableRowCount ) {
462             my @row    = @{$rowRef};
463             my @vals   = ();
464             my $pos    = 0;
465             my $select = "SELECT ";
466             my $where  = "WHERE 1=1";
467             while ( ( my $colname, my $colpos ) = each(%columns) ) {
468
469 # compare ID numbers when there is an ID column, otherwise, compare the rest of the columns
470                 if (   ( $colname ne 'id' && not defined $columns{'id'} )
471                     || ( $colname eq 'id' ) )
472                 {
473                     $select .= "$colname, ";
474                     if (
475                         defined @row[$colpos]
476                       ) # if it's null data, the SQL needs to be "is null", not "="
477                     {
478                         $pos++;
479                         $where .= " AND $colname = \$$pos";
480                         push( @vals, @row[$colpos] );
481                     }
482                     else {
483                         $where .= " AND $colname is null";
484                     }
485                 }
486             }
487
488             # remove the trailing comma+space
489             $select = substr( $select, 0, -2 );
490             $select .= "\nFROM $table\n$where\n";
491             print $select if $debug;
492             print Dumper( \@vals ) if $debug;
493             my @res = @{ dbhandler_query( $select, \@vals, 1 ) };
494
495             # seed data doesn't have a match, want this row for our new dataset
496             if ( $#res == 0 ) {
497                 push( @ret, $rowRef );
498             }
499             else {
500                 $removeCount++;
501
502 # Each time we match seed data, we count. If the number of rows found equals the
503 # number of total rows, we don't need to keep checking back on the seed database
504                 $seedTableUsedRowCount++;
505             }
506             undef @res;
507             undef $select;
508             undef $where;
509             undef $pos;
510             undef @vals;
511         }
512         else {
513 # exhausted the seed database table rows, this data can just blindly get added to the
514 # result set
515             push( @ret, $rowRef );
516         }
517     }
518
519     # print Dumper(\@ret);
520     print "Removed $removeCount rows (exists in seed data)\n" if $removeCount;
521     return \@ret;
522 }
523
524 sub getSchemaTables {
525     my @ret       = ();
526     my @tableTest = ();
527     my $query     = <<'splitter';
528 SELECT schemaname||'.'||tablename
529 FROM pg_catalog.pg_tables
530 WHERE
531 schemaname NOT IN('pg_catalog','information_schema')
532 ORDER BY 1
533
534 splitter
535
536     my @results   = @{ dbhandler_query($query) };
537     my $resultPos = 0;
538     foreach (@results) {
539         my $row = $_;
540         my @row = @{$row};
541         if ( getTableRowCount( @row[0] ) > 0 ) {
542             push( @ret, lc @row[0] );
543             push( @ret, getTableColumnNames( @row[0] ) );
544         }
545         else {
546             print "no rows in @row[0]\n" if $debug;
547         }
548         $resultPos++;
549         last if $#results == $resultPos;    # ignore the column header metadata
550     }
551
552     undef $resultPos;
553
554     return \@ret;
555 }
556
557 sub getTableRowCount {
558     my $table   = shift;
559     my $seed    = shift;
560     my $ret     = 0;
561     my $query   = "SELECT count(*) FROM $table";
562     my @results = @{ dbhandler_query( $query, undef, $seed ) };
563     foreach (@results) {
564         my $row = $_;
565         my @row = @{$row};
566         $ret = @row[0];
567         last;    # ignore column header metadata
568     }
569
570     return $ret;
571 }
572
573 sub getTableColumnNames {
574     my $table   = shift;
575     my $ret     = 0;
576     my $query   = "SELECT * FROM $table LIMIT 1";
577     my @results = @{ dbhandler_query($query) };
578     $ret = pop @results;
579     return $ret;
580 }
581
582 sub getDBconnects {
583     my $openilsfile = shift;
584     my $xml         = new XML::Simple;
585     my $data        = $xml->XMLin($openilsfile);
586     my %conf;
587     $conf{"dbhost"} =
588       $data->{default}->{apps}->{"open-ils.storage"}->{app_settings}
589       ->{databases}->{database}->{host};
590     $conf{"db"} = $data->{default}->{apps}->{"open-ils.storage"}->{app_settings}
591       ->{databases}->{database}->{db};
592     $conf{"dbuser"} =
593       $data->{default}->{apps}->{"open-ils.storage"}->{app_settings}
594       ->{databases}->{database}->{user};
595     $conf{"dbpass"} =
596       $data->{default}->{apps}->{"open-ils.storage"}->{app_settings}
597       ->{databases}->{database}->{pw};
598     $conf{"port"} =
599       $data->{default}->{apps}->{"open-ils.storage"}->{app_settings}
600       ->{databases}->{database}->{port};
601     return \%conf;
602
603 }
604
605 sub checkTableForInclusion {
606     my $table  = shift;
607     my @schema = split( /\./, $table );
608     foreach (@skipTables) {
609         return 0 if ( lc $table eq lc $_ );
610         if ( $_ =~ /\*$/ ) {
611             my @thisSchema = split( /\./, $_ );
612             return 0 if ( lc @schema[0] eq lc @thisSchema[0] );
613         }
614     }
615     return 1;
616 }
617
618 sub logfile_readFile {
619     my $file   = shift;
620     my $trys   = 0;
621     my $failed = 0;
622     my @lines;
623
624     #print "Attempting open\n";
625     if ( -e $file ) {
626         my $worked = open( inputfile, '< ' . $file );
627         if ( !$worked ) {
628             print "******************Failed to read file*************\n";
629         }
630         binmode( inputfile, ":utf8" );
631         while ( !( open( inputfile, '< ' . $file ) ) && $trys < 100 ) {
632             print "Trying again attempt $trys\n";
633             $trys++;
634             sleep(1);
635         }
636         if ( $trys < 100 ) {
637
638             #print "Finally worked... now reading\n";
639             @lines = <inputfile>;
640             close(inputfile);
641         }
642         else {
643             print "Attempted $trys times. COULD NOT READ FILE: $file\n";
644         }
645         close(inputfile);
646     }
647     else {
648         print "File does not exist: $file\n";
649     }
650     return \@lines;
651 }
652
653 sub dbhandler_setupConnection {
654     my $dbname = shift;
655     my $host   = shift;
656     my $login  = shift;
657     my $pass   = shift;
658     my $port   = shift;
659     my $seed   = shift;
660     if ($seed) {
661         $dbHandlerSeed = DBI->connect(
662             "DBI:Pg:dbname=$dbname;host=$host;port=$port",
663             $login, $pass,
664             {
665                 AutoCommit       => 1,
666                 post_connect_sql => "SET CLIENT_ENCODING TO 'UTF8'",
667                 pg_utf8_strings  => 1
668             }
669         );
670     }
671     else {
672         $dbHandler = DBI->connect(
673             "DBI:Pg:dbname=$dbname;host=$host;port=$port",
674             $login, $pass,
675             {
676                 AutoCommit       => 1,
677                 post_connect_sql => "SET CLIENT_ENCODING TO 'UTF8'",
678                 pg_utf8_strings  => 1
679             }
680         );
681     }
682 }
683
684 sub dbhandler_query {
685     my $querystring = shift;
686     my $valuesRef   = shift;
687     my $seed        = shift;
688     my @values      = $valuesRef ? @{$valuesRef} : ();
689     my @ret;
690
691     my $query;
692     $query = $dbHandler->prepare($querystring)     if ( !$seed );
693     $query = $dbHandlerSeed->prepare($querystring) if ($seed);
694     my $i = 1;
695     foreach (@values) {
696         $query->bind_param( $i, $_ );
697         $i++;
698     }
699     $query->execute();
700     my @columnNames = @{ $query->{NAME} };
701     my %colPos      = ();
702     my $pos         = 0;
703     foreach (@columnNames) {
704         $colPos{$_} = $pos;
705         $pos++;
706     }
707     undef @columnNames;
708
709     while ( my $row = $query->fetchrow_arrayref() ) {
710         my @pushData = ();
711         foreach ( @{$row} ) {
712             my $thisCol = $_;
713             if ( ref $thisCol eq 'ARRAY' )    # handle [] datatypes
714             {
715                 my $t = join( ',', @{$thisCol} );
716                 if ( isStringArray($thisCol) == 1 ) {
717                     $t = join( "','", @{$thisCol} );
718                 }
719                 push( @pushData, "{$t}" );
720                 undef $t;
721             }
722             else {
723                 push( @pushData, $thisCol );
724             }
725             undef $thisCol;
726         }
727         push( @ret, \@pushData );
728     }
729     undef($querystring);
730     push( @ret, \%colPos );
731
732     return \@ret;
733 }
734
735 sub isStringArray {
736     my $arrayRef = shift;
737     my @array    = @{$arrayRef};
738     foreach (@array) {
739         if ( $_ =~ m/[^\-^0-9^\.]/g ) {
740             return 1;
741         }
742     }
743     return 0;
744 }
745
746 sub setupDB {
747     my %dbconf = %{ getDBconnects($xmlconf) };
748     dbhandler_setupConnection(
749         $dbconf{"db"},     $dbconf{"dbhost"}, $dbconf{"dbuser"},
750         $dbconf{"dbpass"}, $dbconf{"port"}
751     );
752
753     %dbconf = %{ getDBconnects($xmlconfseed) };
754     dbhandler_setupConnection( $dbconf{"db"}, $dbconf{"dbhost"},
755         $dbconf{"dbuser"}, $dbconf{"dbpass"}, $dbconf{"port"}, 1 );
756 }
757
758 sub checkCMDArgs {
759     print "Checking command line arguments...\n" if ($debug);
760
761     if ( $outputFolder eq '' ) {
762         print
763 "Output folder not provided. Please pass in a command line path argument with --output-folder\n";
764         exit 1;
765     }
766     if ( !$xmlconfseed ) {
767         print
768 "Please provide a path to the Evergreen seed database conneciton details. Please pass in a command line path argument with --xmlseed\n";
769         exit 1;
770     }
771
772     if ( !-e $xmlconf ) {
773         print
774 "$xmlconf does not exist.\nEvergreen database xml configuration file does not exist. Please provide a path to the Evergreen opensrf.xml database conneciton details. --xmlconf\n";
775         exit 1;
776     }
777
778     if ( !-e $xmlconfseed ) {
779         print
780 "$xmlconfseed does not exist.\nEvergreen seed database xml configuration file does not exist. Please provide a path to the seed Evergreen opensrf.xml database conneciton details. --xmlconfseed\n";
781         exit 1;
782     }
783
784     # Trim any trailing / on path
785     $outputFolder =~ s/\/$//g;
786 }
787
788 sub printHelp {
789     print $help;
790     exit 0;
791 }
792
793 exit;