File Coverage

blib/lib/DBIx/Perform.pm
Criterion Covered Total %
statement 16 18 88.8
branch n/a
condition n/a
subroutine 6 6 100.0
pod n/a
total 22 24 91.6


line stmt bran cond sub pod time code
1              
2             # Brenton Chapin
3 1     1   11819 use 5.6.0;
  1         5  
  1         79  
4              
5             package DBIx::Perform;
6              
7 1     1   7 use strict;
  1         2  
  1         41  
8 1     1   6 use warnings;
  1         7  
  1         35  
9 1     1   1672 use POSIX;
  1         10362  
  1         85  
10 1     1   7999 use Carp;
  1         3  
  1         96  
11 1     1   2858 use Curses; # to get KEY_*
  0            
  0            
12             use DBIx::Perform::DButils;
13             use DBIx::Perform::UserInterface;
14             use DBIx::Perform::SimpleList;
15             use DBIx::Perform::Instruct;
16             use base 'Exporter';
17             use Data::Dumper;
18              
19             our $VERSION = '0.695';
20              
21             use constant 'KEY_DEL' => '330';
22              
23             use vars qw(@EXPORT_OK $DB $STH $STHDONE $MASTER_STH $MASTER_STHDONE );
24              
25             @EXPORT_OK = qw(run);
26              
27             # debug: set (unset) in runtime env
28             $::TRACE = $ENV{TRACE};
29             $::TRACE_DATA = $ENV{TRACE_DATA};
30              
31             our $GlobalUi = new DBIx::Perform::UserInterface;
32             #our $MasterList = new DBIx::Perform::SimpleList;
33              
34             #our $RowList = new DBIx::Perform::SimpleList;
35             our $RowList = undef;
36             our $DB;
37              
38             our $extern_name; #name of executable with external C functions
39              
40             #FIX this is off with respect to UserInterface
41             our %INSERT_RECALL = (
42             Pg => \&Pg_refetch,
43             Informix => \&Informix_refetch,
44             Oracle => \&Oracle_refetch,
45             );
46              
47             our %Tag_screens = ();
48              
49             # --- runtime subs ---
50              
51             sub run {
52             my $fname = shift;
53             $extern_name = shift;
54              
55             # can't vouch for any other than yml
56             my $file_hash = $GlobalUi->parse_yml_file($fname); # xml file
57             #my $file_hash = $GlobalUi->parse_xml_file ($fname); # xml file
58             #my $file_hash = $GlobalUi->parse_per_file ($fname); # per file
59              
60             $DB = DBIx::Perform::DButils::open_db( $file_hash->{'db'} );
61              
62             register_button_handlers();
63              
64             $RowList = $GlobalUi->get_current_rowlist;
65              
66             $GlobalUi->run;
67             }
68              
69             sub register_button_handlers {
70              
71             # register the button handlers
72             $GlobalUi->register_button_handler( 'query', \&querymode );
73             $GlobalUi->register_button_handler( 'next', \&do_next );
74             $GlobalUi->register_button_handler( 'previous', \&do_previous );
75             $GlobalUi->register_button_handler( 'view', \&do_view );
76             $GlobalUi->register_button_handler( 'add', \&addmode );
77             $GlobalUi->register_button_handler( 'update', \&updatemode );
78             $GlobalUi->register_button_handler( 'remove', \&removemode );
79             $GlobalUi->register_button_handler( 'table', \&do_table );
80             $GlobalUi->register_button_handler( 'screen', \&do_screen );
81             $GlobalUi->register_button_handler( 'current', \&do_current );
82             $GlobalUi->register_button_handler( 'master', \&do_master );
83             $GlobalUi->register_button_handler( 'detail', \&do_detail );
84             $GlobalUi->register_button_handler( 'output', \&do_output );
85             $GlobalUi->register_button_handler( 'no', \&do_no );
86             $GlobalUi->register_button_handler( 'yes', \&do_yes );
87             $GlobalUi->register_button_handler( 'exit', \&doquit );
88             }
89              
90             sub clear_textfields {
91              
92             warn "TRACE: entering clear_textfields\n" if $::TRACE;
93              
94             my $fl = $GlobalUi->get_field_list;
95              
96             # my $app = $GlobalUi->{app_object};
97              
98             $fl->reset;
99             while ( my $fo = $fl->iterate_list ) {
100             my $tag = $fo->get_field_tag;
101             $fo->set_value('');
102             $GlobalUi->set_screen_value( $tag, '' );
103              
104             # my $scrns = get_screen_from_tag($ft);
105             # foreach my $scrn (@$scrns) {
106             # my $form = $GlobalUi->get_current_form;
107             # my $subform = $form->getSubform('DBForm');
108             # $subform->getWidget($ft)->setField( 'VALUE', '' );
109             # }
110              
111             }
112             warn "TRACE: leaving clear_textfields\n" if $::TRACE;
113             }
114              
115             sub clear_table_textfields {
116             my $mode = shift;
117              
118             warn "TRACE: entering clear_textfields\n" if $::TRACE;
119              
120             my $fl = $GlobalUi->get_field_list;
121             my $cur_tab = $GlobalUi->get_current_table_name;
122             my $app = $GlobalUi->{app_object};
123             my $joins_by_tag = $app->{joins_by_tag};
124              
125             return if $mode eq 'update';
126              
127             $fl->reset;
128             while ( my $fo = $fl->iterate_list ) {
129             my ( $tag, $table, $col ) = $fo->get_names;
130             if ( $cur_tab eq $table ) {
131              
132             next if ! $fo->allows_focus( $mode );
133              
134             next if $mode eq 'query'
135             && !defined $fo->{queryclear}
136             && $joins_by_tag->{$tag};
137              
138             next if $mode eq 'add'
139             && $joins_by_tag->{$tag};
140              
141             $fo->set_value('');
142             $GlobalUi->set_screen_value( $tag, '' );
143             }
144             }
145             warn "TRACE: leaving clear_textfields\n" if $::TRACE;
146             }
147              
148             #Clears the fields belonging to the detail table and not the master.
149             #Don't use "queryclear" attribute here. Believe this is supposed to work
150             #as if queryclear is false for all the fields.
151             sub clear_detail_textfields {
152             my $mastertbl = shift;
153             my $detailtbl = shift;
154             my $app = $GlobalUi->{app_object};
155             my $joins_by_tag = $app->{joins_by_tag};
156             my $fl = $GlobalUi->get_field_list;
157             my %master;
158              
159             $fl->reset;
160             while ( my $fo = $fl->iterate_list ) {
161             my ( $tag, $table, $col ) = $fo->get_names;
162             if ($joins_by_tag->{$tag}) {
163             $master{$tag} = 1 if $table eq $mastertbl;
164             # $detail{$tag} = 1 if $table eq $detailtbl;
165             }
166             }
167            
168             $fl->reset;
169             while ( my $fo = $fl->iterate_list ) {
170             my ( $tag, $table, $col ) = $fo->get_names;
171             if ($table eq $detailtbl && !$master{$tag}) {
172             $fo->set_value('');
173             $GlobalUi->set_screen_value( $tag, '' );
174             } else {
175             my $val = $GlobalUi->get_screen_value( $tag );
176             $fo->set_value($val);
177             }
178             }
179             }
180              
181              
182             # If there are no rows, it sets DONTSWITCH and statusbars a message.
183             # Returns true if no rows.
184             # Added check for "deletedrow", which is true if the user has deleted
185             # the current row.
186             sub check_rows_and_advise {
187             my $form = shift;
188             my $app = $GlobalUi->{app_object};
189              
190             if ($app->{deletedrow}) {
191             $GlobalUi->display_error('th47w');
192             $form->setField( 'DONTSWITCH', 1 );
193             return 1;
194             }
195             if ( $RowList->is_empty ) {
196             my $m = $GlobalUi->{error_messages}->{'th15.'};
197             $GlobalUi->display_error($m);
198             $form->setField( 'DONTSWITCH', 1 );
199             return 1;
200             }
201             if (my $row_status = refresh_row(1, 1)) {
202             if ($row_status == 2) {
203             $GlobalUi->display_error('so35.');
204             } else {
205             $GlobalUi->display_error('so34.');
206             }
207             return 1;
208             }
209             return undef;
210             }
211              
212             sub goto_screen {
213             my $dest_screen = shift;
214             my $app = $GlobalUi->{app_object};
215              
216             my $fn = $app->getField('form_name');
217             return 0 if ( $fn eq $dest_screen );
218              
219             #save status of source form
220             my $form = $app->getForm($fn);
221             my $wid = $form->getWidget('ModeButtons');
222             my $button = $wid->getField('VALUE');
223             $wid = $form->getWidget('InfoMsg');
224             my $info_msg = $wid->getField('VALUE');
225             $wid = $form->getWidget('ModeName');
226             my $name = $wid->getField('VALUE');
227             $wid = $form->getWidget('ModeLabel');
228             my $label = $wid->getField('VALUE');
229             my $focus = $form->getField('FOCUSED');
230              
231             $form->setField( 'EXIT', 1 );
232             $app->setField( 'form_name', $dest_screen );
233             warn "goto_screen: button = :$button:\n" if $::TRACE;
234              
235             #copy saved status into destination form
236             $form = $app->getForm($dest_screen);
237             $GlobalUi->{form_object} = $form;
238             $wid = $form->getWidget('ModeButtons');
239             $wid->setField( 'VALUE', $button );
240             $wid = $form->getWidget('InfoMsg');
241             $wid->setField( 'VALUE', $info_msg );
242             $wid = $form->getWidget('ModeName');
243             $wid->setField( 'VALUE', $name );
244             $wid = $form->getWidget('ModeLabel');
245             $wid->setField( 'X', length $name );
246             $wid->setField( 'VALUE', $label );
247             $wid->setField( 'COLUMNS', length $label );
248             $form->setField( 'FOCUSED', $focus );
249             my $tbln = $GlobalUi->{current_table_number};
250             $GlobalUi->update_table($tbln);
251              
252             $GlobalUi->clear_display_error;
253             return 1;
254             }
255              
256             sub goto_screen1 {
257             my $rv = goto_screen('Run0');
258              
259             return $rv;
260             }
261              
262             sub next_screen {
263             my $app = $GlobalUi->{app_object};
264              
265             my ( $cf, $cfa );
266             $cf = $app->getField('form_name');
267             $cfa = $app->getField('form_names');
268             my ($cfn) = $cf =~ /^Run(\d+)/;
269             $cfn++;
270             $cfn = 0 if ( $cfn >= @$cfa );
271             $cf = "Run$cfn";
272             return goto_screen($cf);
273             }
274              
275             sub do_screen {
276             my $app = $GlobalUi->{app_object};
277              
278             # $GlobalUi->clear_comment_and_error_display;
279             $GlobalUi->clear_display_comment;
280              
281             my $cf = $app->getField('form_name');
282             my $form = $app->getForm($cf);
283             # $GlobalUi->update_info_message( $form, 'screen' );
284             next_screen();
285              
286             $GlobalUi->clear_display_error;
287             $cf = $app->getField('form_name');
288             $form = $app->getForm($cf);
289             my $row = $RowList->current_row;
290             display_row( $form, $row );
291             $GlobalUi->set_field_bounds_on_screen;
292             $form->setField( 'DONTSWITCH', 1 );
293             }
294              
295             sub do_current {
296             refresh_row();
297             }
298              
299             sub refresh_row {
300             my $suppress_msg = shift;
301             my $test_only = shift;
302             my $driver = $DB->{'Driver'}->{'Name'};
303             my $form = $GlobalUi->{form_object};
304             my $current_table = $GlobalUi->get_current_table_name;
305             my $row;
306             my $sth;
307             # $GlobalUi->update_info_message( $form, 'current' );
308             $form->setField( 'DONTSWITCH', 1 );
309              
310             my $refetcher = $INSERT_RECALL{$driver} || \&Default_refetch;
311             if ( defined($refetcher) ) {
312             $row =
313             &$refetcher( $sth, $current_table, (), ());
314             }
315             my $changed = 0;
316             if ( defined($row) ) {
317             my $cur_row = $RowList->current_row;
318             for (my $idx = $#$row; $idx >= 0; $idx--) {
319             my $valdb = $row->[$idx];
320             next if !defined $valdb; #skip if not in current table
321             my $valmem = $cur_row->[$idx];
322             next if !defined $valmem;
323             if ($valdb ne $valmem) {
324             $cur_row->[$idx] = $row->[$idx] if !$test_only;
325             #warn "diff on $idx\n";
326             #warn "$valdb:\n";
327             #warn "$valmem:\n";
328             $changed = 1;
329             }
330             }
331             return $changed if $test_only;
332             if ($changed) {
333             my $subform = $form->getSubform('DBForm') || $form;
334             display_row( $subform, $cur_row );
335             unless ($suppress_msg) {
336             my $msg = $GlobalUi->{error_messages}->{'ro54.'};
337             $GlobalUi->display_status($msg);
338             }
339             } else {
340             unless ($suppress_msg) {
341             $GlobalUi->clear_display_error;
342             }
343             }
344             } else {
345             $changed = 2;
346             }
347             return $changed;
348             }
349              
350              
351              
352             # unsupported buttons
353              
354             sub do_view {
355             my $form = $GlobalUi->{form_object};
356             $form->setField( 'DONTSWITCH', 1 );
357             my $m = $GlobalUi->{error_messages}->{'th26d'};
358             $GlobalUi->display_error($m);
359              
360             return undef;
361             }
362              
363             sub do_output {
364             my $form = $GlobalUi->{form_object};
365             # $GlobalUi->update_info_message( $form, 'output' );
366             $form->setField( 'DONTSWITCH', 1 );
367             $GlobalUi->clear_comment_and_error_display;
368             my $m = $GlobalUi->{error_messages}->{'th26d'};
369             $GlobalUi->display_error($m);
370              
371             return undef;
372             }
373              
374             # implemented buttons
375              
376             sub find_best_screen_for_table {
377             # On Perform forms that have 2+ tables and 2+ screens,
378             # sperform may change screens when the user changes tables.
379             # It is not clear what logic sperform uses to pick a screen.
380             # It could be "1st screen with a field that is associated with only
381             # that table (not joined to any other table)". Or it could be
382             # "screen with the most fields associated with that table".
383             # Other heuristics can be devised that produce the same results
384             # as observed in sperform.
385             my $ctbl = shift;
386             my %first_scr;
387             my $fl = $GlobalUi->get_field_list;
388             $fl->reset;
389             while (my $fo = $fl->iterate_list) {
390             my ($tag, $tbl, $col) = $fo->get_names;
391             my $scrs = get_screen_from_tag($tag);
392             my $scr = @$scrs[0];
393             #warn "$tag $tbl.$col $scr\n";
394             if ($tbl eq $ctbl) {
395             if (!defined $first_scr{$tag} || $scr < $first_scr{$tag}) {
396             $first_scr{$tag} = $scr;
397             }
398             } else {
399             $first_scr{$tag} = -1;
400             }
401             }
402             my $fscr = 9999;
403             foreach my $scr (values %first_scr) {
404             $fscr = $scr if $scr >= 0 && $scr < $fscr;
405             }
406             $fscr = 0 if $fscr == 9999;
407             return $fscr;
408             }
409              
410             # returns list of field_tags used by a table
411             sub table_fields {
412             my $ctbl = shift;
413              
414             my %tables = ( "$ctbl" => 1 );
415             my %tags;
416             my $more;
417             my $fl = $GlobalUi->get_field_list;
418             do {
419             $more = 0;
420             $fl->reset;
421             while (my $fo = $fl->iterate_list) {
422             my ($tag, $tbl, $col) = $fo->get_names;
423             if ($tables{"$tbl"} || $tags{"$tag"}) {
424             $more = 1 if (!defined $tags{"$tag"}
425             || !defined $tables{"$tbl"});
426             $tables{"$tbl"} = 1;
427             $tags{"$tag"} = 1;
428             my $lookup = $fo->{lookup_hash};
429             if ($lookup) {
430             foreach my $lu (values %$lookup) {
431             foreach my $lu2 (keys %$lu) {
432             $tags{"$lu2"} = 0;
433             }
434             }
435             }
436             }
437             }
438             } while ($more);
439             #warn "fields used by table $ctbl =\n" . join ("\n", keys %tags) . "\n";
440             return %tags;
441             }
442              
443             sub do_table {
444             warn "TRACE: entering do_table\n" if $::TRACE;
445              
446             my $form = $GlobalUi->get_current_form;
447             my @tables = @{ $GlobalUi->{attribute_table_names} };
448              
449             warn "Attribute tables: @tables" if $::TRACE_DATA;
450              
451             # $GlobalUi->update_info_message( $form, 'table' );
452             $GlobalUi->clear_comment_and_error_display;
453             $form->setField( 'DONTSWITCH', 1 );
454              
455             $GlobalUi->increment_global_tablelist;
456             $GlobalUi->increment_global_rowlist;
457              
458             my $tbl = $GlobalUi->get_current_table_name;
459             table_fields($tbl);
460             my $scr = find_best_screen_for_table($tbl);
461             goto_screen("Run$scr");
462              
463             # toggle the brackets around a field on the screen
464             $form = $GlobalUi->get_current_form;
465             my $subform = $form->getSubform('DBForm');
466             $subform->setField('editmode', 'query');
467             $GlobalUi->set_field_bounds_on_screen;
468              
469             $RowList = $GlobalUi->get_current_rowlist;
470             # display_row( $form, $RowList->current_row );
471              
472             warn "TRACE: leaving do_table\n" if $::TRACE;
473             }
474              
475             sub doquit {
476             my $key = shift;
477             my $form = shift;
478             my $app = $GlobalUi->{app_object};
479              
480             $form->setField( 'EXIT', 1 );
481             $app->setField( 'EXIT', 1 );
482             extern_exit();
483             system 'clear';
484             exit;
485             }
486              
487             sub do_yes {
488             my $key = shift;
489             my $form = shift;
490              
491             do_remove( $key, $form );
492              
493             do_no( $key, $form);
494             }
495              
496             sub do_no {
497             my $key = shift;
498             my $form = shift;
499              
500             warn "TRACE: entering do_no\n" if $::TRACE;
501             $GlobalUi->change_focus_to_button( $form, 'perform' );
502             # $GlobalUi->update_info_message( $form, 'remove' );
503             my $wid = $form->getWidget('ModeButtons');
504             $wid->setField('VALUE', 6); #'6' is the "Remove" button
505             }
506              
507             # called from button_push with the top-level form.
508             sub changemode {
509             my $mode = shift;
510             my $mode_resume = shift;
511              
512             my $app = $GlobalUi->{app_object};
513              
514             #my $fn = $app->getField('form_name');
515             #my $form = $app->getForm($fn);
516             my $form = $GlobalUi->get_current_form;
517              
518             my $subform = $form->getSubform('DBForm') || $form;
519             my $fl = $GlobalUi->get_field_list;
520              
521             my $table = $GlobalUi->get_current_table_name;
522             my @taborder =
523             DBIx::Perform::Forms::temp_generate_taborder( $table, $mode );
524              
525             clear_table_textfields($mode);
526              
527             # change the UI mode
528             $GlobalUi->change_mode_display( $form, $mode );
529             # $GlobalUi->update_info_message( $form, $mode );
530              
531             my $scr = find_best_screen_for_table($table);
532             if (goto_screen("Run$scr")) {
533             # if ( goto_screen1() ) {
534             $app->setField( 'resume_command', $mode_resume );
535             return 1;
536             }
537              
538             my $actkey = trigger_ctrl_blk( 'before', $mode, $table );
539             return if $actkey eq "\cC";
540              
541             $app->{fresh} = 1;
542              
543             $GlobalUi->{focus} = $taborder[0];
544              
545             $subform->setField( 'TABORDER', \@taborder );
546             $subform->setField( 'FOCUSED', $taborder[0] ); # first field.
547             $subform->setField( 'editmode', $mode );
548              
549             return 0;
550             }
551              
552             sub querymode {
553             warn "TRACE: entering querymode\n" if $::TRACE;
554              
555             $GlobalUi->clear_comment_and_error_display;
556              
557             warn "TRACE: leaving querymode\n" if $::TRACE;
558             return if changemode( 'query', \&querymode_resume );
559             }
560              
561             # Called as a resume entry, 'cause we have to force the form into
562             # the subform since we can't rely on lack of DONTSWITCH to switch there.
563             sub querymode_resume {
564             my ($form) = @_;
565             querymode(@_);
566             $form->setField( 'FOCUSED', 'DBForm' );
567             }
568              
569             sub do_master {
570             warn "TRACE: entering do_master\n" if $::TRACE;
571              
572             my $app = $GlobalUi->{app_object};
573             my $form = $GlobalUi->get_current_form;
574              
575             # $GlobalUi->update_info_message( $form, 'master' );
576             $GlobalUi->clear_comment_and_error_display;
577             $form->setField( 'DONTSWITCH', 1 );
578              
579             my ( $master, $detail );
580             my $ct = $GlobalUi->get_current_table_name;
581             my ( $m, $d ) = $GlobalUi->get_master_detail_table_names($ct);
582              
583             my @masters = @$m;
584             $master = $masters[0];
585             my @details = @$d;
586             $detail = $details[0] || '';
587              
588             if ( $ct eq $detail ) { # switch to master from detail
589             if ( my $tb = $GlobalUi->go_to_table($master) ) {
590              
591             my $tbl = $GlobalUi->get_current_table_name;
592             my $scr = find_best_screen_for_table($tbl);
593             goto_screen("Run$scr");
594              
595             # $GlobalUi->update_info_message( $form, 'master' );
596             $GlobalUi->clear_comment_and_error_display;
597             $form->setField( 'DONTSWITCH', 1 );
598              
599             # toggle the brackets around a field on the screen
600             $GlobalUi->set_field_bounds_on_screen;
601              
602             $RowList = $GlobalUi->get_current_rowlist;
603             display_row( $form, $RowList->current_row );
604             warn "TRACE: leaving do_master\n" if $::TRACE;
605             return;
606             }
607             warn "TRACE: leaving do_master\n" if $::TRACE;
608             die "something wrong with do_master";
609             }
610              
611             $form->setField( 'DONTSWITCH', 1 );
612             my $msg = $GlobalUi->{error_messages}->{'no47.'};
613             $GlobalUi->display_error($msg);
614             warn "TRACE: leaving do_master\n" if $::TRACE;
615             return undef;
616             }
617              
618             # . switches to the detail table if in a master table
619             # and does a query
620             # . sends a status message if current table isn't a detail
621              
622             sub do_detail {
623             warn "TRACE: entering do_detail\n" if $::TRACE;
624              
625             my $app = $GlobalUi->{app_object};
626             my $form = $GlobalUi->get_current_form;
627             my $subform = $form->getSubform('DBForm') || $form;
628              
629             # $GlobalUi->update_info_message( $form, 'detail' );
630             $GlobalUi->clear_comment_and_error_display;
631             $form->setField( 'DONTSWITCH', 1 );
632              
633             my $ct = $GlobalUi->get_current_table_name;
634             my ( $m, $d ) = $GlobalUi->get_master_detail_table_names($ct);
635              
636             my @masters = @$m;
637             my @details = @$d;
638             my ( $master, $detail );
639              
640             if ( $#masters > 0 ) {
641             $master = $masters[1];
642             $detail = $details[1];
643             }
644             else {
645             $master = $masters[0] || '';
646             $detail = $details[0];
647             }
648              
649             if ( $ct eq $master ) { # switch to detail from master
650             my $master_is_empty = $RowList->is_empty;
651             if ( my $tb = $GlobalUi->go_to_table($detail) ) {
652              
653             my $tbl = $GlobalUi->get_current_table_name;
654             my $scr = find_best_screen_for_table($tbl);
655             goto_screen("Run$scr");
656              
657             # $GlobalUi->update_info_message( $form, 'master' );
658             $GlobalUi->clear_comment_and_error_display;
659             $form->setField( 'DONTSWITCH', 1 );
660              
661             # toggle the brackets around a field on the screen
662             $GlobalUi->set_field_bounds_on_screen;
663              
664             $RowList = $GlobalUi->get_current_rowlist;
665             # display_row( $form, $RowList->current_row );
666              
667             clear_detail_textfields($master, $detail);
668             if ( $master_is_empty ) {
669             $GlobalUi->display_error('no11d');
670             } else {
671             do_query;
672             }
673              
674             warn "TRACE: leaving do_detail\n" if $::TRACE;
675             return;
676             }
677             warn "TRACE: leaving do_detail\n" if $::TRACE;
678             die "something wrong with do_detail";
679             }
680              
681             $form->setField( 'DONTSWITCH', 1 );
682             $GlobalUi->display_error('no48.');
683             warn "TRACE: leaving do_detail\n" if $::TRACE;
684             return undef;
685             }
686              
687             sub do_previous {
688             my $key = shift;
689             my $form = shift;
690             my $app = $GlobalUi->{app_object};
691              
692             # $GlobalUi->update_info_message( $form, 'previous' );
693             $GlobalUi->clear_comment_and_error_display;
694             $form->setField( 'DONTSWITCH', 1 );
695             $GlobalUi->clear_display_error;
696              
697             if ( $RowList->is_empty ) {
698             $GlobalUi->display_error('no16.');
699             $app->{deletedrow} = 0;
700             return;
701             }
702             if ( $RowList->is_first ) {
703             my $row = $RowList->current_row;
704             display_row( $form, $row );
705              
706             # at the end of the list, switch to "Previous" button
707             $form->getWidget('ModeButtons')->setField( 'VALUE', 2 );
708             $GlobalUi->display_error('no41.');
709             # $GlobalUi->update_info_message( $form, 'previous' );
710             return unless $app->{deletedrow};
711             }
712             my $distance = $app->{'number'};
713             $distance = 1 unless $distance;
714             $app->{deletedrow} = 0;
715              
716             # Perform counts down from the most recent fetch - don't know why
717             my $row = $RowList->previous_row($distance);
718             display_row( $form, $row );
719              
720             if (my $row_status = refresh_row(1, 1)) {
721             if ($row_status == 2) {
722             $GlobalUi->display_error('so35.');
723             } else {
724             $GlobalUi->display_error('so34.');
725             }
726             }
727             }
728              
729             sub do_next {
730             my $key = shift;
731             my $form = shift;
732             my $app = $GlobalUi->{app_object};
733              
734             # $GlobalUi->update_info_message( $form, 'next' );
735             $GlobalUi->clear_display_error;
736             $form->setField( 'DONTSWITCH', 1 );
737             $GlobalUi->clear_display_error;
738              
739             if ( $RowList->is_empty ) {
740             $GlobalUi->display_error('no16.');
741             $app->{deletedrow} = 0;
742             return;
743             }
744             if ( $RowList->is_last ) {
745             my $row = $RowList->current_row;
746             display_row( $form, $row );
747              
748             # at the end of the list, switch to "Next" button
749             $form->getWidget('ModeButtons')->setField( 'VALUE', 1 );
750             $GlobalUi->display_error('no41.');
751             # $GlobalUi->update_info_message( $form, 'next' );
752             return unless $app->{deletedrow};
753             }
754             my $distance = $app->{'number'};
755             $distance = 1 unless $distance;
756             $distance = 0 if $app->{deletedrow};
757             $app->{deletedrow} = 0;
758              
759             # Perform counts down from the most recent fetch (up for prev)
760             my $row = $RowList->next_row($distance);
761             display_row( $form, $row );
762              
763             if (my $row_status = refresh_row(1, 1)) {
764             if ($row_status == 2) {
765             $GlobalUi->display_error('so35.');
766             } else {
767             $GlobalUi->display_error('so34.');
768             }
769             }
770             }
771              
772             sub addmode {
773             warn "TRACE: entering addmode\n" if $::TRACE;
774             return if changemode( 'add', \&addmode_resume );
775              
776             my $form = $GlobalUi->get_current_form;
777             my $subform = $form->getSubform('DBForm') || $form;
778             my $fl = $GlobalUi->get_field_list;
779              
780             $GlobalUi->clear_comment_and_error_display;
781              
782             # initalize any serial or default fields to screen
783             $fl->display_defaults_to_screen($GlobalUi);
784              
785             warn "TRACE: leaving addmode\n" if $::TRACE;
786             }
787              
788             sub addmode_resume {
789             my $subform = shift;
790             addmode(@_);
791             $subform->setField( 'FOCUSED', 'DBForm' );
792             }
793              
794             sub updatemode {
795             my $form = $GlobalUi->get_current_form;
796              
797             # $GlobalUi->update_info_message( $form, 'update' );
798             return if check_rows_and_advise($form);
799              
800             return if changemode( 'update', \&updatemode_resume );
801              
802             $GlobalUi->clear_comment_and_error_display;
803              
804             my $subform = $form->getSubform('DBForm');
805             my $fl = $GlobalUi->get_field_list;
806              
807             my $row = $RowList->current_row;
808              
809             $fl->reset;
810             while ( my $f = $fl->iterate_list ) {
811             my ( $ft, $tbl, $col ) = $f->get_names;
812             my $w = $subform->getWidget($ft);
813             next unless $col;
814             }
815             }
816              
817             sub updatemode_resume {
818             my ($form) = @_;
819             updatemode(@_);
820             $form->setField( 'FOCUSED', 'DBForm' );
821             }
822              
823             # sub edit_control #replaced with Perform::Instruct::trigger_ctrl_blk
824              
825             sub removemode {
826             my $key = shift;
827             my $form = shift;
828              
829             my %info_msgs = %{ $GlobalUi->{info_messages} };
830             my %err_msgs = %{ $GlobalUi->{error_messages} };
831             my @buttons = $GlobalUi->{buttons_yn};
832             my $app = $GlobalUi->{app_object};
833              
834             # $GlobalUi->update_info_message( $form, 'remove' );
835             $form->setField( 'DONTSWITCH', 1 );
836             $GlobalUi->clear_comment_and_error_display;
837              
838             return if check_rows_and_advise($form);
839              
840             #'before remove' only works on tables. Don't believe it makes any
841             # sense to trigger off a column-- the smallest element that can be
842             # removed is 1 row.
843             my $table = $GlobalUi->get_current_table_name;
844             my $actkey = trigger_ctrl_blk( 'before', 'remove', $table );
845             return if $actkey eq "\cC";
846              
847             $GlobalUi->switch_buttons( $form );
848             # $GlobalUi->update_info_message( $form, 'yes' );
849             my $wid = $form->getWidget('ModeButtons');
850             $wid->setField('VALUE', 0);
851             }
852              
853             sub do_remove {
854              
855             #my $key = shift;
856             #my $form = shift;
857              
858             warn "TRACE: entering do_remove\n" if $::TRACE;
859              
860             my $app = $GlobalUi->{app_object};
861             my $form = $GlobalUi->get_current_form;
862             # $GlobalUi->update_info_message( $form, 'remove' );
863              
864             return if check_rows_and_advise($form);
865              
866             my $table = $GlobalUi->get_current_table_name;
867              
868             my $subform = $form->getSubform('DBForm');
869             my $fl = $GlobalUi->get_field_list;
870              
871             my @wheres = ();
872             my @values = ();
873              
874             my $row = $RowList->current_row;
875             my $aliases = $app->{'aliases'};
876              
877             my $ralias = $aliases->{"$table.rowid"};
878             my $ridx = $RowList->{aliases}->{$ralias};
879             my $wheres = "rowid = $$row[$ridx]";
880             my $cmd = "delete from $table where $wheres";
881              
882             warn "Remove command:\n$cmd\n" if $::TRACE_DATA;
883             my $rc = $DB->do( $cmd, {}, @values );
884             if ( !defined $rc ) {
885             my $m1 = $GlobalUi->{error_messages}->{'da11r'};
886             my $m2 = ": $DBI::errstr";
887             $GlobalUi->display_comment($m1);
888             $GlobalUi->display_error($m2);
889             }
890             else {
891             $GlobalUi->display_status('ro8d');
892             $RowList->remove_row;
893             clear_textfields();
894             $app->{deletedrow} = 1;
895             }
896             trigger_ctrl_blk( 'after', 'remove', $table );
897             $form->setField( 'DONTSWITCH', 1 ); # in all cases.
898              
899             $GlobalUi->change_mode_display( $subform, 'perform' );
900             warn "TRACE: exiting do_remove\n" if $::TRACE;
901             }
902              
903             sub OnFieldEnter {
904             my ( $status_bar, $field_tag, $subform, $key ) = @_;
905              
906             warn "TRACE: entering OnFieldEnter\n" if $::TRACE;
907             &$status_bar if ($status_bar);
908              
909             my $app = $GlobalUi->{app_object};
910             my $form = $GlobalUi->get_current_form;
911             my $fl = $GlobalUi->get_field_list;
912             my $table = $GlobalUi->get_current_table_name;
913              
914              
915             my $fo = $fl->get_field_object( $table, $field_tag );
916             die "undefined field object" unless defined($fo);
917              
918             my $mode = $subform->getField('editmode');
919             my $widget = $subform->getWidget($field_tag);
920             my $val = $fo->get_value;
921             $val = '' unless defined $val;
922             if (length($val) <= $widget->{CONF}->{COLUMNS} || $mode ne 'query') {
923             $widget->{CONF}->{'EXIT'} = 0;
924              
925             my $comment = $fo->{comments};
926             $comment
927             ? $GlobalUi->display_comment($comment)
928             : $GlobalUi->clear_display_comment;
929             } else {
930             $widget->{CONF}->{'EXIT'} = 1;
931             $widget->{CONF}->{OVERFLOW} = 1;
932             }
933              
934             # do any BEFORE control blocks.
935             my $actkey = trigger_ctrl_blk_fld( 'before', "edit$mode", $fo );
936              
937             bail_out() if ( $actkey eq "\cC" ); # 3 is ASCII code for ctrl-c
938              
939             warn "TRACE: leaving OnFieldEnter\n" if $::TRACE;
940             }
941              
942             sub OnFieldExit {
943             my ( $field_tag, $subform, $key ) = @_;
944              
945             my $widget = $subform->getWidget($field_tag);
946             my $ovf = $widget->{CONF}->{OVERFLOW} || 0;
947             return if $subform->getField('EXIT') && !$ovf;
948             warn "TRACE: entering OnFieldExit\n" if $::TRACE;
949              
950             my $app = $GlobalUi->{app_object};
951             my $form = $GlobalUi->get_current_form;
952             my $fl = $GlobalUi->get_field_list;
953             my $table = $GlobalUi->get_current_table_name;
954             my $fo = $fl->get_field_object( $table, $field_tag );
955             my $mode = $subform->getField('editmode');
956              
957             # erase comments and error messages
958             $GlobalUi->clear_comment_and_error_display;
959              
960             if ( $key eq "\cp" ) {
961             my $aliases = $app->{'aliases'};
962             my $row = $RowList->current_row;
963             my ( $tag, $tbl, $col ) = $fo->get_names;
964             my $tnc = "$tbl.$col";
965             my $alias = $aliases->{$tnc};
966             # my $val = $row->{$alias};
967             my $idx = $RowList->{aliases}->{$alias};
968             my $val = $row->[$idx];
969             my ( $pos, $rc );
970             #warn "ctrl-p: $tag = $val\n";
971             ( $val, $rc ) = $fo->format_value_for_display( $val );
972             $fo->set_value($val);
973             $GlobalUi->set_screen_value( $tag, $val );
974             }
975              
976             if ($mode eq 'query' && length($fo->get_value) > $fo->{size} || $ovf ) {
977             my $wid = $form->getWidget('Comment');
978             my $val = $GlobalUi->get_screen_value($field_tag);
979             $wid->setField( 'VALUE', $val );
980             my $cursorpos = $widget->getField( 'CURSORPOS' );
981             $wid->{CONF}->{CURSORPOS} = $cursorpos;
982             my $mwh = $form->{MWH};
983             $wid->setField( 'NAME', $fo->{field_tag} );
984             $wid->{CONF}->{FOCUSSWITCH} = "\t\n\cp\cw\cc\ck\c[\cf\cb";
985             $wid->{CONF}->{FOCUSSWITCH_MACROKEYS} = [ KEY_UP, KEY_DOWN, KEY_DEL ];
986             $wid->{CONF}->{EXIT} = 0;
987             $wid->draw($mwh);
988             $key = $wid->execute($mwh);
989             $val = $wid->getField( 'VALUE' );
990             $wid->setField( 'NAME', '');
991             $widget->{CONF}->{OVERFLOW} = 0;
992             $fo->set_value($val);
993             $GlobalUi->set_screen_value( $field_tag, $val );
994             }
995             $widget->setField( 'CURSORPOS', 0 );
996             if ($mode ne "query" && !$GlobalUi->{newfocus}) {
997             my $good = 1;
998             my $val = $GlobalUi->get_screen_value($field_tag);
999             $good = 0 if ($fo->validate_input($val, $mode) < 0);
1000             if ($good) {
1001             if ($fo->is_any_numeric_db_type) {
1002             if (!$fo->is_number($val) && $val ne '') {
1003             $GlobalUi->display_error('er11d');
1004             $good = 0;
1005             }
1006             }
1007             if ($good) {
1008             $val = $fo->get_value;
1009             my ($junk, $rc) = $fo->format_value_for_display($val);
1010             $good = !$rc;
1011             $GlobalUi->display_error('er11d') if (!$good);
1012             }
1013             if ($good) {
1014             $good = 0 if (!verify_joins($table, $field_tag));
1015             }
1016             }
1017             if (!$good) {
1018             $GlobalUi->{newfocus} = $GlobalUi->{focus}
1019             }
1020             }
1021              
1022             trigger_lookup($field_tag);
1023              
1024             my $actkey = trigger_ctrl_blk_fld( 'after', "edit$mode", $fo );
1025             my $value = $fo->get_value;
1026              
1027             $key = $actkey if !defined $key || $actkey ne "\c0";
1028             warn "key: [" . unpack( 'U*', $key ) . "]\n" if $::TRACE;
1029              
1030             $subform->setField( 'DONTSWITCH', 0 );
1031             if (
1032             $key eq "\t" # advance to the next field
1033             || $key eq "\n"
1034             || $key eq KEY_DOWN || $key eq KEY_RIGHT
1035             )
1036             {
1037              
1038             # $GlobalUi->clear_comment_and_error_display;
1039             return;
1040             }
1041              
1042             my $dontswitch = 1;
1043              
1044             if ( $key eq "\c[" ) {
1045             return if $GlobalUi->{newfocus};
1046             $actkey = trigger_ctrl_blk( 'after', "edit$mode", $table );
1047             return if $GlobalUi->{newfocus};
1048             my $wid = $form->getWidget('ModeButtons');
1049             my $mode =
1050             lc( ( $wid->getField('NAMES') )->[ $wid->getField('VALUE') ] );
1051             my $modesubs = $GlobalUi->{mode_subs};
1052             my $sub = $modesubs->{$mode}; # mode subroutine
1053              
1054             if ( $sub && ref($sub) eq 'CODE' ) {
1055             $dontswitch = 0; # let the sub decide.
1056             &$sub( $field_tag, $widget, $subform )
1057             ; # call the mode "do_add" etc..
1058             }
1059             else {
1060             beep();
1061             }
1062             }
1063             elsif ( $key eq "\cw" ) {
1064             $GlobalUi->display_help_screen(1);
1065             $GlobalUi->{'newfocus'} = $GlobalUi->{'focus'}
1066             unless $GlobalUi->{'newfocus'};
1067             return;
1068             }
1069             elsif ( $key eq "\cC" || $key eq KEY_DEL ) # Ctrl-C
1070             {
1071             bail_out();
1072             }
1073             elsif ($key eq "\cK"
1074             || $key eq KEY_UP
1075             || $key eq KEY_LEFT
1076             || $key eq KEY_BACKSPACE
1077             || $key eq KEY_STAB )
1078             {
1079             my $ct = $GlobalUi->get_current_table_name;
1080             # my $mode = $subform->getField('editmode');
1081              
1082             my @taborder =
1083             DBIx::Perform::Forms::temp_generate_taborder( $ct, $mode );
1084             my %taborder = map { ( $taborder[$_], $_ ) } ( 0 .. $#taborder );
1085             my $i = $taborder{ $GlobalUi->{'focus'} };
1086             $i = ( $i <= 0 ) ? $#taborder : $i - 1;
1087              
1088             $subform->setField( 'FOCUSED', $taborder[$i] );
1089             $GlobalUi->{'newfocus'} = $taborder[$i]
1090             unless $GlobalUi->{'newfocus'};
1091              
1092             # $GlobalUi->clear_comment_and_error_display;
1093              
1094             return;
1095             }
1096             elsif ( $key eq "\cF" ) {
1097             my $ct = $GlobalUi->get_current_table_name;
1098             # my $mode = $subform->getField('editmode');
1099             my @taborder =
1100             DBIx::Perform::Forms::temp_generate_taborder( $ct, $mode );
1101             my %taborder = map { ( $taborder[$_], $_ ) } ( 0 .. $#taborder );
1102             my $i = $taborder{ $GlobalUi->{'focus'} };
1103             my $w = $subform->getWidget( $taborder[$i] );
1104             my $y_cur = $w->getField('Y');
1105             my $y = $y_cur;
1106             my $screenpad = 0;
1107             my $limit = @taborder + 0;
1108             do {
1109             $i = ( $i >= $#taborder ) ? 0 : $i + 1;
1110             my ( $cf, $cfa, $cfn );
1111             $cf = $app->getField('form_name');
1112             $cfa = $app->getField('form_names');
1113             ($cfn) = $cf =~ /^Run(\d+)/;
1114             my $limit2 = @$cfa + 0;
1115             do {
1116             $w = $subform->getWidget( $taborder[$i] );
1117             unless ( defined $w ) {
1118             $cfn++;
1119             $cfn = 0 if ( $cfn >= @$cfa );
1120             $cf = "Run$cfn";
1121             my $form = $app->getForm($cf);
1122             $subform = $form->getSubform('DBForm');
1123             $screenpad += $y_cur + 1;
1124             }
1125             $limit2--;
1126             } while ( !( defined $w ) && $limit2 >= 0 );
1127             $limit--;
1128             $y = $w->getField('Y') + $screenpad;
1129             } while ( $y <= $y_cur && $limit >= 0 );
1130             $i = $taborder{ $GlobalUi->{'focus'} } unless $limit >= 0;
1131             $GlobalUi->{'newfocus'} = $taborder[$i]
1132             unless $GlobalUi->{'newfocus'};
1133             }
1134             elsif ( $key eq "\cB" ) {
1135             my $ct = $GlobalUi->get_current_table_name;
1136             # my $mode = $subform->getField('editmode');
1137             my @taborder =
1138             DBIx::Perform::Forms::temp_generate_taborder( $ct, $mode );
1139             my %taborder = map { ( $taborder[$_], $_ ) } ( 0 .. $#taborder );
1140             my $i = $taborder{ $GlobalUi->{'focus'} };
1141             my $w = $subform->getWidget( $taborder[$i] );
1142             my $y_cur = $w->getField('Y');
1143             my $y = $y_cur;
1144             my $screenpad = 0;
1145             my $limit = @taborder + 0;
1146             do {
1147             $i = ( $i <= 0 ) ? $#taborder : $i - 1;
1148             my ( $cf, $cfa, $cfn );
1149             $cf = $app->getField('form_name');
1150             $cfa = $app->getField('form_names');
1151             ($cfn) = $cf =~ /^Run(\d+)/;
1152             my $limit2 = @$cfa + 0;
1153             do {
1154             $w = $subform->getWidget( $taborder[$i] );
1155             unless ( defined $w ) {
1156             $cfn--;
1157             $cfn = $#$cfa if ( $cfn < 0 );
1158             $cf = "Run$cfn";
1159             my $form = $app->getForm($cf);
1160             $subform = $form->getSubform('DBForm');
1161             $screenpad -= 256; #FIX -- can't guarantee < 256 lines
1162             }
1163             $limit2--;
1164             } while ( !( defined $w ) && $limit2 >= 0 );
1165             $limit--;
1166             $y = $w->getField('Y') + $screenpad;
1167             } while ( $y >= $y_cur && $limit >= 0 );
1168             $i = $taborder{ $GlobalUi->{'focus'} } unless $limit >= 0;
1169             $GlobalUi->{'newfocus'} = $taborder[$i]
1170             unless $GlobalUi->{'newfocus'};
1171             }
1172              
1173             if ($dontswitch) {
1174             $subform->setField( 'DONTSWITCH', 1 );
1175             }
1176              
1177             warn "TRACE: leaving OnFieldExit\n" if $::TRACE;
1178             }
1179              
1180             sub bail_out {
1181             warn "TRACE: entering bail_out\n" if $::TRACE;
1182             my $app = $GlobalUi->{app_object};
1183             my $form = $GlobalUi->get_current_form;
1184             my $subform = $form->getSubform('DBForm');
1185              
1186             # Bailing out of Query, Add, Update or Modify.
1187             # Re-display the row as it was, if any.
1188             if ( $RowList->not_empty ) {
1189             display_row( $subform, $RowList->current_row );
1190             }
1191             # else {
1192             # clear_textfields();
1193             # }
1194              
1195             # Back to top menu
1196             $GlobalUi->clear_comment_and_error_display;
1197              
1198             $form->setField( 'DONTSWITCH', 0 );
1199             $subform->setField( 'EXIT', 1 );
1200              
1201             my $wname = $subform->getField('FOCUSED');
1202             my $wid = $subform->getWidget($wname);
1203             $wid->{CONF}->{'EXIT'} = 1;
1204             $GlobalUi->change_mode_display( $subform, 'perform' );
1205             }
1206              
1207             #if the given field joins columns, and one of those other than the given
1208             #table.column has "*", then must do a query.
1209             #If the result of the query is that the current value is not in that
1210             #other table.column, then the input must be rejected and the cursor
1211             #kept in the field.
1212             sub verify_joins {
1213             my $t = shift;
1214             my $f = shift;
1215             warn "TRACE: entering verify_joins for field $f, table $t\n" if $::TRACE;
1216             my $fl = $GlobalUi->get_field_list;
1217             my $fos = $fl->get_fields_by_field_tag($f);
1218             for (my $i = $#$fos; $i >= 0; $i--) {
1219             if ($$fos[$i]->{verify}) {
1220             my $dt = $$fos[$i]->{table_name};
1221             return 1 if $dt eq $t;
1222             my $dc = $$fos[$i]->{column_name};
1223             return verify_join($f, $dt, $dc);
1224             }
1225             my $luh = $$fos[$i]->{lookup_hash};
1226             foreach my $n (keys %$luh) {
1227             my $lus = $luh->{$n};
1228             foreach my $lu (keys %$lus) {
1229             if ($lus->{$lu}->{verify}) {
1230             my $dt = $lus->{$lu}->{join_table};
1231             my $dc = $lus->{$lu}->{join_column};
1232             return verify_join($f, $dt, $dc);
1233             }
1234             }
1235             }
1236             }
1237             return 1;
1238             }
1239              
1240             sub verify_join {
1241             my ($f, $dt, $dc) = @_;
1242              
1243             my $val = $GlobalUi->get_screen_value($f);
1244             my $query = "select $dt.$dc from $dt"
1245             . "\nwhere $dt.$dc = ?";
1246             warn "verify_join\n$query\n$val\n" if $::TRACE;
1247              
1248             my $sth = $DB->prepare($query);
1249             warn "$DBI::errstr\n" unless $sth;
1250             $sth->execute(($val));
1251             my $ref = $sth->fetchrow_array;
1252             return 1 if $ref;
1253             my $m = sprintf($GlobalUi->{error_messages}->{'th55e'}, $dt);
1254             $GlobalUi->display_error($m);
1255             # $GlobalUi->display_error(" This is an invalid value --"
1256             # . " it does not exist in \"$dt\" table ");
1257             return 0;
1258             }
1259              
1260             sub verify_composite_joins {
1261             my $app = $GlobalUi->{app_object};
1262             my $instrs = $app->getField('instrs');
1263             my $composites = $instrs->{COMPOSITES};
1264              
1265             if (defined $composites) {
1266             my $current_tbl = $GlobalUi->get_current_table_name;
1267             my $fl = $GlobalUi->get_field_list;
1268             foreach my $co (@$composites) {
1269             if ( $co->{TBL1} eq $current_tbl
1270             || $co->{TBL2} eq $current_tbl) {
1271             my $tbln = 1;
1272             $tbln = 2 if $co->{VFY2} eq '*';
1273             my $tbl = $co->{"TBL$tbln"};
1274              
1275             my %wheres;
1276             my $col;
1277             for (my $i = 0; $i < @{$co->{COLS1}}; $i++ ) {
1278             $col = $co->{"COLS$tbln"}[$i];
1279             my $flds = $fl->get_fields_by_table_and_column($tbl, $col);
1280             my $val =
1281             $GlobalUi->get_screen_value($$flds[0]->{field_tag});
1282             $wheres{"$tbl.$col = ?"} = $val;
1283             }
1284              
1285             my $query = "select $tbl.$col\nfrom $tbl\nwhere\n"
1286             . join ("\nand ", keys %wheres);
1287             warn "verify_composite_joins:\n$query\n"
1288             . join (", ", values %wheres) . "\n" if $::TRACE;
1289              
1290             my $ref = 0;
1291             my $sth = $DB->prepare($query);
1292             warn "$DBI::errstr\n" unless $sth;
1293             $sth->execute(values %wheres) if $sth;
1294             $ref = $sth->fetchrow_array if $sth;
1295             return 1 if $ref;
1296              
1297             my $m = sprintf($GlobalUi->{error_messages}->{'in61e'}, $tbl);
1298             $GlobalUi->display_error($m);
1299             # $GlobalUi->display_error(" Invalid value -- its composite "
1300             # . "value does not exist in \"$tbl\" table ");
1301             return 0;
1302             }
1303             }
1304             }
1305             return 1;
1306             }
1307              
1308             # this sub is for debugging only
1309             #sub temp_which_subform_are_we_in
1310             #{
1311             # my $sf = shift;
1312             # my $app = $GlobalUi->{app_object};
1313             # my ($cfn, $cfa);
1314             # $cfa = $app->getField('form_names');
1315             # for ($cfn = 0; $cfn < @$cfa; $cfn++) {
1316             # my $cf = "Run$cfn";
1317             # my $form = $app->getForm($cf);
1318             # my $subform = $form->getSubform('DBForm');
1319             # return $cfn if ($subform == $sf);
1320             # }
1321             # return -1;
1322             #}
1323              
1324             #sub temp_get_screen_from_tag
1325             #{
1326             # my $tag = shift;
1327             # my $app = $GlobalUi->{app_object};
1328             #
1329             # my ($cfn, $cfa);
1330             # $cfa = $app->getField('form_names');
1331             # for ($cfn = 0; $cfn < @$cfa; $cfn++) {
1332             # my $cf = "Run$cfn";
1333             # my $form = $app->getForm($cf);
1334             # my $subform = $form->getSubform('DBForm');
1335             # return $cfn if (defined $subform->getWidget($tag));
1336             # }
1337             # return -1;
1338             #}
1339              
1340              
1341             sub get_screen_from_tag {
1342             my $tag = shift;
1343              
1344             unless ( defined $Tag_screens{$tag} ) {
1345             my @scrns = ();
1346             my $app = $GlobalUi->{app_object};
1347             my ( $cfn, $cfa );
1348             $cfa = $app->getField('form_names');
1349             for ( $cfn = 0 ; $cfn < @$cfa ; $cfn++ ) {
1350             my $cf = "Run$cfn";
1351             my $form = $app->getForm($cf);
1352             my $subform = $form->getSubform('DBForm');
1353             if ( defined $subform->getWidget($tag) ) {
1354             push @scrns, $cfn;
1355             }
1356             }
1357             $Tag_screens{$tag} = \@scrns;
1358             }
1359             return $Tag_screens{$tag};
1360             }
1361              
1362             #sub get_value_from_tag {
1363             # my $field_tag = shift;
1364             #
1365             # my $fl = $GlobalUi->get_field_list;
1366             # my $fo = get_field_object_from_tag($field_tag);
1367             # my $rv;
1368             # $rv = $fo->get_value if defined $fo;
1369             ##warn "get_value_from_tag: $rv field = :$field_tag:\n";
1370             # return $rv;
1371             #}
1372              
1373             sub get_field_object_from_tag {
1374             my $ft = shift;
1375             my $fl = $GlobalUi->get_field_list;
1376              
1377             $fl->reset;
1378             while ( my $fo = $fl->iterate_list ) {
1379             if ( $fo->{field_tag} eq $ft ) {
1380             return $fo;
1381             }
1382             }
1383             return undef;
1384             }
1385              
1386             #Lookups always go with a join. Given a line in a .per script:
1387             # f1 = t1.c1 lookup f2 = t2.c2 joining t2.c1
1388             #We fill f2 with t2.c2 from those rows of t2 in which t1.c1 = t2.c1
1389             #We fill immediately whenever the value in f1 changes.
1390             #Not certain what should happen if c1 has duplicate values.
1391             #active_tabcol = t1.c1, join_table = t2, join_column = t2.c1
1392             sub trigger_lookup {
1393             my $trigger_tag = shift;
1394             warn "TRACE: entering trigger_lookup for $trigger_tag\n" if $::TRACE;
1395             my $app = $GlobalUi->{app_object};
1396             my $tval = $GlobalUi->get_screen_value($trigger_tag);
1397             my $fl = $GlobalUi->get_field_list;
1398             my $fos = $fl->get_fields_by_field_tag($trigger_tag);
1399              
1400             my %compcol;
1401             my $composites;
1402             my $instrs = $app->getField('instrs');
1403             $composites = $instrs->{COMPOSITES} if $instrs;
1404             if ($composites) {
1405             for (my $i = $#$composites; $i >= 0; $i--) {
1406             my $cst = @$composites[$i];
1407             for (my $j = 2; $j > 0; $j--) {
1408             my $tbl = $cst->{"TBL$j"};
1409             my $cols = $cst->{"COLS$j"};
1410             for (my $k = $#$cols; $k >= 0; $k--) {
1411             my $col = $$cols[$k];
1412             $compcol{"$tbl.$col"} = $i;
1413             }
1414             }
1415             }
1416             }
1417              
1418              
1419             foreach my $f1o (@$fos) {
1420             my ( $f1, $t1, $c1 ) = $f1o->get_names;
1421             my $tnc = "$t1.$c1";
1422              
1423             $fl->reset;
1424             while ( my $fo = $fl->iterate_list ) {
1425             my ( $tag, $tbl, $col ) = $fo->get_names;
1426              
1427             if ( defined $fo->{active_tabcol}
1428             && $fo->{active_tabcol} eq $tnc ) {
1429             my $val;
1430             my $t2 = $fo->{join_table};
1431             my $c2 = $fo->{join_column};
1432             if ( defined $tval && $tval ne '' ) {
1433             my %tbls;
1434             $tbls{$t1} = 1;
1435             $tbls{$t2} = 1;
1436             $tbls{$tbl} = 1;
1437             my $cm = $c2;
1438             $cm = $c1 if $t1 eq $tbl;
1439             my $query =
1440             "select $col from $tbl"
1441             . "\nwhere $cm = ?";
1442             # "select $tbl.$col from " . join (', ', keys %tbls)
1443             # . " where $tnc = $t2.$c2"
1444             # . " and $tnc = ?";
1445             my $sth = $DB->prepare($query);
1446             warn "$DBI::errstr\n" unless $sth;
1447             $sth->execute(($tval)) if $sth;
1448             $val = $sth->fetchrow_array;
1449             warn "query = $query\nval = $tval\n" if $::TRACE;
1450             warn "tag = :$tag: result of query = :$val:\n"
1451             if defined $val && $::TRACE;
1452             }
1453             else {
1454             $val = '';
1455             }
1456             $fo->set_value($val);
1457             $app->{redraw_subform} = 1;
1458             my ( $pos, $rc );
1459             ( $val, $rc ) = $fo->format_value_for_display( $val );
1460             $GlobalUi->set_screen_value( $tag, $val );
1461             }
1462              
1463             }
1464              
1465             if ($composites) {
1466             #Seems it should be possible to handle composites with much less code
1467             #than this.
1468             my $idx = $compcol{"$tnc"};
1469             if (defined $idx && $idx >= 0) {
1470             my $cst = @$composites[$idx];
1471             $compcol{"$tnc"} = -1;
1472              
1473             my $v = 0;
1474             $v = 1 if $cst->{VFY1};
1475             $v = 2 if $cst->{VFY2};
1476             $v = $cst->{TBL1} eq $GlobalUi->get_current_table_name?2:1
1477             if !$v;
1478             my $cjtbl = $cst->{"TBL$v"};
1479              
1480             my $query;
1481             $fl->reset;
1482             while ( my $fo = $fl->iterate_list ) {
1483             my ( $tag, $tbl, $col ) = $fo->get_names;
1484             if ($tbl eq $cjtbl && !$compcol{"$tbl.$col"}) {
1485             my $cols = $cst->{"COLS$v"};
1486             $query = "select $col from $tbl\nwhere "
1487             . join(" = ?\nand ", @$cols);
1488             $query .= " = ?";
1489             my $good = 1;
1490             my @cjvals = ();
1491             for (my $i = 0; $i <= $#$cols; $i++) {
1492             if ($col eq $$cols[$i]) {
1493             $good = 0;
1494             last;
1495             }
1496             my $cjfs = $fl->get_fields_by_table_and_column(
1497             $cjtbl, $$cols[$i]);
1498             my $cjtag = $$cjfs[0]->{field_tag};
1499             my $val = $GlobalUi->get_screen_value($cjtag);
1500             if (!defined $val || $val eq '') {
1501             $good = 0;
1502             last;
1503             }
1504             push @cjvals, $val;
1505             }
1506             if ($good) {
1507             my $sth = $DB->prepare($query);
1508             warn "$DBI::errstr\n" unless $sth;
1509             $sth->execute(@cjvals);
1510             my $val = $sth->fetchrow_array;
1511             if ($::TRACE) {
1512             warn "composite join query =\n$query\n";
1513             warn "vals = " . join (", ", @cjvals) . "\n";
1514             warn "query result = $val\n";
1515             }
1516             $fo->set_value($val);
1517             $app->{redraw_subform} = 1;
1518             my ( $pos, $rc );
1519             ( $val, $rc )
1520             = $fo->format_value_for_display( $val );
1521             $GlobalUi->set_screen_value( $tag, $val );
1522             }
1523             }
1524             }
1525             }
1526             }
1527              
1528              
1529             }
1530             }
1531              
1532             #Complicated queries are tricky to get right. A perfectly valid query
1533             # may be unacceptably slow. Given 3 tables, t1, t2, and t3, each with
1534             # columns mca, mcb, ca, cb where mca and mcb are "matching columns"
1535             # (t1.mca = t2.mca) and t1.ca is unrelated to t2.ca and so on, we
1536             # want every row from t1, with 1 matching row (if any) from t2 and t3.
1537             # We have to use some means of getting just 1 row from t2 and t3 per row
1538             # from t1. Speaking of just t1 and t2, an inner join will leave out a row
1539             # from t1 if no rows in t2 match that row. An outer join will have 2 or
1540             # more rows in the results if more than 1 row of t2 matches a single row
1541             # of t1. So, neither delivered the desired results. (Just why sperform
1542             # works that way is another question that doesn't seem to have a good
1543             # answer.) An answer to this problem was to use a function that would
1544             # return just one row of t2 per row of t1, such as "min". The query then
1545             # became:
1546             #
1547             # select min(t2.ca) aa, min(t2.cb) ab, t1.ca ac, t1.cb ad
1548             # from t1, outer t2 where t1.mca = t2.mca
1549             # group by t1.ca, t1.cb
1550             #
1551             # This worked except when t1 had duplicate rows. However, when t3 is
1552             # thrown in the mix, and we join the tables with a relation between t2
1553             # and t3, then we have trouble. The query below might be extremely slow,
1554             # taking many hours to run:
1555             #
1556             # select min(t2.ca) aa, min(t2.cb) ab, t1.c2 ac, t1.c3 ad,
1557             # min(t3.ca) ae, min(t3.cb) af, t1.mca ag, min(t2.mcb) ah
1558             # from t1, outer t2, t3 where t1.mca = t2.mca and t2.mcb = t3.mcb
1559             # group by t1.c2, t1.c3, t1.mca
1560             #
1561             # As long as all the joins are between t1 and the other tables, the query
1562             # is fast. To handle the situation when they're not, needed to work out
1563             # another query formulation. Doing it in 2 queries with a temporary table
1564             # works:
1565             #
1566             # select min(t2.ca) aa, min(t2.cb) ab, t1.ca ac, t1.cb ad,
1567             # t1.mca ae, min(t2.mcb) af
1568             # from t1, outer t2 where t1.mca = t2.mca
1569             # group by t1.ca, t1.cb into temp tmpperl;
1570             # select tmpperl.aa aa, tmpperl.ab ab, tmpperl.ac ac, tmpperl.ad ad,
1571             # tmpperl.ae ae, min(t3.ca) af, min(t3.cb) ag
1572             # from tmpperl, outer t3
1573             # where tmpperl.af = t3.mcb
1574              
1575             # Take 6: Query is still not good enough.
1576             # At least 2 problems:
1577             # 1. The minimum value of each column may be in different rows,
1578             # and if min is used on more than one col, we want everything to be from
1579             # the same row.
1580             # 2. In a lookup, the form may ask for the same column in more than one place,
1581             # with different conditions.
1582              
1583             # Take 7: The query strategy had to change some more.
1584             # The strategy used in take5 could get columns from different rows of
1585             # joined tables, because all it did was get the minimum of each column
1586             # regardless of what rows the minimums of any other columns came from.
1587             # This version replaces the single query for those minimums with 2.
1588             # The 1st of the 2 queries gets only the minimum rowid. Then the 2nd does
1589             # not use minimum at all but instead gets the rest of the columns from
1590             # the joined table with "where joined.rowid = pf_tmpx.row_id".
1591              
1592             #Make a graph of all the joins.
1593             # (Each node represents a table, and each edge represents a join.)
1594             sub compute_joins {
1595             my (%joins, %tags);
1596             my $fl = $GlobalUi->get_field_list;
1597             $fl->reset;
1598             while ( my $fo = $fl->iterate_list ) {
1599             my ( $tag, $tbl, $col ) = $fo->get_names;
1600             #get joins in lookups
1601             if ($fo->{active_tabcol}) {
1602             my $t2 = $fo->{join_table};
1603             my $c2 = $fo->{join_column};
1604             my ( $t1, $c1 ) = $fo->{active_tabcol} =~ /(\w+)\.(\w+)/;
1605             if ($t1 ne $t2) {
1606             $joins{$t1}->{$t2}->{"$c1 $c2 $tag"} = 1;
1607             $joins{$t2}->{$t1}->{"$c2 $c1 $tag"} = 1;
1608             }
1609             }
1610             #get all other joins
1611             if ( defined $tags{$tag} ) {
1612             foreach my $jtag (keys %{$tags{$tag}}) {
1613             my ( $t1, $c1 ) = $jtag =~ /(\w+)\.(\w+)/;
1614             if ($t1 ne $tbl) {
1615             $joins{$t1}->{$tbl}->{"$c1 $col"} = 1;
1616             $joins{$tbl}->{$t1}->{"$col $c1"} = 1;
1617             }
1618             }
1619             }
1620             $tags{$tag}->{"$tbl.$col"} = 1;
1621             }
1622             return %joins;
1623             }
1624              
1625             sub get_query_conditions {
1626             my $qtbl = shift;
1627             my %wheres;
1628              
1629             my $fl = $GlobalUi->get_field_list;
1630             $fl->reset;
1631             while ( my $fo = $fl->iterate_list ) {
1632             next if $fo->{displayonly};
1633             my ( $tag, $tbl, $col ) = $fo->get_names;
1634             if ($qtbl eq $tbl) {
1635             my $val = $GlobalUi->get_screen_value($tag);
1636             $val = $fo->get_value if $fo->{right};
1637             if ( defined $val && $val ne '') {
1638             my ( $wexpr, $wv ) = query_condition( $tbl, $col, $val );
1639             $wheres{$wexpr} = \@$wv;
1640             }
1641             }
1642             }
1643             return %wheres;
1644             }
1645              
1646             #input is an array of "tbl.col" strings.
1647             #output is an array of "tbl.col alias" strings.
1648             sub append_aliases {
1649             my %tncs = @_;
1650             #warn "TRACE: entering append_alias\n";
1651             my $fl = $GlobalUi->get_field_list;
1652             my $app = $GlobalUi->{app_object};
1653             my $aliases = $app->{aliases};
1654             my %hash;
1655              
1656             my $ctbl = $GlobalUi->get_current_table_name;
1657             my %fields = table_fields($ctbl);
1658              
1659             my %aliased;
1660             foreach my $tnc (keys %tncs) {
1661             #warn "getting alias for $tnc\n";
1662             if (! $hash{$tnc}) {
1663             my ($t, $c) = $tnc =~ /(\w+)\.(\w+)/;
1664             my $flds = $fl->get_fields_by_table_and_column($t, $c);
1665             my $alias = $aliases->{$tnc};
1666             if (@$flds > 1) {
1667             my $i;
1668             for ($i = 0; $i < @$flds; $i++) {
1669             my $fo = @$flds[$i];
1670             my ( $tag, $tbl, $col ) = $fo->get_names;
1671             if (defined $fields{"$tag"}) {
1672             $alias = $aliases->{"$tnc $tag"};
1673             $aliased{"$tnc $alias"} = 1;
1674             }
1675             }
1676             } else {
1677             $aliased{"$tnc $alias"} = 1;
1678             }
1679             $hash{$tnc} = 1;
1680             }
1681             }
1682             #warn "TRACE: leaving append_alias\n";
1683             return %aliased;
1684             }
1685              
1686             #input is a "table" and an array of "col" strings.
1687             #output is an array of "tbl.col alias" strings.
1688             sub prepend_table_name {
1689             my $tbl = shift;
1690             my %cs = @_;
1691             my %tncs;
1692              
1693             foreach my $c (keys %cs) {
1694             $tncs{"$tbl.$c"} = 1;
1695             }
1696             return %tncs;
1697             }
1698              
1699             our $stmptn = 1; #temp table number, for queries
1700             our $stmptlun = 1; #temp table number for lookups, for queries
1701              
1702             #sub do_query_take7 {
1703             sub create_query {
1704             # my ( $field, $widget, $subform ) = @_;
1705             my $TMPTBL = "pf_tmp";
1706             my (%tbl_visit, %tbl_prev_visit, %tbl_cur_visit, %tbl_next_visit);
1707             my ($tmptn, $tmptlun) = ($stmptn, $stmptlun);
1708             my $more;
1709             my $current_tbl = $GlobalUi->get_current_table_name;
1710             my $app = $GlobalUi->{app_object};
1711             my $query;
1712             my @queries = ();
1713             my $sth;
1714              
1715             $app->{deletedrow} = 0;
1716             generate_query_aliases();
1717             my $aliases = $app->{'aliases'};
1718             my %joins = compute_joins;
1719              
1720             #warn Data::Dumper->Dump([%lookups], ['lookups']);
1721              
1722              
1723             #first query
1724             my %wheres = get_query_conditions($current_tbl);
1725             my $fl = $GlobalUi->get_field_list;
1726             my @colsa = $fl->get_columns($current_tbl);
1727             my %cols;
1728             map { $cols{$_} = 1; } @colsa;
1729             $cols{rowid} = 1;
1730              
1731             my %tncs = prepend_table_name($current_tbl, %cols);
1732             my %selects = append_aliases(%tncs);
1733              
1734              
1735             $query = "select\n" . join (",\n", keys %selects)
1736             . "\nfrom $current_tbl";
1737             my $query_wheres = "";
1738             $query_wheres = "\nwhere\n" . join ("\nand ", keys %wheres) if %wheres;
1739             my $query_count = "select count(*) from $current_tbl";
1740             $query .= $query_wheres;
1741             $query_count .= $query_wheres;
1742              
1743             my @vals;
1744             foreach my $val (values %wheres) {
1745             push @vals, @$val;
1746             }
1747              
1748              
1749             my @tables = ("$TMPTBL$tmptn");
1750             my %tblsintmp;
1751             $tblsintmp{$current_tbl} = 1;
1752             my @outertbls = keys %{$joins{$current_tbl}};
1753             $more = @outertbls;
1754              
1755              
1756              
1757             #Starting with $current_table, follow the joins as in a breadth first search.
1758             #The number of queries needed is 1 + 2x the depth of the search + lookups.
1759             while ($more) {
1760             $query .= "\ninto temp $TMPTBL$tmptn";
1761              
1762             #do the query for the rowid, and put the results into a temporary table
1763             warn "$query;\n" if $::TRACE;
1764              
1765             push @queries, $query;
1766              
1767              
1768              
1769             my %tmpcols = ();
1770             my %groupbys = ();
1771             foreach my $tnc (keys %tncs) {
1772             my $alias = $aliases->{$tnc};
1773             $tmpcols{"$alias $alias"} = 1;
1774             $groupbys{"$alias"} = 1;
1775             }
1776             %groupbys = prepend_table_name("$TMPTBL$tmptn", %groupbys);
1777              
1778             %selects = prepend_table_name("$TMPTBL$tmptn", %tmpcols);
1779             foreach my $tbl (@outertbls) {
1780             my $alias = $aliases->{"$tbl.rowid"};
1781             $tmpcols{"$alias $alias"} = 1;
1782             $selects{"min($tbl.rowid) $alias"} = 1;
1783             }
1784              
1785             @tables = ("$TMPTBL$tmptn");
1786             my %wheres = ();
1787             my %tblslookedup = ();
1788             my %tblsjoined = ();
1789             foreach my $t1 (@outertbls) {
1790             foreach my $t2 (keys %{$joins{$t1}}) {
1791             if ($tblsintmp{$t2}) {
1792             my $joincols = $joins{$t1}->{$t2};
1793             foreach my $join (keys %$joincols) {
1794             my ($c1, $c2, $junk, $tag)
1795             = $join =~ /(\w+) (\w+)( (\w+))?/;
1796             my $alias = $aliases->{"$t2.$c2"};
1797             if (! $tag) {
1798             $tblsjoined{$t1} = 1;
1799             $wheres{"$t1.$c1 = $TMPTBL$tmptn.$alias"} = 1;
1800             }
1801             }
1802             }
1803             }
1804             }
1805              
1806             # Deal with lookups. Could be prettier, but works.
1807             # To limit the number of queries, the first lookup to a new table is done
1808             # without creating another temporary table.
1809             my %wheres2;
1810             my %whereslu;
1811             my %selects2;
1812             my @lookuptbls;
1813             foreach my $t1 (@outertbls) {
1814             foreach my $t2 (keys %{$joins{$t1}}) {
1815             if ($tblsintmp{$t2} || $tblsintmp{$t1}) {
1816             my $joincols = $joins{$t1}->{$t2};
1817             foreach my $join (keys %$joincols) {
1818             my ($c1, $c2, $junk, $tag)
1819             = $join =~ /(\w+) (\w+)( (\w+))?/;
1820             my $alias = $aliases->{"$t2.$c2"};
1821             if ($tag) {
1822             my $fo = get_field_object_from_tag($tag);
1823             my ($lutag, $lutbl, $lucol) = $fo->get_names;
1824             my $aliaslu = $aliases->{"$lutbl.$lucol $lutag"};
1825             #warn "lookup $lutbl.$lucol $aliaslu where $t1.$c1 = $t2.$c2\n";
1826              
1827             my $alias1 = $alias;
1828             my $lucol2 = $c1;
1829             if ($lutbl eq $t2) {
1830             next unless $tblsintmp{$t1};
1831             $lucol2 = $c2;
1832             $alias1 = $aliases->{"$t1.$c1"};
1833             } else {
1834             next unless $tblsintmp{$t2};
1835             }
1836             if (! $tblslookedup{$t1}) {
1837             # first lookup joining a new table, no need for another temporary.
1838             $tblslookedup{$t1} = "$t2.$c2";
1839             # $wheres{"$t1.$c1 = $TMPTBL$tmptn.$alias1"} = 1;
1840             $whereslu{"$t1.$c1 = $TMPTBL$tmptn.$alias1"} = 1;
1841             } else {
1842             # table has been joined in a previous lookup, therefore make a query
1843             # into a separate temporary table, and join that temporary table.
1844             my $aliaslu2 = $aliases->{"$lutbl.$lucol2"};
1845             $query = "select\n$lutbl.$lucol $aliaslu"
1846             . ",\n$lutbl.$lucol2 $aliaslu2";
1847              
1848              
1849             #speeds up queries in cases where the looked up table has many rows
1850             # by limiting the number of rows fetched to <= number in the main query.
1851             foreach my $cond (keys %wheres) {
1852             if ($cond =~ /^$lutbl\./) {
1853             if ($query !~ /$TMPTBL$tmptn/) {
1854             $cond =~ /= (\w+\.(\w+))$/;
1855             $query .= ",\nmin($1) $2"
1856             . "\nfrom $lutbl,"
1857             . " $TMPTBL$tmptn"
1858             . "\nwhere"
1859             . "\n$cond";
1860             } else {
1861             $query .= "\nand $cond";
1862             }
1863             }
1864             }
1865             if ($query =~ /from $lutbl/) {
1866             $query .= "\ngroup by"
1867             . "\n$lutbl.$lucol,"
1868             . "\n$lutbl.$lucol2";
1869             } else {
1870             $query .= "\nfrom $lutbl";
1871             }
1872              
1873              
1874              
1875             $query .= "\ninto temp "
1876             . "${TMPTBL}lu$tmptlun";
1877             warn "$query;\n" if $::TRACE;
1878             push @queries, $query;
1879             $selects{"min(${TMPTBL}lu$tmptlun.rowid)"
1880             . " zlu$tmptlun"} = 1;
1881             push @lookuptbls, "${TMPTBL}lu$tmptlun";
1882             $whereslu{"${TMPTBL}lu$tmptlun.$aliaslu2"
1883             . " = $TMPTBL$tmptn.$alias1"} = 1;
1884             my $tn = $tmptn + 1;
1885             $selects2{$aliaslu} =
1886             "${TMPTBL}lu$tmptlun.$aliaslu $aliaslu";
1887             $wheres2{"${TMPTBL}lu$tmptlun.rowid"
1888             . " = $TMPTBL$tn.zlu$tmptlun"} = 1;
1889             $tmptlun++;
1890             }
1891             }
1892             }
1893             }
1894             }
1895             }
1896             %wheres = (%wheres, %whereslu);
1897              
1898            
1899              
1900             push @tables, @lookuptbls;
1901             push @tables, @outertbls;
1902             $query = "select\n" . join (",\n", keys %selects)
1903             . "\nfrom\n" . join (",\nouter ", @tables)
1904             . "\nwhere\n" . join ("\nand ", keys %wheres)
1905             . "\ngroup by\n" . join (",\n", keys %groupbys);
1906             $tmptn++;
1907             $query .= "\ninto temp $TMPTBL$tmptn";
1908              
1909             warn "$query;\n" if $::TRACE;
1910             push @queries, $query;
1911              
1912             #the query for the rows matching the rowids fetched in the previous
1913             #query, which will put the results into a temporary table
1914              
1915              
1916             $more = 0;
1917             %wheres = %wheres2;
1918             @tables = ("$TMPTBL$tmptn");
1919             push @tables, @lookuptbls;
1920             push @tables, @outertbls;
1921             %selects = prepend_table_name("$TMPTBL$tmptn", %tmpcols);
1922             foreach my $t1 (@outertbls) {
1923             @colsa = $fl->get_columns($t1);
1924             for (my $i = 0; $i < @colsa; $i++) {
1925             if (defined $tblslookedup{$t1}
1926             && $tblslookedup{$t1} eq "$t1.$colsa[$i]") {
1927             splice (@colsa, $i, 1);
1928             $i--;
1929             }
1930             }
1931              
1932             %cols = ();
1933             map { $cols{$_} = 1; } @colsa;
1934             #warn "---> cols:\n" . join ("\n", keys %cols) . "\n";
1935             my %newtncs = prepend_table_name($t1, %cols);
1936             %tncs = (%tncs, %newtncs);
1937             %newtncs = append_aliases(%newtncs);
1938             %selects = (%selects, %newtncs);
1939             my $alias = $aliases->{"$t1.rowid"};
1940             $wheres{"$TMPTBL$tmptn.$alias = $t1.rowid"} = 1;
1941             $tblsintmp{$t1} = 1;
1942             }
1943              
1944             #change some of the tables, for lookups
1945             foreach my $sel (keys %selects) {
1946             my ($alias) = $sel =~ / (\w+)$/;
1947             if ($selects2{$alias}) {
1948             delete $selects{$sel};
1949             $selects{$selects2{$alias}} = 1;
1950             }
1951             }
1952              
1953             my @newoutertbls;
1954             foreach my $t1 (keys %tblsjoined) {
1955             foreach my $t2 (keys %{$joins{$t1}}) {
1956             unless ($tblsintmp{$t2}) {
1957             $more = 1;
1958             push @newoutertbls, $t2;
1959             }
1960             }
1961             }
1962             @outertbls = @newoutertbls;
1963              
1964              
1965              
1966             $query = "select\n" . join (",\n", keys %selects)
1967             . "\nfrom\n" . join (",\nouter ", @tables)
1968             . "\nwhere\n" . join ("\nand ", keys %wheres);
1969              
1970             $tmptn++;
1971              
1972             }
1973              
1974             my $tn = $tmptn - 1;
1975             if ($tn > $stmptn) {
1976             my $alias = $aliases->{"$current_tbl.rowid"};
1977             $query .= "\norder by $TMPTBL$tn.$alias";
1978             # } else {
1979             # $query .= "\norder by $current_tbl.rowid";
1980             }
1981             push @queries, $query;
1982              
1983             warn "$query\n" if $::TRACE;
1984             warn "values for 1st query:\n" . join ("\n", @vals) . "\n" if $::TRACE;
1985              
1986             # compute indexes with final query
1987             $query =~ s/\nfrom (.|\n)*$//;
1988             $query =~ s/^select\n//;
1989             my %ialiases;
1990             my $i = 0;
1991             while ($query =~ s/\w+\.\w+ (\w+),?\n?//) {
1992             $ialiases{$1} = $i;
1993             $i++;
1994             }
1995             %{$RowList->{aliases}} = %ialiases;
1996              
1997              
1998             return ($tmptn, $tmptlun, $query_count, \@queries, \@vals);
1999             }
2000              
2001              
2002             sub do_query {
2003             my ($tmptn, $tmptlun, $query_count, $qref, $vref) = create_query;
2004             my @queries = @$qref;
2005             my @vals = @$vref;
2006             my $TMPTBL = "pf_tmp";
2007              
2008             warn "values for 1st query:\n" . join ("\n", @vals) . "\n" if $::TRACE;
2009             #execute the queries
2010              
2011             my $err = $GlobalUi->{error_messages};
2012             $GlobalUi->display_status( $err->{'se09.'} );
2013             Curses::refresh(curscr);
2014              
2015             my $errmsg;
2016             for (my $i = 0; $i < $#queries; $i++) {
2017             my $sth = $DB->prepare($queries[$i]);
2018             if ($sth) {
2019             my $result;
2020             if ($i == 0 && @vals) {
2021             $result = $sth->execute(@vals);
2022             } else {
2023             $result = $sth->execute;
2024             }
2025             if (!defined $result) {
2026             $errmsg = $DBI::errstr;
2027             last;
2028             }
2029             }
2030             else {
2031             $errmsg = $DBI::errstr; # =~ /SQL:[^:]*:\s*(.*)/;
2032             # warn "ERROR:\n$DBI::errstr\noccurred after\n$queries[$i]\n";
2033             last;
2034             }
2035             $GlobalUi->display_status( $err->{'se10.'} );
2036             Curses::refresh(curscr);
2037             }
2038              
2039             my $form = $GlobalUi->get_current_form;
2040             my $subform = $form->getSubform('DBForm') || $form;
2041             execute_query( $subform, $query_count, $queries[$#queries], \@vals );
2042              
2043             # drop temporary tables
2044             my @drops;
2045             my $tn = $tmptn - 1;
2046             while ($tn >= $stmptn) {
2047             push @drops, "drop table $TMPTBL$tn";
2048             $tn--;
2049             }
2050             for (my $i = $tmptlun; $i >= $stmptlun ; $i--) {
2051             push @drops, "drop table ${TMPTBL}lu$i";
2052             }
2053              
2054             for (my $i = $#drops; $i >= 0; $i--) {
2055             my $sth = $DB->prepare($drops[$i]);
2056             if ($sth) {
2057             $sth->execute;
2058             }
2059             # else {
2060             # warn "ERROR:\n$DBI::errstr\noccurred after\n$drops[$i]\n";
2061             # }
2062             }
2063              
2064             $stmptn = $tmptn;
2065             $stmptlun = $tmptlun;
2066              
2067             $GlobalUi->display_error($errmsg) if $errmsg;
2068             warn "leaving do_query\n" if $::TRACE;
2069             }
2070              
2071             sub query_condition {
2072             my ( $tbl, $col, $val ) = @_;
2073             my $err = $GlobalUi->{error_messages};
2074              
2075             #warn "parms = :$tbl:$col:$val:\n";
2076             # Determine what kind of comparison should be done
2077              
2078             my $op = '=';
2079             my $cval = $val;
2080             my @cvals = ();
2081              
2082             if ( $val eq '=' ) { $op = 'is null'; $cval = undef; }
2083             elsif ( $val =~ /^\s*(<<|>>)(.*?)$/ ) {
2084             $cval = query_condition_minmax($tbl, $col, $val);
2085             }
2086             elsif ( $val =~ /^\s*(([<>][<=>]?)|!?=)(.*?)$/ ) {
2087             $op = $1;
2088             $cval = $3;
2089             }
2090             elsif ( $val =~ /^(.+?):(.+)$/ ) {
2091             $op = "between ? and ";
2092             push( @cvals, $1 );
2093             $cval = $2;
2094             }
2095             elsif ( $val =~ /^(.+?)\|(.+)$/ ) { # might should use in ($1,$2)
2096             $op = "= ? or $col = ";
2097             push( @cvals, $1 );
2098             $cval = $2;
2099             }
2100             # SQL wildcard characters
2101             elsif ( $val =~ /[*%?]/ ) { $cval =~ tr/*?/%_/; $op = 'like'; }
2102              
2103             my $where = "$tbl.$col $op" . ( defined($cval) ? ' ?' : '' );
2104             push( @cvals, $cval ) if defined($cval);
2105             return ( $where, \@cvals );
2106             }
2107              
2108             #To handle min/max, do a query, then add
2109             # the results to the where clause. Ex, if asking for '>>' from
2110             # table and column 't.c', then the query here is:
2111             # select max(t.c) from t
2112             # If the result of that query is '41', then we add this to the wheres:
2113             # t.c = 41
2114             sub query_condition_minmax {
2115             my $tbl = shift;
2116             my $col = shift;
2117             my $qc = shift;
2118              
2119             my $mm = 'max';
2120             $mm = 'min' if $qc =~ /<
2121              
2122             my $query = "select $mm($tbl.$col) from $tbl";
2123              
2124             my $sth = $DB->prepare($query);
2125             if ($sth) {
2126             $sth->execute;
2127             }
2128             else {
2129             warn "$DBI::errstr\n";
2130             }
2131             my $ref = $sth->fetchrow_array;
2132             warn "query condition min/max is $ref\n" if $::TRACE;
2133             return $ref;
2134             }
2135              
2136             sub execute_query {
2137             my $subform = shift;
2138             my $query_count = shift;
2139             my $query = shift;
2140             my $vals = shift;
2141             my $app = $GlobalUi->{app_object};
2142             my $current_table = $GlobalUi->get_current_table_name;
2143             my $err = $GlobalUi->{error_messages};
2144              
2145             warn "entering execute_query\n" if $::TRACE;
2146             $GlobalUi->display_status( $err->{'se11.'} );
2147             Curses::refresh(curscr);
2148              
2149             # update row list
2150             my $row = $RowList->stuff_list( $DB, $query_count, $query, $vals );
2151             my $size = $RowList->list_size;
2152              
2153             # Print outcome of query to status bar
2154             if ( $size == 0 ) { $GlobalUi->display_status('no11d'); }
2155             elsif ( $size == 1 ) { $GlobalUi->display_status('1_8d'); }
2156             else {
2157             my $msg = sprintf($err->{'ro7d'}, $size);
2158             $GlobalUi->display_status($msg);
2159             }
2160              
2161             #execute any instructions triggered after a query
2162             trigger_ctrl_blk( 'after', 'query', $current_table );
2163              
2164             # display the first table
2165             display_row( $subform, $row );
2166              
2167             # change focus to the user interface
2168             $GlobalUi->change_mode_display( $subform, 'perform' );
2169              
2170             warn "TRACE: leaving execute_query\n" if $::TRACE;
2171             return $size;
2172             }
2173              
2174             sub next_alias {
2175             my $i = shift;
2176             my $reserved_words =
2177             'ada|add|all|and|any|are|asc|avg|bit|bor|day|dec'
2178             . '|end|eqv|for|get|iif|imp|int|key|lag|map|max|min'
2179             . '|mod|mtd|new|non|not|off|old|out|pad|qtd|ref|row'
2180             . '|set|sql|sum|top|use|var|wtd|xor|yes|ytd';
2181             my $alias;
2182             do {
2183             $alias =
2184             chr( $i / ( 26 * 26 ) + ord('a') )
2185             . chr( ( $i / 26 ) % 26 + ord('a') )
2186             . chr( $i % 26 + ord('a') );
2187             $i++;
2188             } while ( $alias =~ /$reserved_words/ );
2189             return ($alias, $i);
2190             }
2191              
2192             sub generate_query_aliases {
2193             my $app = $GlobalUi->{app_object};
2194              
2195             my $fl = $GlobalUi->get_field_list;
2196             $fl->reset;
2197             my $i = 0;
2198             my $j = 0; #(25 * 10) + 9;
2199             my $alias;
2200             my %aliases;
2201              
2202             while ( my $fo = $fl->iterate_list ) {
2203             next if $fo->{displayonly};
2204             ($alias, $i) = next_alias($i);
2205             my ( $tag, $tbl, $col ) = $fo->get_names;
2206             if (defined $fo->{subscript_floor} && defined $aliases{"$tbl.$col"}) {
2207             $i--;
2208             $alias = $aliases{"$tbl.$col"};
2209             }
2210             $aliases{"$tbl.$col"} = $alias;
2211             $aliases{"$tbl.$col $tag"} = $alias;
2212             if (defined $fo->{join_table}) {
2213             $tbl = $fo->{join_table};
2214             $col = $fo->{join_column};
2215             unless (defined $aliases{"$tbl.$col $tag"}) {
2216             ($alias, $i) = next_alias($i);
2217             $aliases{"$tbl.$col $tag"} = $alias;
2218             $aliases{"$tbl.$col"} = $alias;
2219             }
2220             }
2221             unless (defined $aliases{"$tbl.rowid"}) {
2222             $alias = 'z'
2223             . chr( $j / 10 + ord('0') )
2224             . chr( $j % 10 + ord('0') );
2225             $j++;
2226             $aliases{"$tbl.rowid"} = $alias;
2227             }
2228             }
2229             #warn Data::Dumper->Dump([%aliases], ['aliases']);
2230             $app->{aliases} = \%aliases;
2231             }
2232              
2233             sub do_subscript {
2234             my ($fo, $str) = @_;
2235             my $min = $fo->{subscript_floor}-1;
2236             my $max = $fo->{subscript_ceiling}-1;
2237             my $tag = $fo->get_field_tag;
2238             my $v = $GlobalUi->get_screen_value($tag);
2239             $str = '' if !defined $str;
2240             $v = '' if !defined $v;
2241             my @chars = split //, $str;
2242             my @vcs = split //, $v;
2243             my $max2 = $min + length $v;
2244             $max = $max2 if $max > $max2;
2245             my $i = $max;
2246             if ($v ne '') {
2247             for (; $i >= $min; $i--) {
2248             $chars[$i] = $vcs[$i-$min];
2249             }
2250             }
2251             for ($i = $max; $i >= 0; $i--) {
2252             $chars[$i] = ' ' if !defined $chars[$i] || $chars[$i] eq '';
2253             }
2254             $str = join ('', @chars);
2255             $str =~ s/\s+$//;
2256             #warn ":$v: is $min to $max of str =\n:$str:\n";
2257             return $str;
2258             }
2259              
2260             sub do_add {
2261             my ( $field, $widget, $subform ) = @_;
2262              
2263             warn "TRACE: entering do_add\n" if $::TRACE;
2264              
2265             my $app = $GlobalUi->{app_object};
2266             my $current_table = $GlobalUi->get_current_table_name;
2267             my $driver = $DB->{'Driver'}->{'Name'};
2268             my $fl = $GlobalUi->get_field_list;
2269             my $fo = $fl->get_field_object( $current_table, $field );
2270              
2271             my ( %ca, $row, $msg );
2272             $GlobalUi->change_mode_display( $subform, 'add' );
2273             $GlobalUi->update_subform($subform);
2274              
2275             # First test the input of the current field
2276              
2277             my $v = $fo->get_value;
2278             my $rc = $fo->validate_input( $v, 'add' );
2279             return if $rc != 0;
2280              
2281             generate_query_aliases();
2282              
2283             return if !verify_composite_joins();
2284              
2285             my %vals;
2286             # test the subform as a whole
2287             $fl->reset;
2288             while ( $fo = $fl->iterate_list ) {
2289             my ( $tag, $tbl, $col ) = $fo->get_names;
2290             next if $tbl ne $current_table; # FIX: single table adds...?
2291              
2292             my $v = $fo->get_value;
2293             next if $fo->is_serial || defined( $fo->{displayonly} );
2294              
2295             # special handling for subscript attribute
2296             if ( defined $fo->{subscript_floor}) {
2297             $vals{$col} = do_subscript($fo, $vals{$col});
2298             next;
2299             }
2300             else {
2301             $rc = $fo->format_value_for_database( 'add', undef );
2302             my $v2 = $fo->get_value;
2303             }
2304             return $rc if $rc != 0;
2305              
2306             # add col and val for the sql add
2307              
2308             if (defined $v) {
2309             $ca{$col} = $v if $v ne '';
2310             }
2311             }
2312              
2313             foreach my $col (keys %vals) {
2314             $ca{$col} = $vals{$col} if $vals{$col} ne '';
2315             }
2316              
2317             # insert to db
2318              
2319             my ( $serial_val, $serial_fo, $serial_col );
2320             undef $rc;
2321              
2322             my $holders = join ', ', map { "?" } keys %ca;
2323             my $cols = join ', ', keys %ca;
2324              
2325             my $cmd = "insert into $current_table ($cols) values ($holders)";
2326             my $sth = $DB->prepare($cmd);
2327              
2328             if ($sth) {
2329             $rc = $sth->execute(values %ca);
2330             }
2331             else {
2332             my $m = $GlobalUi->{error_messages}->{'ad21e'};
2333             $GlobalUi->display_error($m);
2334             }
2335             if ( $driver eq "Informix" ) {
2336             $serial_fo = $fl->get_serial_field; # returns one field or undef
2337             $serial_col = $serial_fo->{column_name};
2338             $serial_val = $sth->{ix_sqlerrd}[1]; # get db supplied value
2339              
2340             if ( defined($serial_val) && defined($serial_col) ) {
2341              
2342             $serial_fo->set_value($serial_val);
2343             $GlobalUi->set_screen_value( $serial_col, $serial_val );
2344             }
2345             }
2346             else { warn "$driver serial values not currently supported"; }
2347              
2348             if ( !defined $rc ) {
2349             my $m = ": $DBI::errstr";
2350             $GlobalUi->display_comment('db16e');
2351             $GlobalUi->display_error($m);
2352             $GlobalUi->change_mode_display( $subform, 'add' );
2353             return;
2354             }
2355              
2356             # refreshes the values on the screen after add
2357             my $refetcher = $INSERT_RECALL{$driver} || \&Default_refetch;
2358             if ( defined($refetcher) ) {
2359             $row = &$refetcher( $sth, $current_table );
2360             }
2361             if ( defined($row) ) {
2362             $RowList->add_row($row);
2363             display_row( $subform, $RowList->current_row );
2364             $msg = $GlobalUi->{error_messages}->{'ro6d'};
2365             $GlobalUi->display_status($msg);
2366             trigger_ctrl_blk( 'after', 'add', $current_table );
2367             }
2368             else {
2369             $msg = $GlobalUi->{error_messages}->{'fa39e'};
2370             $GlobalUi->display_error($msg);
2371             }
2372             $subform->setField( 'EXIT', 1 ); # back to menu
2373             $GlobalUi->change_mode_display( $subform, 'perform' );
2374              
2375             warn "TRACE: leaving do_add\n" if $::TRACE;
2376             return undef;
2377             }
2378              
2379             sub do_update {
2380             my $field = shift;
2381             my $widget = shift;
2382             my $subform = shift;
2383              
2384             return if !verify_composite_joins();
2385              
2386             my $app = $GlobalUi->{app_object};
2387             my $form = $GlobalUi->{form_object};
2388             my $fl = $GlobalUi->get_field_list;
2389             my $table = $GlobalUi->get_current_table_name;
2390             # my $singleton = undef;
2391              
2392             my %wheres = ();
2393             my %upds = ();
2394              
2395             my $aliases = $app->{'aliases'};
2396             my %aliased_upds = ();
2397             my $cur_row = $RowList->current_row;
2398              
2399             $GlobalUi->change_mode_display( $form, 'update' );
2400             $GlobalUi->update_subform($subform);
2401             $GlobalUi->change_mode_display( $subform, 'update' );
2402              
2403             my %vals;
2404             my %sstags;
2405             $fl->reset;
2406             while ( my $fo = $fl->iterate_list ) {
2407             my ( $tag, $tbl, $col ) = $fo->get_names;
2408             next if $tbl ne $table; # guess...
2409              
2410             # reexamine the placement of this test
2411             next if !( $fo->allows_focus('update') );
2412              
2413             my $tnc = "$tbl.$col";
2414             my $alias = $aliases->{$tnc};
2415             # next unless defined $cur_row->{$alias};
2416             my $idx = $RowList->{aliases}->{$alias};
2417             #warn "do_upd: $tnc -> $alias -> $idx\n" if $::TRACE;
2418             my $fv = $cur_row->[$idx];
2419             # next unless defined $fv;
2420             $fv = '' unless defined $fv;
2421             #warn "do_upd: field val = $fv\n" if $::TRACE;
2422              
2423             # get value from field
2424             my $v = $fo->get_value;
2425             my $rc = 0;
2426              
2427             # my $rc = $fo->validate_input( $v, 'update' );
2428             # return if $rc != 0;
2429              
2430             # # special handling for subscript attribute
2431             if ( defined $fo->{subscript_floor}) {
2432             $vals{$tnc} = do_subscript($fo, $vals{$tnc});
2433             push @{$sstags{$tnc}}, $tag;
2434             next;
2435             }
2436             else {
2437             $rc = $fo->format_value_for_database( 'update', undef );
2438             }
2439             return $rc if $rc != 0;
2440              
2441             # add col and val for the sql add
2442              
2443             # my $fv = $cur_row->{$alias} if defined $alias;
2444              
2445             #warn "$tag $col $alias\n:$v:\n:$fv:\n";
2446             if ( $v ne $fv ) {
2447             $upds{$col} = $v;
2448             $aliased_upds{$alias} = $v;
2449             }
2450              
2451             }
2452             # $fl->print_list;
2453              
2454             #strings composed of substrings
2455             foreach my $tnc (keys %vals) {
2456             my $alias = $aliases->{$tnc};
2457             my ($col) = $tnc =~ /\.(\w+)/;
2458             my $idx = $RowList->{aliases}->{$alias};
2459             my $fv = $cur_row->[$idx];
2460             my $v = $vals{$tnc};
2461             #warn "$tnc = :$v:$fv\n";
2462             my $dv = defined $v ? 1 : 0;
2463             my $dfv = defined $fv ? 1 : 0;
2464             if (($dv && $dfv && $v ne $fv)
2465             || ($dv ^ $dfv) ) {
2466             $upds{$col} = $v;
2467             $aliased_upds{$alias} = $v;
2468             foreach my $tag (@{$sstags{$tnc}}) {
2469             $alias = $aliases->{"$tnc $tag"};
2470             #warn "alias of $tag $tnc is $alias\n";
2471             $aliased_upds{$alias} = $v;
2472             }
2473             }
2474             }
2475              
2476             my @updcols = keys(%upds);
2477             if ( @updcols == 0 ) {
2478             $GlobalUi->display_status('no14d');
2479             $GlobalUi->change_mode_display( $form, 'update' );
2480             return;
2481             }
2482             my @updvals;
2483             for (my $i = 0; $i <= $#updcols; $i++) {
2484             if ($upds{$updcols[$i]} eq '') {
2485             $updcols[$i] .= ' = NULL';
2486             } else {
2487             push @updvals, $upds{$updcols[$i]};
2488             $updcols[$i] .= ' = ?';
2489             }
2490             }
2491             my $sets = join( ', ', @updcols);
2492             warn "updcols: [@updcols]" if $::TRACE_DATA;
2493              
2494             my $ralias = $aliases->{"$table.rowid"};
2495             # my @wherevals = ( $cur_row->{$ralias} );
2496             my $ridx = $RowList->{aliases}->{$ralias};
2497             my @wherevals = ( $cur_row->[$ridx] );
2498             my $cmd = "update $table set $sets where rowid = ?";
2499             warn "cmd: [$cmd]" if $::TRACE_DATA;
2500             warn "ud: [@updvals]" if $::TRACE_DATA;
2501             warn "whv: [@wherevals]" if $::TRACE_DATA;
2502              
2503             my $rc = $DB->do( $cmd, {}, @updvals, @wherevals );
2504             if ( !defined $rc ) {
2505              
2506             # display DB error string
2507             my $m1 = $GlobalUi->{error_messages}->{'db16e'};
2508             my $m2 = ": $DBI::errstr";
2509             $GlobalUi->display_comment($m1);
2510             $GlobalUi->display_error($m2);
2511             $GlobalUi->change_mode_display( $form, 'update' );
2512             return;
2513             }
2514             else {
2515              
2516             # refreshes the values on the screen after update
2517             my $driver = $DB->{'Driver'}->{'Name'};
2518             my $refetcher = $INSERT_RECALL{$driver} || \&Default_refetch;
2519             my $sth;
2520             my $row;
2521             if ( defined($refetcher) ) {
2522             $row =
2523             &$refetcher( $sth, $table );
2524             }
2525              
2526             my $m = $GlobalUi->{error_messages}->{'ro10d'};
2527             $m = ( 0 + $rc ) . " " . $m;
2528             $GlobalUi->display_status($m);
2529              
2530             # Since the new value is now in, change the where value...
2531             my $tmp = $RowList->current_row;
2532              
2533             map { $tmp->[$RowList->{aliases}->{$_}] = $aliased_upds{$_}; }
2534             keys %aliased_upds;
2535             for (my $i = $#$row; $i >= 0; $i--) {
2536             $tmp->[$i] = $row->[$i] if defined $row->[$i];
2537             }
2538             trigger_ctrl_blk( 'after', 'update', $table );
2539             display_row( $subform, $RowList->current_row );
2540             }
2541             $subform->setField( 'EXIT', 1 ); # back to menu
2542             $GlobalUi->change_mode_display( $subform, 'perform' );
2543             }
2544              
2545             sub display_row {
2546             my $form = shift;
2547             my $row = shift;
2548              
2549             warn "TRACE: entering display_row\n" if $::TRACE;
2550              
2551             #warn Data::Dumper->Dump([$row], ['row']);
2552             return if !$row;
2553             my $app = $GlobalUi->{app_object};
2554             return if $app->{deletedrow};
2555              
2556             my $subform = $form->getSubform('DBForm') || $form;
2557             my %table_hash = ();
2558             my %field_hash = ();
2559             my @ofs;
2560             my $aliases = $app->{aliases};
2561             my $sl = $GlobalUi->get_field_list;
2562              
2563             my %ft = table_fields($GlobalUi->get_current_table_name);
2564              
2565             $sl->reset;
2566             while ( my $fo = $sl->iterate_list ) {
2567             my ( $tag, $table, $col ) = $fo->get_names;
2568             next if !defined $ft{$tag};
2569             my $tnc = "$table.$col";
2570              
2571             @ofs = ();
2572             # if ( defined $table ) {
2573             push @ofs, $tnc;
2574             if ( ! $table_hash{$table} ) {
2575             $table_hash{$table} = 1;
2576             push @ofs, $table;
2577             }
2578             # }
2579             push @ofs, $col;
2580             trigger_ctrl_blk( 'before', 'display', @ofs );
2581              
2582             my $alias = $aliases->{$tnc};
2583             my $alias2 = $aliases->{"$tnc $tag"} || '';
2584             my $idx;
2585             if ($alias2) {
2586             $idx = $RowList->{aliases}->{$alias2};
2587             }
2588             if ((!$alias2 || !defined $idx) && $alias) {
2589             $idx = $RowList->{aliases}->{$alias};
2590             }
2591             #warn "index = $idx\n" if defined $idx;
2592             my $val;
2593             $val = $row->[$idx] if defined $idx;
2594             if ( !defined $field_hash{$tag} || defined $val ) {
2595             $val = '' if !defined $val;
2596             $field_hash{$tag} = $val;
2597              
2598             my $pos = 0;
2599             my $rc;
2600             #my $warnstr = "display: $tag $table.$col";
2601             #$warnstr .= " $alias" if defined $alias;
2602             #$warnstr .= " $alias2\n" if defined $alias2;
2603             #my $tmp = $fo->{type} || '';
2604             #$warnstr .= "$tmp";
2605             #$tmp = $fo->{db_type} || '';
2606             #$warnstr .= " $tmp";
2607             #$tmp = $fo->{display_only_type} || '';
2608             #$warnstr .= " $tmp\n";
2609             #$warnstr .= "$val:\n" if defined $val;
2610             #warn $warnstr;
2611             ( $val, $rc ) = $fo->format_value_for_display( $val );
2612             $GlobalUi->set_screen_value( $tag, $val );
2613              
2614             @ofs = ();
2615             push @ofs, $tnc if defined $table;
2616             push @ofs, $col;
2617             trigger_ctrl_blk( 'after', 'display', @ofs );
2618             }
2619             }
2620              
2621             $sl->reset;
2622             while ( my $fo = $sl->iterate_list ) {
2623             my $tag = $fo->{field_tag};
2624             my $val = $field_hash{$tag};
2625             # $fo->set_value($val) if defined $val;
2626             $fo->{value} = $val if defined $val;
2627             }
2628              
2629             $app->{fresh} = 1;
2630             @ofs = keys %table_hash;
2631             trigger_ctrl_blk( 'after', 'display', @ofs );
2632              
2633             warn "TRACE: leaving display_row\n" if $::TRACE;
2634             }
2635              
2636             # Post-Add/Update refetch functions:
2637             sub Pg_refetch {
2638             my $sth = shift;
2639             my $table = shift;
2640              
2641             my $oid = $sth->{'pg_oid_status'};
2642             my $row = $DB->selectrow_hashref("select * from $table where oid='$oid'");
2643             return $row;
2644             }
2645              
2646             sub Informix_refetch {
2647             my $sth = shift; # statement handle
2648             my $table = shift; # table to query
2649             # my $cols = shift; # columns to query
2650             # my $vals = shift; # values to query
2651             # my $fld = shift; # serial field name
2652             # my $serial = shift; # serial field value
2653              
2654             warn "entering Informix_refetch\ntable = $table\n" if $::TRACE;
2655              
2656             my $aliases = $GlobalUi->{app_object}->{aliases};
2657             create_query if !$RowList->{aliases};
2658             my $rowid = $sth->{ix_sqlerrd}[5];
2659             if (! $rowid) {
2660             my $alias = $aliases->{"$table.rowid"};
2661             my $cur_row = $RowList->current_row;
2662             my $idx = $RowList->{aliases}->{$alias};
2663             $rowid = $cur_row->[$idx];
2664             }
2665             my %selects;
2666             foreach my $tnct ( keys %$aliases ) {
2667             my ($tnc, $t) = $tnct =~ /^((\w+)\.\w+)/;
2668             my $alias = $aliases->{$tnct};
2669             $selects{"$tnc $alias"} = 1 if ( $t eq $table );
2670             }
2671             my $select = join (",\n", keys %selects);
2672              
2673             my ( $lsth, $query, $row );
2674             $query = "SELECT\n$select\nFROM $table WHERE rowid = $rowid";
2675             warn "refetch query =\n$query\n" if $::TRACE;
2676             $lsth = $DB->prepare($query);
2677             if ($lsth) {
2678             my $row_hash = $DB->selectrow_hashref( $query, {} );
2679             foreach my $alias (keys %$row_hash) {
2680             my $idx = $RowList->{aliases}->{$alias};
2681             $row->[$idx] = $row_hash->{$alias} if defined $idx;
2682             }
2683             }
2684              
2685             return $row if defined($row);
2686             return undef;
2687             }
2688              
2689             # not tested
2690             sub Oracle_refetch {
2691             my $sth = shift; # statement handle; ignored.
2692             my $table = shift; # table to query
2693             my $cols = shift; # columns to query
2694             my $vals = shift; # values to query
2695              
2696             my $wheres = join ' AND ', map { "$_ = ?" } @$cols;
2697             my $query = "SELECT * FROM $table WHERE $wheres";
2698              
2699             # prepare is skipped in selectrow_hashref for Oracle?
2700             $sth = $DB->prepare($query);
2701             my $row = $DB->selectrow_hashref( $query, {}, @$vals );
2702              
2703             return $row;
2704             }
2705              
2706             # When we don't know how to get the row-ID or similar marker, just query
2707             # on all the values we know...
2708             sub Default_refetch {
2709             my $sth = shift; # statement handle; ignored.
2710             my $table = shift;
2711             my $cols = shift; # columns to query
2712             my $vals = shift; # values to query
2713              
2714             my $wheres = join ' AND ', map { "$_ = ?" } @$cols;
2715             my $query = "SELECT * FROM $table WHERE $wheres";
2716             my $row = $DB->selectrow_hashref( $query, {}, @$vals );
2717             return $row;
2718             }
2719              
2720             # What a kludge... required by Curses::Application
2721             package main;
2722              
2723             1;
2724             __DATA__