adding search path to dbi connect and stripping out schema defs from xml
[migration-tools.git] / mig-bin / mig-gsheet
1 #!/usr/bin/perl
2
3 use Env qw(
4     HOME PGHOST PGPORT PGUSER PGDATABASE MIGSCHEMA
5     MIGBASEWORKDIR MIGBASEGITDIR MIGGITDIR MIGWORKDIR
6 );
7 use Net::Google::Spreadsheets;
8 use Net::Google::DataAPI::Auth::OAuth2;
9 use Net::OAuth2::AccessToken;
10 use Storable;
11 use DBI;
12 use FindBin;
13 use lib "$FindBin::Bin/";
14 my $mig_bin = "$FindBin::Bin/";
15 use Mig;
16 use strict;
17 use Env qw(
18     HOME PGHOST PGPORT PGUSER PGDATABASE MIGSCHEMA
19     MIGBASEWORKDIR MIGBASEGITDIR MIGGITDIR MIGWORKDIR
20     );
21 use Switch;
22 use Cwd 'abs_path';
23 use Pod::Usage;
24 use Data::Dumper;
25
26 pod2usage(-verbose => 2) if defined $ARGV[0] && $ARGV[0] eq '--help';
27
28 Mig::die_if_no_env_migschema();
29 die_if_gsheet_tracked_table_does_not_exist();
30 die_if_gsheet_tracked_column_does_not_exist();
31
32 my $cmd_push;
33 my $next_arg_is_push;
34 my $cmd_pull;
35 my $next_arg_is_pull;
36 my @worksheet_names;
37 my $cmd_export = 0;
38 my @table_names;
39 my $sql;
40 my $sth;
41 my @ws;
42 my @tracked_ws_names;
43
44 foreach my $arg (@ARGV) {
45     if ($arg eq '--push') {
46         $next_arg_is_push = 1;
47         next;
48     }
49     if ($next_arg_is_push) {
50         $cmd_push = $arg;
51         $next_arg_is_push = 0;
52         next;
53     }
54     if ($arg eq '--pull') {
55         $next_arg_is_pull = 1;
56         next;
57     }
58     if ($next_arg_is_pull) {
59         $cmd_pull = $arg;
60         $next_arg_is_pull = 0;
61         next;
62     }
63     if ($arg eq '--export') {
64         $cmd_export = 1;
65         next;
66     }
67 }
68
69 abort('must specify --push (db->worksheets) or --pull (worksheets->db)') unless (defined $cmd_push or defined $cmd_pull);
70 if (defined $cmd_push and defined $cmd_pull) { abort('you can not specify both a --push and --pull on the same command'); }
71
72 my $dbh = Mig::db_connect();
73 my $spreadsheet = connect_gsheet();
74 abort('could not connect to google sheet') unless (defined $spreadsheet);
75
76 $sql = 'SELECT tab_name FROM ' . $MIGSCHEMA . '.gsheet_tracked_table;';
77 $sth = $dbh->prepare($sql);
78 my $ra = $sth->execute();
79 while (my @row = $sth->fetchrow_array) {
80     push @tracked_ws_names, $row[0];
81 }
82
83 if (defined $cmd_pull) {
84     print "Pulling ";
85     if ($cmd_pull eq 'all') {
86         print "all worksheets.\n";
87         @ws = $spreadsheet->worksheets;
88         foreach my $wsn (@ws) { push @worksheet_names, $wsn->title; }
89     } else {
90         print "only worksheet $cmd_pull.\n";
91         if (!defined $cmd_pull) { abort('command incomplete'); } 
92         push @worksheet_names, $cmd_pull;
93     }
94     my @m = array_match(\@worksheet_names,\@tracked_ws_names);
95     foreach my $w (@m) { 
96         my $pull_ws = $spreadsheet->worksheet( {title => $w} ); 
97         my $push_tb = get_table_name($MIGSCHEMA,$w,$dbh);
98         my @rows = $pull_ws->rows;
99         my @content;
100         map { $content[$_->row - 1][$_->col - 1] = $_->content } $pull_ws->cells;
101         my @tab_headers = shift @content;
102         my $tab_headers_length = $#{ $tab_headers[0] };
103         my @pg_headers;
104         for my $i ( 0 .. $tab_headers_length ) {
105             push @pg_headers, $tab_headers[0][$i];
106         }
107         shift @content;
108         #todo: check for clean headers at some point ...
109         truncate_table($MIGSCHEMA,$push_tb,$dbh);
110         print "Inserting from $w to $push_tb.\n";
111         for my $j (@content) {
112             insert_row($MIGSCHEMA,$push_tb,$dbh,\@pg_headers,$j);
113         }
114         timestamp($MIGSCHEMA,$push_tb,$dbh,'pull');
115     }
116 }
117
118 if (defined $cmd_push) {
119     print "Pushing ";
120     if ($cmd_push eq 'all') {
121         print "all tables.\n";
122         $sql = 'SELECT table_name FROM ' . $MIGSCHEMA . '.gsheet_tracked_table';
123         $sth = $dbh->prepare($sql);
124         $ra = $sth->execute();
125         while (my @row = $sth->fetchrow_array) {
126             push @table_names, $row[0];
127         }
128     } else {
129         print "only table $cmd_push.\n";
130         if (!defined $cmd_push) { abort('command incomplete'); }
131         push @table_names, $cmd_push;
132     }
133     foreach my $t (@table_names) {
134         my $pull_tb = $MIGSCHEMA . "." . $t;;
135         my @table_headers = get_pg_column_headers($t,$MIGSCHEMA);
136         my $push_ws_name = get_worksheet_name($MIGSCHEMA,$t,$dbh);
137         my $push_ws = $spreadsheet->worksheet( {title => $push_ws_name} );
138         if (!defined $push_ws) { next; }
139         my @rows;
140         my $i = 0;
141         foreach my $rth (@table_headers) { $rows[0][$i] = $rth; $i++; }         
142         $sql = "SELECT * FROM $pull_tb;";
143         $sth = $dbh->prepare($sql);
144         $sth->execute();
145         my $grabhash = $sth->fetchall_arrayref({});
146         erase_sheet($push_ws,$push_ws_name);
147
148         #get from postgres the headers to use in the sheet from tracked columns
149         $sql = 'SELECT column_name FROM ' . $MIGSCHEMA . '.gsheet_tracked_column WHERE table_id = (SELECT id FROM ' . $MIGSCHEMA . '.gsheet_tracked_table WHERE table_name = \'' . $t . '\')';
150         $sth = $dbh->prepare($sql);
151         $sth->execute();
152         my $sheet_headers = $sth->fetchall_arrayref();
153         my $sheet_headers_length = @$sheet_headers;
154         #now I need to do new rows using those headers
155         my @content;
156         foreach my $row ( @{$grabhash} ) {
157             my $record = {};
158             for my $column ( sort keys %{ $row } ) {
159                 print Dumper(@$sheet_headers);
160                 print "column: $column\n";
161                 my $clean_column = $column;
162                 $clean_column =~ s/_//g;
163                 print "clean column: $clean_column\n";
164                 if ( $column ~~ @$sheet_headers ) {
165                     $record->{$clean_column} = $row->{$column}; 
166                 }
167             }
168             push @content, $record;
169         }
170
171         foreach my $fillsheet (@content) {
172             my $new_row = $push_ws->add_row (
173                 $fillsheet
174             );
175         }
176         timestamp($MIGSCHEMA,$pull_tb,$dbh,'push');
177     }
178 }   
179
180 sub die_if_gsheet_tracked_table_does_not_exist {
181     if (!check_for_gsheet_tracked_table()) {
182         die "Table $MIGSCHEMA.gsheet_tracked_table does not exist.  Bailing...\n";
183     }
184 }
185
186 sub array_match {
187     my ($xa,$xb) = @_;
188     my @a = @{ $xa };
189     my @b = @{ $xb };
190     my @r;
191
192     foreach my $av (@a) {
193         foreach my $bv (@b) {
194             if ($av eq $bv) { push @r, $bv; }
195         }
196     }    
197     return @r;
198 }
199
200 sub get_pg_column_headers {
201     my $table_name = shift;
202     my $schema_name = shift;
203     my @headers;
204     my $dbh = Mig::db_connect();
205     $sql = 'SELECT column_name FROM information_schema.columns WHERE table_schema = ' . $dbh->quote( $schema_name ) . ' AND table_name = ' . $dbh->quote( $table_name ) . ';';
206     $sth = $dbh->prepare($sql);
207     $ra = $sth->execute();
208     while (my @row = $sth->fetchrow_array) {
209         push @headers, $row[0];
210     }
211     return @headers;
212 }
213
214 sub erase_sheet {
215     my $ws = shift;
216     my $ws_name = shift;
217
218     print "Erasing $ws_name.\n";
219     my @rows = $ws->rows;
220     my $i;
221     my $j = @rows;
222     $j = int(($j / 2))-1;
223     if ($j < 2) { $j = 2; }
224     while ($j > 0) {
225         #bodge until I figure out why google sheets is only deleting even numbered rows
226         $i = 1;
227         foreach my $row (@rows) {
228             if ($i != 1) { $row->delete; }
229             $i++;
230         };
231         $j--;
232     };
233     return;
234 }
235
236 sub check_for_gsheet_tracked_table {
237     my $dbh = Mig::db_connect();
238     my $sth = $dbh->prepare("
239         SELECT EXISTS(
240             SELECT 1
241             FROM information_schema.tables
242             WHERE table_schema = " . $dbh->quote( $MIGSCHEMA ) . "
243             AND table_name = 'gsheet_tracked_table'
244         );"
245     );
246     my $rv = $sth->execute()
247         || die "Error checking for table (tracked_gsheet_table): $!";
248     my @cols = $sth->fetchrow_array;
249     $sth->finish;
250     Mig::db_disconnect($dbh);
251     return $cols[0];
252 }
253
254 sub die_if_gsheet_tracked_column_does_not_exist {
255     if (!check_for_gsheet_tracked_column()) {
256         die "Table $MIGSCHEMA.gsheet_tracked_column does not exist.  Bailing...\n";
257     }
258 }
259
260 sub get_table_name {
261     my $migs = shift;
262     my $worksheet = shift;
263     my $dbh = shift;
264
265     my $sql = 'SELECT table_name FROM ' . $migs . '.gsheet_tracked_table WHERE tab_name = \'' . $worksheet . '\';';
266     my $sth = $dbh->prepare($sql);
267     my $xs = $sth->execute();
268     my $table_name;
269     while (my @row = $sth->fetchrow_array) {
270         $table_name = $row[0];
271     }
272
273     return $table_name;
274 }
275
276 sub get_worksheet_name {
277     my $migs = shift;
278     my $table = shift;
279     my $dbh = shift;
280
281     my $sql = 'SELECT tab_name FROM ' . $migs . '.gsheet_tracked_table WHERE table_name = \'' . $table . '\';';
282     my $sth = $dbh->prepare($sql);
283     my $xs = $sth->execute();
284     my $worksheet_name;
285     while (my @row = $sth->fetchrow_array) {
286         $worksheet_name = $row[0];
287     }
288
289     return $worksheet_name;
290 }
291
292
293 sub check_for_gsheet_tracked_column {
294     my $dbh = Mig::db_connect();
295     my $sth = $dbh->prepare("
296         SELECT EXISTS(
297             SELECT 1
298             FROM information_schema.tables
299             WHERE table_schema = " . $dbh->quote( $MIGSCHEMA ) . "
300             AND table_name = 'gsheet_tracked_column'
301         );"
302     );
303     my $rv = $sth->execute()
304         || die "Error checking for table (gsheet_tracked_column): $!";
305     my @cols = $sth->fetchrow_array;
306     $sth->finish;
307     Mig::db_disconnect($dbh);
308     return $cols[0];
309 }
310
311 sub insert_row {
312     my ($schema, $table, $dbh, $headers_ref, $row_ref) = @_;
313     my @headers = @{ $headers_ref };
314     my @row_data = @{ $row_ref };
315
316     my $header_string = '(' . join(",", @headers) . ')';
317     map {s/\'/\'\'/g; } @row_data;
318     my $row_string = '(' . join(",", map {qq/'$_'/} @row_data) . ')';
319     print "INSERT INTO $schema.$table $header_string VALUES $row_string\n"; 
320     $dbh->do(qq/
321         INSERT INTO $schema.$table $header_string VALUES $row_string ;
322     /);
323     
324     print "Row pushed to $table in schema $schema.\n";
325 }
326
327 sub timestamp {
328     my ($schema, $table, $dbh, $action) = @_;
329
330     my $column;
331     if ($action eq 'pull') { $column = 'last_pulled' }
332         else { $column = 'last_pushed' }; 
333
334     $dbh->do(qq/
335         UPDATE $schema.gsheet_tracked_table SET $column = NOW() WHERE table_name = '$table';
336     /);
337
338 }
339
340
341 sub truncate_table {
342     my $schema = shift;
343     my $table = shift;
344     my $dbh = shift;
345
346     $dbh->do(qq/
347         TRUNCATE TABLE $schema.$table;;
348     /);
349     print "Table $schema.$table truncated.\n";
350 }
351
352 sub abort {
353     my $msg = shift;
354     print STDERR "$0: $msg", "\n";
355     exit 1;
356 }
357
358 sub connect_gsheet {
359     if (!defined $ENV{'CLIENTID'}) {
360         exec '/bin/bash', '--init-file', '~/.mig/oauth.env';
361         print "Open Authentication settings were not loaded, please re-run.\n";
362     }
363     my $session_filename = $ENV{SESSIONFILE};
364     my $oauth2 = Net::Google::DataAPI::Auth::OAuth2->new(
365         client_id => $ENV{CLIENTID},
366         client_secret => $ENV{CLIENTSECRET},
367         scope => ['http://spreadsheets.google.com/feeds/'],
368         redirect_uri => 'https://developers.google.com/oauthplayground',
369     );
370     my $session = retrieve($session_filename);
371     my $restored_token = Net::OAuth2::AccessToken->session_thaw(
372         $session,
373         auto_refresh => 1,
374         profile => $oauth2->oauth2_webserver,
375     );
376     $oauth2->access_token($restored_token);
377     my $service = Net::Google::Spreadsheets->new(auth => $oauth2);
378
379     my $spreadsheet = $service->spreadsheet(
380         {  
381             title => $MIGSCHEMA
382         }
383     );
384     return $spreadsheet;
385 }
386
387