#!/usr/bin/perl # # $Header: /Users/claude/fuzz/lib/Genezzo/Plan/RCS/TypeCheck.pm,v 7.17 2006/08/26 06:58:03 claude Exp claude $ # # copyright (c) 2005,2006 Jeffrey I Cohen, all rights reserved, worldwide # # package Genezzo::Plan::TypeCheck; use Genezzo::Util; use strict; use warnings; use warnings::register; use Carp; our $VERSION; BEGIN { $VERSION = do { my @r = (q$Revision: 7.17 $ =~ /\d+/g); sprintf "%d."."%02d" x $#r, @r }; # must be all one line, for MakeMaker } our $GZERR = sub { my %args = (@_); return unless (exists($args{msg})); if (exists($args{self})) { my $self = $args{self}; if (defined($self) && exists($self->{GZERR})) { my $err_cb = $self->{GZERR}; return &$err_cb(%args); } } carp $args{msg} if warnings::enabled(); }; sub _init { my $self = shift; my %args = @_; return 0 unless (exists($args{plan_ctx}) && defined($args{plan_ctx})); $self->{plan_ctx} = $args{plan_ctx}; my %valid_aggs = qw( MIN 1 MAX 1 AVG 1 SUM 1 MEAN 1 STDDEV 1 COUNT 1 ECOUNT 1 ); $self->{aggregate_functions} = \%valid_aggs; return 1; } sub new { # whoami; my $invocant = shift; my $class = ref($invocant) || $invocant ; my $self = { }; my %args = (@_); if ((exists($args{GZERR})) && (defined($args{GZERR})) && (length($args{GZERR}))) { # NOTE: don't supply our GZERR here - will get # recursive failure... $self->{GZERR} = $args{GZERR}; my $err_cb = $self->{GZERR}; # capture all standard error messages $Genezzo::Util::UTIL_EPRINT = sub { &$err_cb(self => $self, severity => 'error', msg => @_); }; $Genezzo::Util::WHISPER_PRINT = sub { &$err_cb(self => $self, # severity => 'error', msg => @_); }; } return undef unless (_init($self, %args)); return bless $self, $class; } # end new sub TypeCheck { my $self = shift; my %required = ( algebra => "no algebra !", statement => "no sql statement !", dict => "no dictionary !" ); my %args = ( # %optional, @_); return undef unless (Validate(\%args, \%required)); my $algebra = $args{algebra}; my $err_status; # build a special "statement handle" to hold error and context info my $tc_sth = {}; $tc_sth->{statement} = $args{statement}; $algebra = $self->TableCheck(algebra => $algebra, dict => $args{dict}, tc_sth => $tc_sth ); return ($algebra, 1) unless (defined($algebra)); # if error greet $tc_sth->{tc1}->{tc_err}; unless (scalar(@{$tc_sth->{tc1}->{tc_err}->{nosuch_table}})) { $algebra = $self->ColumnCheck(algebra => $algebra, dict => $args{dict}, statement => $args{statement}, tc_sth => $tc_sth ); } unless (exists($tc_sth->{tc1}) && exists($tc_sth->{tc2}) && exists($tc_sth->{tc3})) { greet "incomplete tc"; $err_status = 1; } if (!defined($err_status)) { for my $kk (keys(%{$tc_sth->{tc1}->{tc_err}})) { if (scalar(@{$tc_sth->{tc1}->{tc_err}->{$kk}})) { $err_status = 1; last; } } if (!defined($err_status)) { for my $kk (keys(%{$tc_sth->{tc3}->{tc_err}})) { next # only case of hash vs array if ($kk eq "duplicate_alias"); if (scalar(@{$tc_sth->{tc3}->{tc_err}->{$kk}})) { $err_status = 1; last; } } } greet "tc errors" if (defined($err_status)); } # NOTE: attach the "statement handle" to the algebra -- it contains # useful information for code generation $algebra->{tc_sth} = $tc_sth; return ($algebra, $err_status); } sub TableCheck { my $self = shift; my %required = ( algebra => "no algebra !", dict => "no dictionary !", tc_sth => "no statement handle !" ); my %args = ( # %optional, @_); return undef unless (Validate(\%args, \%required)); my $algebra = $args{algebra}; # XXX XXX: maybe break the type check phases into separate packages # first, fetch table info from dictionary my $tc1 = {}; # type check tree context for tree walker my $tc_sth = $args{tc_sth}; $tc_sth->{tc1} = $tc1; # local tree walk state $tc1->{tpos} = 0; # mark each table # save bad tables for error reporting... $tc1->{tc_err}->{nosuch_table} = []; $tc1->{tc_err}->{duplicate_table} = []; $algebra = $self->_get_table_info($algebra, $args{dict}, $tc_sth); # next, cross reference table info with query blocks my $tc2 = {}; # type check tree context for tree walker $tc_sth->{tc2} = $tc2; # local tree walk state $tc2->{qb_list} = []; # build an arr starting with current query block num $tc2->{qb_dependency} = []; # save qb parent dependency # save table definition/query block info for later type check phases... $tc2->{tablist} = []; # arr by qb num of table information $algebra = $self->_check_table_info($algebra, $args{dict}, $tc_sth); if (0) { local $Data::Dumper::Indent = 1; local $Data::Dumper::Sortkeys = 1; print Data::Dumper->Dump([$tc2],['tc2']); } return $algebra; } # convert an array of quoted strings/barewords into an array # of normalized strings sub _process_name_pieces { my @pieces = @_; my @full_name; # turn array of name "pieces" back into full names for my $name_piece (@pieces) { # may need to distinguish between bareword and # quoted strings if (exists($name_piece->{quoted_string})) { my $p1 = $name_piece->{quoted_string}; # strip leading/trailing quotes my @p2 = $p1 =~ m/^\"(.*)\"$/; push @full_name, @p2; } else { # XXX XXX: may need to uc or lc here... if (exists($name_piece->{bareword})) { my $p1 = $name_piece->{bareword}; push @full_name, lc($p1); } # while ( my ($kk,$p1) = (each(%{$name_piece}))) # { # next if ($kk =~ m/^(p1|p2)$/); # push @full_name, lc($p1); # } } } # NOTE: issue of handling quoted name pieces with # embedded "." (dot) if wish to construct full_name_str # as join('.', @full_name) -- need to avoid ambiguity return @full_name; } sub _process_name_position { my @pieces = @_; my @full_pos; for my $name_piece (@pieces) { my ($p1, $p2); $p1 = undef; $p2 = undef; $p1 = ($name_piece->{p1}) if (exists($name_piece->{p1})); $p2 = ($name_piece->{p2}) if (exists($name_piece->{p2})); # build array of positions of each piece of name... push @full_pos, [$p1, $p2]; } return @full_pos; } # recursive function to decorate table info # # get table information from the dictionary # number each table uniquely # sub _get_table_info # private { # whoami; # NOTE: get the current subroutine name so it is easier # to call recursively my $subname = (caller(0))[3]; my $self = shift; # generic tree of hashes/arrays my ($genTree, $dict, $tc_sth) = @_; my $treeCtx = $tc_sth->{tc1}; # recursively convert all elements of array if (ref($genTree) eq 'ARRAY') { my $maxi = scalar(@{$genTree}); $maxi--; for my $i (0..$maxi) { $genTree->[$i] = $self->$subname($genTree->[$i], $dict, $tc_sth); } } if (ref($genTree) eq 'HASH') { keys( %{$genTree} ); # XXX XXX: need to reset expression!! # recursively convert all elements of hash, but treat # table name specially if (exists($genTree->{table_name})) { # uniquely number each table reference # Note: use for join order to select STAR expansion $genTree->{tc_table_position} = $treeCtx->{tpos}; $treeCtx->{tpos}++; my @full_name = _process_name_pieces(@{$genTree->{table_name}}); # build a "dot" separated string my $full_name_str = join('.', @full_name); $genTree->{tc_table_fullname} = $full_name_str; # look it up in the dictionary if (! ($dict->DictTableExists ( tname => $full_name_str, silent_exists => 1, silent_notexists => 0 ) ) ) { push @{$treeCtx->{tc_err}->{nosuch_table}}, ["table", $full_name_str]; # return undef; # XXX XXX XXX XXX } else { # XXX XXX: temporary? # get hash by column name $genTree->{tc_table_colhsh} = $dict->DictTableGetCols (tname => $full_name_str); my @colarr; (keys(%{$genTree->{tc_table_colhsh}})); # build array by column position while ( my ($chkk, $chvv) = each ( %{$genTree->{tc_table_colhsh}})) { my %nh = (colname => $chkk, coltype => $chvv->[1]); $colarr[$chvv->[0]] = \%nh; } shift @colarr; $genTree->{tc_table_colarr} = \@colarr; } } # end if tablename if (exists($genTree->{new_table_name})) { my @full_name = _process_name_pieces(@{$genTree->{new_table_name}}); # build a "dot" separated string my $full_name_str = join('.', @full_name); $genTree->{tc_newtable_fullname} = $full_name_str; # look it up in the dictionary if ($dict->DictTableExists ( tname => $full_name_str, silent_exists => 0, silent_notexists => 1 ) ) { push @{$treeCtx->{tc_err}->{duplicate_table}}, ["table", $full_name_str]; # return undef; # XXX XXX XXX XXX } } # end if new table name if (exists($genTree->{new_index_name})) { my @full_name = _process_name_pieces(@{$genTree->{new_index_name}}); # build a "dot" separated string my $full_name_str = join('.', @full_name); $genTree->{tc_newindex_fullname} = $full_name_str; # look it up in the dictionary if ($dict->DictTableExists ( tname => $full_name_str, silent_exists => 0, silent_notexists => 1 ) ) { # XXX XXX: should be "duplicate index"... push @{$treeCtx->{tc_err}->{duplicate_table}}, ["index", $full_name_str]; # return undef; # XXX XXX XXX XXX } } # end if new index name if (exists($genTree->{tablespace_name})) { my @full_name = _process_name_pieces(@{$genTree->{tablespace_name}}); # build a "dot" separated string my $full_name_str = join('.', @full_name); $genTree->{tc_tablespace_fullname} = $full_name_str; # look it up in the dictionary if (! ($dict->DictObjectExists ( object_type => "tablespace", object_name => $full_name_str, silent_exists => 1, silent_notexists => 0 ) ) ) { push @{$treeCtx->{tc_err}->{nosuch_table}}, ["tablespace", $full_name_str]; # return undef; # XXX XXX XXX XXX } } if (exists($genTree->{new_tablespace_name})) { my @full_name = _process_name_pieces(@{$genTree->{new_tablespace_name}}); # build a "dot" separated string my $full_name_str = join('.', @full_name); $genTree->{tc_newtablespace_fullname} = $full_name_str; # look it up in the dictionary if ($dict->DictObjectExists ( object_type => "tablespace", object_name => $full_name_str, silent_exists => 0, silent_notexists => 1 ) ) { push @{$treeCtx->{tc_err}->{duplicate_table}}, ["tablespace", $full_name_str]; greet $treeCtx->{tc_err}->{duplicate_table}; # return undef; # XXX XXX XXX XXX } else { greet "no dup found"; } } # end if new tablespace name if (exists($genTree->{table_alias})) { if (scalar(@{$genTree->{table_alias}})) { # don't build an alias unless we really have one my @full_name = _process_name_pieces(@{$genTree->{table_alias}}); # build a "dot" separated string my $full_name_str = join('.', @full_name); $genTree->{tc_table_fullalias} = $full_name_str; } # XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX # detect FROM clause subquery -- need to build # tc_table_colhsh, tc_table_colarr later # XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX unless (exists($genTree->{table_name})) { # uniquely number each table reference # Note: use for join order to select STAR expansion $genTree->{tc_table_position} = $treeCtx->{tpos}; if (exists($genTree->{tc_table_fullalias})) { $genTree->{tc_FROM_SUBQ} = { alias => "USER_ALIAS" }; } else { # build a unique alias # XXX XXX: need a better unique function $genTree->{tc_table_fullalias} = "_SYS_ALIAS_" . $treeCtx->{tpos}; $genTree->{tc_FROM_SUBQ} = { alias => "SYSTEM_ALIAS" }; } $genTree->{tc_FROM_SUBQ}->{subq_schema} = "UNKNOWN" ; $treeCtx->{tpos}++; # setup the "table fullname" for check table... $genTree->{tc_table_fullname} = $genTree->{tc_table_fullalias}; } # end if FROM subq } # end if table alias while ( my ($kk, $vv) = each ( %{$genTree})) # big while { if ($kk !~ m/^(table_name|table_alias)$/) { $genTree->{$kk} = $self->$subname($vv, $dict, $tc_sth); } } # end big while } return $genTree; } # check the validity of results of _get_table_info # # determine proper table/alias name # find duplicates # associate table info with appropriate query block # build list of query block dependency information for correlated subqueries # sub _check_table_info # private { # whoami; # NOTE: get the current subroutine name so it is easier # to call recursively my $subname = (caller(0))[3]; my $self = shift; # generic tree of hashes/arrays my ($genTree, $dict, $tc_sth) = @_; my $treeCtx = $tc_sth->{tc2}; # recursively convert all elements of array if (ref($genTree) eq 'ARRAY') { my $maxi = scalar(@{$genTree}); $maxi--; for my $i (0..$maxi) { $genTree->[$i] = $self->$subname($genTree->[$i], $dict, $tc_sth); } } if (ref($genTree) eq 'HASH') { keys( %{$genTree} ); # XXX XXX: need to reset expression!! # recursively convert all elements of hash my $qb_setup = 0; # TRUE if top hash of query block if (exists($genTree->{query_block})) { $qb_setup = 1; # keep track of current query block number my $current_qb = $genTree->{query_block}; # push on the front unshift @{$treeCtx->{qb_list}}, $current_qb; unless (defined($treeCtx->{tablist}->[$current_qb])) { # build a hash to hold the table info associated with # the current query block $treeCtx->{tablist}->[$current_qb] = { tables => {}, # reserve space for select list column aliases select_list_aliases => {}, select_col_num => 0 }; } if (exists($genTree->{query_block_parent})) { # save the query block dependency information my @foo = @{$genTree->{query_block_parent}}; $treeCtx->{qb_dependency}->[$current_qb] = \@foo; } } # NOTE: build an alias if we don't have one. Do it outside the # loop in order to avoid updating the hash as we traverse it. if (exists($genTree->{tc_table_fullname})) { unless (exists($genTree->{tc_table_fullalias})) { my $tab_alias = $genTree->{tc_table_fullname}; $genTree->{tc_table_fullalias} = $tab_alias; } } while ( my ($kk, $vv) = each ( %{$genTree})) # big while { if ($kk !~ m/^tc_table_fullname$/) { $genTree->{$kk} = $self->$subname($vv, $dict, $tc_sth); } else # table name { my $tab_alias; if (exists($genTree->{tc_table_fullalias})) { $tab_alias = $genTree->{tc_table_fullalias}; } else { # NOTE: should never get here - should always # define an alias outside this loop... $tab_alias = $vv; } # store table info in the table list for the current # query block my $current_qb = $treeCtx->{qb_list}->[0]; my $tablist = $treeCtx->{tablist}->[$current_qb]->{tables}; # use the alias, rather than the tablename -- this is # ok since the alias points to the base table info. if (exists($tablist->{$tab_alias})) { my $msg = "Found duplicate table name: " . "\'$tab_alias\'\n"; my %earg = (self => $self, msg => $msg, statement => $tc_sth->{statement}, severity => 'warn'); &$GZERR(%earg) if (defined($GZERR)); # return undef # XXX XXX XXX } else { # save a reference to current hash $tablist->{$tab_alias} = $genTree; } } # end table name } # end big while if ($qb_setup) { # pop from the front shift @{$treeCtx->{qb_list}}; } } return $genTree; } sub ColumnCheck { my $self = shift; my %required = ( algebra => "no algebra !", statement => "no sql statement !", dict => "no dictionary !", tc_sth => "no statement handle !" ); my %args = ( # %optional, @_); return undef unless (Validate(\%args, \%required)); my $algebra = $args{algebra}; my $tc3 = {}; # type check tree context for tree walker my $tc_sth = $args{tc_sth}; $tc_sth->{tc3} = $tc3; # local tree walk state $tc3->{qb_list} = []; # build an arr starting with current query block num $tc3->{statement} = $args{statement}; # save bad columns for error reporting $tc3->{tc_err}->{duplicate_alias} = {}; $tc3->{tc_err}->{nosuch_column} = []; # use the table information from table typecheck phase $tc3->{tablist} = $tc_sth->{tc2}->{tablist}; # convert "select * " to "select " $algebra = $self->_get_star_cols($algebra, $args{dict}, $tc_sth); # setup select list column aliases and column headers $algebra = $self->_get_col_alias($algebra, $args{dict}, $tc_sth); # map columns to FROM clause tables $algebra = $self->_get_col_info($algebra, $args{dict}, $tc_sth); # use type information to map sql comparison operations to their # perl equivalents $algebra = $self->_fixup_comp_op($algebra, $args{dict}, $tc_sth); $tc3->{tc_err}->{invalid_args} = []; # mark aggregates and check for invalid args $algebra = $self->_find_aggregate_functions($algebra, $args{dict}, $tc_sth); $tc3->{tc_agg_check} = []; # check for aggregates in WHERE clause $algebra = $self->_check_aggregate_functions($algebra, $args{dict}, $tc_sth); # check for GROUPing/aggregates # check for final select list columns vs all projected columns in # all clauses # check args for all functions $tc3->{AndPurity} = 1; # false if find OR's # XXX XXX: moved this to XEVal::Prepare # $algebra = $self->_sql_where($algebra, $args{dict}, $tc_sth); if (0) # XXX XXX XXX XXX { my $tc2 = $tc_sth->{tc2}; local $Data::Dumper::Indent = 1; local $Data::Dumper::Sortkeys = 1; print Data::Dumper->Dump([$tc2],['tc2']); } # NOTE: need to build the select list column aliases *first*, # then type check all columns. # # Different standards (SQL92, SQL99) and different products have # different scoping and precedence rules on the select list column # aliases. In general, the WHERE clause is processed before the # select list defines the column aliases, so it can only use table # and table alias information. (Which makes sense -- you can have # a column alias on an aggregate operator like COUNT(*), which # can't be completely evaluated until the WHERE clause processes # the final row.) # # ORDER BY is the last operation, so it can evaluate expressions # using the column aliases. GROUP BY and HAVING behavior seems to # be a bit of a tossup. We'll try to maintain some flexibility -- # the tablist has separate entries column alias info and table # definitions in each query block. In case of ambiguity of column # alias which matches an existing column name, use rule where # column names take precedence over column aliases in GROUP # BY/HAVING, *but* reverse precendence in ORDER BY. # # What is scope of column aliasing in select list itself? left to # right (ie, col2 can utilize the col1 alias) or "simultaneous"? # # # Note that select list column aliases are allowed to mask table # columns, but all other table column references should not be # ambiguous. # XXX XXX XXX: _get_col_alias to only build up alias info in # tablist, then _get_col_info to resolve column names against # aliases, then tables if necessary return $algebra; } sub _FROM_subq_star_fixup { # whoami; # NOTE: get the current subroutine name so it is easier # to call recursively my $subname = (caller(0))[3]; my $self = shift; # generic tree of hashes/arrays my ($genTree, $dict, $tc_sth) = @_; my $treeCtx = $tc_sth->{tc3}; return unless (exists($genTree->{tc_FROM_SUBQ}) && exists($genTree->{tc_FROM_SUBQ}->{subq_schema}) && ($genTree->{tc_FROM_SUBQ}->{subq_schema} eq 'UNKNOWN')); return if (exists($genTree->{tc_table_colarr}) && scalar($genTree->{tc_table_colarr})); if (exists($genTree->{sql_query}) && exists($genTree->{sql_query}->{operands}) && scalar($genTree->{sql_query}->{operands})) { # select list of 1st sql query takes precedence for set operations... my $first_op = $genTree->{sql_query}->{operands}->[0]; while (!exists($first_op->{sql_select}) && exists($first_op->{operands}) && scalar(@{$first_op->{operands}})) { # XXX XXX: this needs to be recursive for nested set operations!! $first_op = $first_op->{operands}->[0]; } if (exists($first_op->{sql_select}) && exists($first_op->{sql_select}->{select_list}) && scalar(@{$first_op->{sql_select}->{select_list}})) { my $sel_list1 = $first_op->{sql_select}->{select_list}; $genTree->{tc_table_colarr} = []; $genTree->{tc_table_colhsh} = {}; my $sel_index = 0; for my $sel_item (@{$sel_list1}) { $sel_index++; # greet $sel_item; # XXX XXX: is there some way to streamline handling of # literals here? my ($hnam, $htyp); if (scalar(@{$sel_item->{col_alias}})) { # XXX XXX: eliminate this duplicate code my @full_name = _process_name_pieces( @{$sel_item->{col_alias}}); $hnam = join('.',@full_name); # greet 1, $hnam; } else { my $col_hd; if (exists($sel_item->{p1})) { $col_hd = substr($treeCtx->{statement}, $sel_item->{p1}, ($sel_item->{p2} - $sel_item->{p1}) + 1 ); $col_hd =~ s/^\s*//; # trim leading spaces # greet 2, $col_hd; } else { # XXX XXX: generated col for STAR - fake it # XXX XXX: assume have a column name my $npa = $sel_item->{value_expression}->{column_name}; my @col_name = _process_name_pieces(@{$npa}); $col_hd = join(".", @col_name); # greet 3, $col_hd; } $hnam = $col_hd; } # end no alias $htyp = $sel_item->{value_expression}->{tc_expr_type}; my $h1 = { colname => $hnam, coltype => $htyp }; push @{$genTree->{tc_table_colarr}}, $h1; # XXX XXX: need duplicate col name check # or type mismatch here!! $genTree->{tc_table_colhsh}->{$hnam} = [$sel_index, $htyp]; } # end for } } $genTree->{tc_FROM_SUBQ}->{subq_schema} = 'OK'; } # expand STAR select lists... # # sub _get_star_cols { # whoami; # NOTE: get the current subroutine name so it is easier # to call recursively my $subname = (caller(0))[3]; my $self = shift; # generic tree of hashes/arrays my ($genTree, $dict, $tc_sth) = @_; my $treeCtx = $tc_sth->{tc3}; # recursively convert all elements of array if (ref($genTree) eq 'ARRAY') { my $maxi = scalar(@{$genTree}); $maxi--; for my $i (0..$maxi) { $genTree->[$i] = $self->$subname($genTree->[$i], $dict, $tc_sth); } } if (ref($genTree) eq 'HASH') { keys( %{$genTree} ); # XXX XXX: need to reset expression!! # convert subtree first, then process local select list { my $qb_setup = 0; # TRUE if top hash of query block if (exists($genTree->{query_block})) { $qb_setup = 1; # keep track of current query block number my $current_qb = $genTree->{query_block}; # push on the front unshift @{$treeCtx->{qb_list}}, $current_qb; } while ( my ($kk, $vv) = each ( %{$genTree})) # big while { # convert subtree first... $genTree->{$kk} = $self->$subname($vv, $dict, $tc_sth); } if ($qb_setup) { # pop from the front shift @{$treeCtx->{qb_list}}; } } # recursively convert all elements of hash my $qb_setup = 0; # TRUE if top hash of query block if (exists($genTree->{query_block})) { $qb_setup = 1; # keep track of current query block number my $current_qb = $genTree->{query_block}; # push on the front unshift @{$treeCtx->{qb_list}}, $current_qb; } # fixup star select lists for FROM subqueries $self->_FROM_subq_star_fixup($genTree, $dict, $tc_sth); if (exists($genTree->{select_list})) { # if the select list is STAR (not an array) unless (ref($genTree->{select_list}) eq 'ARRAY') { # start in current query block # find our tablist my $current_qb = $treeCtx->{qb_list}->[0]; my $curr_tablist = $treeCtx->{tablist}->[$current_qb]; my $table_cnt = keys( %{$curr_tablist->{tables}} ); # reset my @tab_cols; while ( my ($hkk, $hvv) = each (%{$curr_tablist->{tables}})) { my $tpos = $hvv->{tc_table_position}; my $col_list = []; # get all the column names for my $colh (@{$hvv->{tc_table_colarr}}) { push @{$col_list}, $colh->{colname}; } # convert to array of value expressions for my $colcnt (0..(scalar(@{$col_list})-1)) { my $old_colname = $col_list->[$colcnt]; # quote the strings to preserve case my $cv = {quoted_string => '"' . $col_list->[$colcnt] . '"'}; # table name doesn't change, but building a # new one each time gives a nicer Data::Dumper # output... my $table_name = {quoted_string => '"' . $hkk .'"' }; my $foo = []; if ($table_cnt > 1) { # don't use table name if only one table push @{$foo}, $table_name; } push @{$foo}, $cv; # build the value expression my $nx = { col_alias => [], value_expression => { column_name => $foo } }; $col_list->[$colcnt] = $nx; # FROM SUBQUERY type fixup... if (exists($hvv->{tc_table_colhsh}) && exists($hvv->{tc_table_colhsh}->{$old_colname})) { $nx->{value_expression}->{tc_expr_type} = $hvv->{tc_table_colhsh}->{$old_colname}->[1]; } } # store tables in tpos order $tab_cols[$tpos] = $col_list; } # end each tablist table my $sel_list = []; for my $tabi (@tab_cols) { if (defined($tabi) && scalar(@{$tabi})) { push @{$sel_list}, @{$tabi}; } } $genTree->{select_list} = $sel_list; } } if ($qb_setup) { # pop from the front shift @{$treeCtx->{qb_list}}; } } return $genTree; } # get column aliases and column "headers" # # sub _get_col_alias # private { # whoami; # NOTE: get the current subroutine name so it is easier # to call recursively my $subname = (caller(0))[3]; my $self = shift; # generic tree of hashes/arrays my ($genTree, $dict, $tc_sth) = @_; my $treeCtx = $tc_sth->{tc3}; # recursively convert all elements of array if (ref($genTree) eq 'ARRAY') { my $maxi = scalar(@{$genTree}); $maxi--; for my $i (0..$maxi) { $genTree->[$i] = $self->$subname($genTree->[$i], $dict, $tc_sth); } } if (ref($genTree) eq 'HASH') { keys( %{$genTree} ); # XXX XXX: need to reset expression!! # recursively convert all elements of hash my $qb_setup = 0; # TRUE if top hash of query block if (exists($genTree->{query_block})) { $qb_setup = 1; # keep track of current query block number my $current_qb = $genTree->{query_block}; # push on the front unshift @{$treeCtx->{qb_list}}, $current_qb; } while ( my ($kk, $vv) = each ( %{$genTree})) # big while { if ($kk =~ m/^(column_list)$/) { $genTree->{tc_column_list} = []; for my $all_cols (@{$genTree->{$kk}}) { my @full_name = _process_name_pieces(@{$all_cols}); # build a "dot" separated string my $full_name_str = join('.', @full_name); push @{$genTree->{tc_column_list}}, $full_name_str; } } elsif ($kk !~ m/^(new_column_name|column_name|col_alias)$/) { $genTree->{$kk} = $self->$subname($vv, $dict, $tc_sth); } else # column name or alias { my $isColumnName = ($kk =~ m/column_name$/); my @full_name = _process_name_pieces(@{$vv}); my @full_pos = _process_name_position(@{$vv}); my $stat_pos = []; if (scalar(@full_pos)) { $stat_pos->[0] = $full_pos[0]->[0]; $stat_pos->[1] = $full_pos[-1]->[1]; } # last portion should be column name (if not an alias) my $column_name; $column_name = pop @full_name if ($isColumnName); # build a "dot" separated string my $full_name_str = join('.', @full_name); if ($isColumnName) { # just build the names here -- lookup in dictionary later $genTree->{tc_col_tablename} = $full_name_str if (scalar(@full_name)); if ($kk =~ m/^new_column_name$/) { $genTree->{tc_newcolumn_name} = $column_name; } else { $genTree->{tc_column_name} = $column_name; $genTree->{tc_column_name_stat_pos} = $stat_pos; } } else # column alias { # don't build an alias unless we really have one if (scalar(@full_name)) { # alias for later reference $genTree->{tc_col_fullalias} = $full_name_str; # column "header" for formatting output is the # same as the alias $genTree->{tc_col_header} = $full_name_str; # start in current query block # find our tablist # add our new select list column alias my $current_qb = $treeCtx->{qb_list}->[0]; my $curr_tablist = $treeCtx->{tablist}->[$current_qb]; my $qb_aliases = $curr_tablist->{select_list_aliases}; my $select_col_num = $curr_tablist->{select_col_num}; $curr_tablist->{select_col_num} += 1; if (exists($qb_aliases->{$full_name_str})) { # error: duplicate alias my $dupa = $treeCtx->{tc_err}->{duplicate_alias}; if (exists($dupa->{$full_name_str})) { # count duplicates! $dupa->{$full_name_str} += 1; } else { $dupa->{$full_name_str} = 1; } # XXX XXX: is this illegal? my $msg = "duplicate alias: " . "\'$full_name_str\'"; my %earg = (self => $self, msg => $msg, statement => $tc_sth->{statement}, severity => 'warn'); &$GZERR(%earg) if (defined($GZERR)); # XXX XXX XXX return undef } else # update the alias with position info { # XXX XXX XXX: what else goes here? my $foo = {}; $foo->{p1} = $genTree->{p1}; $foo->{p2} = $genTree->{p2}; $foo->{select_col_num} = $select_col_num; $qb_aliases->{$full_name_str} = $foo; } } else # no alias { # derive column "header" from input txt -- the # default header is just the text of the # expression. my $col_hd; if (exists($genTree->{p1})) { $col_hd = substr($treeCtx->{statement}, $genTree->{p1}, ($genTree->{p2} - $genTree->{p1}) + 1 ); $col_hd =~ s/^\s*//; # trim leading spaces } else { # XXX XXX: generated col for STAR - fake it # XXX XXX: assume have a column name my $npa = $genTree->{value_expression}->{column_name}; my @col_name = _process_name_pieces(@{$npa}); $col_hd = join(".", @col_name); } $genTree->{tc_col_header} = $col_hd; } } # end col alias } } # end big while if ($qb_setup) { # pop from the front shift @{$treeCtx->{qb_list}}; } } return $genTree; } # recursive function to decorate column info # # sub _get_col_info # private { # whoami; # NOTE: get the current subroutine name so it is easier # to call recursively my $subname = (caller(0))[3]; my $self = shift; # generic tree of hashes/arrays my ($genTree, $dict, $tc_sth) = @_; my $treeCtx = $tc_sth->{tc3}; # recursively convert all elements of array if (ref($genTree) eq 'ARRAY') { my $maxi = scalar(@{$genTree}); $maxi--; for my $i (0..$maxi) { $genTree->[$i] = $self->$subname($genTree->[$i], $dict, $tc_sth); } } if (ref($genTree) eq 'HASH') { keys( %{$genTree} ); # XXX XXX: need to reset expression!! # recursively convert all elements of hash my $qb_setup = 0; # TRUE if top hash of query block if (exists($genTree->{query_block})) { $qb_setup = 1; # keep track of current query block number my $current_qb = $genTree->{query_block}; # push on the front unshift @{$treeCtx->{qb_list}}, $current_qb; } L_bigw: while ( my ($kk, $vv) = each ( %{$genTree})) # big while { if ($kk !~ m/^(tc_column_name)$/) { $genTree->{$kk} = $self->$subname($vv, $dict, $tc_sth); } else # column name { my $full_name_str = undef; if (exists($genTree->{tc_col_tablename})) { $full_name_str = $genTree->{tc_col_tablename}; } my $column_name = $genTree->{tc_column_name}; my $stat_pos = []; $stat_pos = ($genTree->{tc_column_name_stat_pos}) if (exists($genTree->{tc_column_name_stat_pos})); # XXX XXX XXX: need to deal with table.rid... if ($column_name =~ m/^(rid|rownum)$/i) { if ($column_name =~ m/^(rid)$/i) { $genTree->{tc_expr_type} = 'c'; } else { $genTree->{tc_expr_type} = 'n'; } # XXX XXX: need to deal with other pseudo cols like # sysdate... # rid and rownum are valid next L_bigw; } my $foundCol = 0; # start in current query block my $current_qb = $treeCtx->{qb_list}->[0]; # NOTE: search backward from most recent # (innermost) query block to earliest (outermost) L_qb: for (my $qb_num = $current_qb; (defined($qb_num) && ($qb_num > 0)); $qb_num--) { my $qb2 = $treeCtx->{tablist}->[$qb_num]->{tables}; # if have a tablename, look there if (defined($full_name_str)) { next L_qb unless (exists($qb2->{$full_name_str})); my $h1 = $qb2->{$full_name_str}->{tc_table_colhsh}; next L_qb unless (exists($h1->{$column_name})); $genTree->{tc_column_num} = $h1->{$column_name}->[0]; $genTree->{tc_expr_type} = $h1->{$column_name}->[1]; $genTree->{tc_column_qb} = $qb_num; $foundCol = 1; last L_qb; # done! } else { # need to check all tables in block keys( %{$qb2} ); # XXX XXX: need to reset L_littlew: while ( my ($hkk, $hvv) = each ( %{$qb2})) # little while { my $h1 = $hvv->{tc_table_colhsh}; next L_littlew unless (exists($h1->{$column_name})); # check all tables in current query block # for duplicate column names if ($foundCol) { my $msg = "column name " . "\'$column_name\' is ambiguous -- "; $msg .= "tables \'" . $genTree->{tc_col_tablename} . "\', \'" . $hkk . "\'"; my %earg = (self => $self, msg => $msg, statement => $tc_sth->{statement}, stat_pos => $stat_pos, severity => 'warn'); &$GZERR(%earg) if (defined($GZERR)); last L_qb; } # set the table name $genTree->{tc_col_tablename} = $hkk; $genTree->{tc_column_num} = $h1->{$column_name}->[0]; $genTree->{tc_expr_type} = $h1->{$column_name}->[1]; $genTree->{tc_column_qb} = $qb_num; $foundCol = 1; # last L_qb; } # end little while last L_qb if ($foundCol); } } # end for each qb num unless ($foundCol) { push @{$treeCtx->{tc_err}->{nosuch_column}}, $full_name_str; my $msg = "column \'$column_name\' not found\n"; my %earg = (self => $self, msg => $msg, statement => $tc_sth->{statement}, stat_pos => $stat_pos, severity => 'warn'); &$GZERR(%earg) if (defined($GZERR)); # return undef; # XXX XXX XXX XXX } } # end is col name } # end big while if ($qb_setup) { # pop from the front shift @{$treeCtx->{qb_list}}; } } return $genTree; } # transform standard sql relational operators to Perl-style, # distinguishing numeric and character comparisons my $relop_map = { '==' => { "n" => "==", "c" => "eq"}, '=' => { "n" => "==", "c" => "eq"}, '<>' => { "n" => "!=", "c" => "ne"}, '!=' => { "n" => "!=", "c" => "ne"}, '>' => { "n" => ">", "c" => "gt"}, '<' => { "n" => "<", "c" => "lt"}, '>=' => { "n" => ">=", "c" => "ge"}, '<=' => { "n" => "<=", "c" => "le"}, '<=>' => { "n" => "<=>", "c" => "cmp"} }; # comp_op fixup # # sub _fixup_comp_op { # whoami; # NOTE: get the current subroutine name so it is easier # to call recursively my $subname = (caller(0))[3]; my $self = shift; # generic tree of hashes/arrays my ($genTree, $dict, $tc_sth) = @_; my $treeCtx = $tc_sth->{tc3}; # recursively convert all elements of array if (ref($genTree) eq 'ARRAY') { my $maxi = scalar(@{$genTree}); $maxi--; for my $i (0..$maxi) { $genTree->[$i] = $self->$subname($genTree->[$i], $dict, $tc_sth); } } if (ref($genTree) eq 'HASH') { keys( %{$genTree} ); # XXX XXX: need to reset expression!! # convert subtree first, then process local select list { my $qb_setup = 0; # TRUE if top hash of query block if (exists($genTree->{query_block})) { $qb_setup = 1; # keep track of current query block number my $current_qb = $genTree->{query_block}; # push on the front unshift @{$treeCtx->{qb_list}}, $current_qb; } while ( my ($kk, $vv) = each ( %{$genTree})) # big while { # convert subtree first... $genTree->{$kk} = $self->$subname($vv, $dict, $tc_sth); } if ($qb_setup) { # pop from the front shift @{$treeCtx->{qb_list}}; } } # recursively convert all elements of hash my $qb_setup = 0; # TRUE if top hash of query block if (exists($genTree->{query_block})) { $qb_setup = 1; # keep track of current query block number my $current_qb = $genTree->{query_block}; # push on the front unshift @{$treeCtx->{qb_list}}, $current_qb; } # grab the WHERE clause text if (exists($genTree->{sc_tree})) { if (exists($genTree->{p1}) && exists($genTree->{p2})) { my $pos1 = $genTree->{p1}; my $pos2 = $genTree->{p2}; my $sc_txt = substr($treeCtx->{statement}, $pos1, ($pos2 - $pos1) + 1 ); $genTree->{sc_txt} = $sc_txt; } } # XXX XXX XXX: Get text for update col = expression... if (exists($genTree->{operator})) { if (($genTree->{operator} eq "=") && (exists($genTree->{p1}) && exists($genTree->{p2}))) { my $pos1 = $genTree->{p1}; my $pos2 = $genTree->{p2}; my $vx_txt = substr($treeCtx->{statement}, $pos1, ($pos2 - $pos1) + 1 ); $genTree->{vx_txt} = $vx_txt; } } if (exists($genTree->{comp_op})) { # print $genTree->{operator}, "\n"; # fixup the perl operators if (($genTree->{comp_op} eq 'comp_perlish') && (3 == scalar(@{$genTree->{operands}}))) { my $op1 = $genTree->{operands}->[1]; $genTree->{operands}->[1] = { tc_comp_op => $op1, orig_comp_op => $op1 }; my $op2 = $genTree->{operands}->[2]; # XXX XXX: op2 should be an array of # perl regex pieces -- reassemble it. # may need to do some work for non-standard # quoting my $perl_lit = join("", @{$op2}); $genTree->{operands}->[2] = { string_literal => $perl_lit, orig_reg_exp => $op2 }; } L_for_ops: for my $op_idx (0..(@{$genTree->{operands}}-1)) { my $op1 = $genTree->{operands}->[$op_idx]; # print $op1, "\n", ref($op1), "\n"; next L_for_ops if (ref($op1)); # ref is false for scalar non-ref # print $op1, "\n"; my $tok_expr = '(<=>|cmp|eq|==|<>|lt|gt|le|ge|!=|<=|>=|<|>|=)'; next L_for_ops unless ($op1 =~ m/^$tok_expr$/); next L_for_ops unless (exists($relop_map->{$op1})); my $h1 = $relop_map->{$op1}; my $left_op = $genTree->{operands}->[$op_idx - 1]; my $right_op = $genTree->{operands}->[$op_idx + 1]; my $op_type = '?'; if ((ref($left_op) eq 'HASH') && (exists($left_op->{tc_expr_type}))) { $op_type = $left_op->{tc_expr_type}; } # else type is char by default # char takes precedence over number, so only test # right side if left side was numeric if (($op_type ne 'c') && (ref($right_op) eq 'HASH') && (exists($right_op->{tc_expr_type}))) { $op_type = $right_op->{tc_expr_type}; } $op_type = 'c' # only allow c or n unless ($op_type =~ m/^(n|c)$/); # update the operator $genTree->{operands}->[$op_idx] = { tc_comp_op => $h1->{$op_type}, orig_comp_op => $op1 }; } # end for } if ($qb_setup) { # pop from the front shift @{$treeCtx->{qb_list}}; } } return $genTree; } sub _find_aggregate_functions { # whoami; # NOTE: get the current subroutine name so it is easier # to call recursively my $subname = (caller(0))[3]; my $self = shift; # generic tree of hashes/arrays my ($genTree, $dict, $tc_sth) = @_; my $treeCtx = $tc_sth->{tc3}; # recursively convert all elements of array if (ref($genTree) eq 'ARRAY') { my $maxi = scalar(@{$genTree}); $maxi--; for my $i (0..$maxi) { $genTree->[$i] = $self->$subname($genTree->[$i], $dict, $tc_sth); } } if (ref($genTree) eq 'HASH') { keys( %{$genTree} ); # XXX XXX: need to reset expression!! # convert subtree first, then process local select list { my $qb_setup = 0; # TRUE if top hash of query block if (exists($genTree->{query_block})) { $qb_setup = 1; # keep track of current query block number my $current_qb = $genTree->{query_block}; # push on the front unshift @{$treeCtx->{qb_list}}, $current_qb; } while ( my ($kk, $vv) = each ( %{$genTree})) # big while { # convert subtree first... $genTree->{$kk} = $self->$subname($vv, $dict, $tc_sth); } if ($qb_setup) { # pop from the front shift @{$treeCtx->{qb_list}}; } } # recursively convert all elements of hash my $qb_setup = 0; # TRUE if top hash of query block if (exists($genTree->{query_block})) { $qb_setup = 1; # keep track of current query block number my $current_qb = $genTree->{query_block}; # push on the front unshift @{$treeCtx->{qb_list}}, $current_qb; } if (exists($genTree->{function_name})) { my $fname = uc($genTree->{function_name}); if (exists($self->{aggregate_functions}->{$fname})) { # perform final aggregation # need to generate stages to perform aggregate # initialization and intermediate aggregation $genTree->{aggregate_stage} = "finalize"; } else { if (exists($genTree->{operands})) { my $ops = $genTree->{operands}; if (scalar(@{$ops}) && (exists($ops->[0]->{all_distinct}))) { if (scalar(@{$ops->[0]->{all_distinct}})) { # invalid all/distinct qualifier # for non-aggregate function my $adq = $ops->[0]->{all_distinct}->[0]; my $msg = "invalid argument ". "\'$adq\' for non-aggregate function \'$fname\'"; my %earg = (self => $self, msg => $msg, statement => $tc_sth->{statement}, severity => 'warn'); push @{$treeCtx->{tc_err}->{invalid_args}}, $msg; &$GZERR(%earg) if (defined($GZERR)); } } } } } if ($qb_setup) { # pop from the front shift @{$treeCtx->{qb_list}}; } } return $genTree; } # end _find_aggregate_functions sub _check_aggregate_functions { # whoami; # NOTE: get the current subroutine name so it is easier # to call recursively my $subname = (caller(0))[3]; my $self = shift; # generic tree of hashes/arrays my ($genTree, $dict, $tc_sth) = @_; my $treeCtx = $tc_sth->{tc3}; # recursively convert all elements of array if (ref($genTree) eq 'ARRAY') { my $maxi = scalar(@{$genTree}); $maxi--; for my $i (0..$maxi) { $genTree->[$i] = $self->$subname($genTree->[$i], $dict, $tc_sth); } } if (ref($genTree) eq 'HASH') { keys( %{$genTree} ); # XXX XXX: need to reset expression!! my $got_one = 0; if (exists($genTree->{alg_op_name})) { $got_one = 1; push @{$treeCtx->{tc_agg_check}}, $genTree; } if (exists($genTree->{aggregate_stage})) { my $op_node = $treeCtx->{tc_agg_check}->[-1]; my $fname = ($genTree->{function_name}); if (exists($op_node->{alg_op_name})) { if ($op_node->{alg_op_name} eq 'project') { # will need to check project to determine if all # projected columns are aggregates or GROUPed $op_node->{tc_has_agg} = 1; } # aggregates are legal in HAVING, ORDER BY, # and illegal in WHERE clause # XXX XXX : also illegal in JOIN conditions... if (($op_node->{alg_op_name} eq 'filter') && ($op_node->{alg_filter_type} eq 'WHERE')) { my $msg = "illegal use of" . " aggregate function \'$fname\' in WHERE clause"; my %earg = (self => $self, msg => $msg, statement => $tc_sth->{statement}, severity => 'warn'); push @{$treeCtx->{tc_err}->{invalid_args}}, $msg; &$GZERR(%earg) if (defined($GZERR)); } } } while ( my ($kk, $vv) = each ( %{$genTree})) # big while { $genTree->{$kk} = $self->$subname($vv, $dict, $tc_sth); } if ($got_one) { pop @{$treeCtx->{tc_agg_check}}; } } return $genTree; } # end _check_aggregate_functions sub GetFromWhereEtc { my $self = shift; my %required = ( algebra => "no algebra !", dict => "no dictionary !", ); my %optional = (top_cmd => "SELECT"); my %args = (%optional, @_); return undef unless (Validate(\%args, \%required)); my $algebra = $args{algebra}; my $tc4 = {}; # type check tree context for tree walker # NOTE: we stashed the statement handle in the top of the # algebra when we did typechecking earlier my $tc_sth = $algebra->{tc_sth}; $tc_sth->{tc4} = $tc4; # NOTE: clear out the "statement handle" since it's not part of # the algebra and we don't want to walk it $algebra->{tc_sth} = undef; # local tree walk state $tc4->{top_qb_num} = 1; # top query block number is 1 if ($args{top_cmd} =~ m/INSERT/i) { # NOTE: "top" query block number 2 for INSERT...SELECT # (use qb 1 to resolve insert table/column info) $tc4->{top_qb_num} = 2; } $tc4->{qb_list} = []; # build an arr starting with current query block num greet $tc4; $tc4->{index_keys} = [] # only build index keys if ($tc_sth->{tc3}->{AndPurity}); # if pure AND search condition $algebra = $self->_get_from_where($algebra, $args{dict}, $tc_sth); my $from = $tc4->{from}; my $sel_list = $tc4->{select_list}; my $where = $tc4->{where}; # XXX XXX XXX: need to localize AndPurity per WHERE clause/search cond my $and_purity = $tc_sth->{tc3}->{AndPurity}; $tc4->{where}->[0]->{sc_and_purity} = $and_purity; if ($and_purity) { $tc4->{where}->[0]->{sc_index_keys} = $tc4->{index_keys}; } # XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX # NOTE: replace the "statement handle" $algebra->{tc_sth} = $tc_sth; return ($algebra, $from, $sel_list, $where); } # transition from old parser to new... # sub _get_from_where { # whoami; # NOTE: get the current subroutine name so it is easier # to call recursively my $subname = (caller(0))[3]; my $self = shift; # generic tree of hashes/arrays my ($genTree, $dict, $tc_sth) = @_; my $treeCtx = $tc_sth->{tc4}; # recursively convert all elements of array if (ref($genTree) eq 'ARRAY') { my $maxi = scalar(@{$genTree}); $maxi--; for my $i (0..$maxi) { $genTree->[$i] = $self->$subname($genTree->[$i], $dict, $tc_sth); } } if (ref($genTree) eq 'HASH') { keys( %{$genTree} ); # XXX XXX: need to reset expression!! # recursively convert all elements of hash my $qb_setup = 0; # TRUE if top hash of query block if (exists($genTree->{query_block})) { $qb_setup = 1; # keep track of current query block number my $current_qb = $genTree->{query_block}; # push on the front unshift @{$treeCtx->{qb_list}}, $current_qb; } if (scalar(@{$treeCtx->{qb_list}})) { my $current_qb = $treeCtx->{qb_list}->[0]; if ($current_qb == $treeCtx->{top_qb_num}) { if (exists($genTree->{from_clause})) { $treeCtx->{from} = $genTree->{from_clause}; } if (exists($genTree->{select_list})) { $treeCtx->{select_list} = $genTree->{select_list}; } # distinguish WHERE and HAVING clauses... if (exists($genTree->{search_cond}) && (exists($genTree->{alg_op_name}) && ($genTree->{alg_op_name} eq 'filter')) && (exists($genTree->{alg_filter_type}) && ($genTree->{alg_filter_type} eq 'WHERE'))) { $treeCtx->{where} = $genTree->{search_cond}; } } } while ( my ($kk, $vv) = each ( %{$genTree})) # big while { if (($kk =~ m/tc_index_key/) && exists($treeCtx->{index_keys})) { # build big list of index keys push @{$treeCtx->{index_keys}}, @{$vv}; } # convert subtree first... $genTree->{$kk} = $self->$subname($vv, $dict, $tc_sth); } if ($qb_setup) { # pop from the front shift @{$treeCtx->{qb_list}}; } } return $genTree; } END { } # module clean-up code here (global destructor) ## YOUR CODE GOES HERE 1; # don't forget to return a true value from the file __END__ # Below is stub documentation for your module. You better edit it! =head1 NAME Genezzo::Plan::TypeCheck - Perform checks on relational algebra representation =head1 SYNOPSIS use Genezzo::Plan::TypeCheck; =head1 DESCRIPTION Perform type-checking/analysis on relational algebra. =head1 ARGUMENTS =head1 FUNCTIONS =over 4 =item TypeCheck Perform typechecking on a relational algebra, and add type information to the tree =item TableCheck Check table references in the relational algebra, and provide type information. =item ColumnCheck Resolve each column reference in the relational algebra back to some base table. =back =head2 EXPORT =over 4 =back =head1 LIMITATIONS =head1 TODO =over 4 =item need to generate stages to perform aggregate initialization and intermediate aggregation =item check for aggregates in WHERE clause =item check for GROUPing/aggregates =item check for final select list columns vs all projected columns in all clauses =item check args for all functions =item check for function existance in GenDBI and main namespaces =item update pod =item need to handle FROM clause subqueries -- some tricky column type issues. check for duplicate aliases/type mismatch in _FROM_subq_star_fixup ? =item check bool_op - AND purity if no OR's. =item check relational operator (comp_op, relop) =item handle ddl/dml (create, insert, delete etc with embedded queries) by checking for query_block info -- look for hash with 'query_block' before attempting table/col resolution. Need special type checking for these functions. =item refactor to common TreeWalker =item handle all pseudo cols =item most value expression stuff needs to migrate to XEval =back =head1 AUTHOR Jeffrey I. Cohen, jcohen@genezzo.com =head1 SEE ALSO L. Copyright (c) 2005,2006 Jeffrey I Cohen. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Address bug reports and comments to: jcohen@genezzo.com For more information, please visit the Genezzo homepage at L =cut