X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FSQL%2FAbstract.pm;h=3016bc134998144786b0fdfd75b02560ff30bccd;hb=463a403c5abe3d1f9b538a64f5e3a7860ba3ea8d;hp=6a8abaf6447d0cf86b8bf7038bb4d82c4f9b917f;hpb=272cc23a2d72b08077eb88fcfb114edb32f875d6;p=scpubgit%2FQ-Branch.git diff --git a/lib/SQL/Abstract.pm b/lib/SQL/Abstract.pm index 6a8abaf..3016bc1 100644 --- a/lib/SQL/Abstract.pm +++ b/lib/SQL/Abstract.pm @@ -137,6 +137,79 @@ sub is_plain_value ($) { # NEW #====================================================================== +our %Defaults = ( + expand => { + not => '_expand_not', + bool => '_expand_bool', + and => '_expand_op_andor', + or => '_expand_op_andor', + nest => '_expand_nest', + bind => '_expand_bind', + in => '_expand_in', + not_in => '_expand_in', + row => '_expand_row', + between => '_expand_between', + not_between => '_expand_between', + op => '_expand_op', + (map +($_ => '_expand_op_is'), ('is', 'is_not')), + ident => '_expand_ident', + value => '_expand_value', + func => '_expand_func', + values => '_expand_values', + }, + expand_op => { + 'between' => '_expand_between', + 'not_between' => '_expand_between', + 'in' => '_expand_in', + 'not_in' => '_expand_in', + 'nest' => '_expand_nest', + (map +($_ => '_expand_op_andor'), ('and', 'or')), + (map +($_ => '_expand_op_is'), ('is', 'is_not')), + 'ident' => '_expand_ident', + 'value' => '_expand_value', + }, + render => { + (map +($_, "_render_$_"), qw(op func bind ident literal row values)), + }, + render_op => { + (map +($_ => '_render_op_between'), 'between', 'not_between'), + (map +($_ => '_render_op_in'), 'in', 'not_in'), + (map +($_ => '_render_unop_postfix'), + 'is_null', 'is_not_null', 'asc', 'desc', + ), + (not => '_render_unop_paren'), + (map +($_ => '_render_op_andor'), qw(and or)), + ',' => '_render_op_multop', + }, + clauses_of => { + delete => [ qw(target where returning) ], + update => [ qw(target set where returning) ], + insert => [ qw(target fields from returning) ], + }, + expand_clause => { + 'delete.from' => '_expand_delete_clause_target', + 'update.update' => '_expand_update_clause_target', + 'insert.into' => '_expand_insert_clause_target', + 'insert.values' => '_expand_insert_clause_from', + }, + render_clause => { + 'delete.target' => '_render_delete_clause_target', + 'update.target' => '_render_update_clause_target', + 'insert.target' => '_render_insert_clause_target', + 'insert.fields' => '_render_insert_clause_fields', + 'insert.from' => '_render_insert_clause_from', + }, +); + +foreach my $stmt (keys %{$Defaults{clauses_of}}) { + $Defaults{expand}{$stmt} = '_expand_statement'; + $Defaults{render}{$stmt} = '_render_statement'; + foreach my $clause (@{$Defaults{clauses_of}{$stmt}}) { + $Defaults{expand_clause}{"${stmt}.${clause}"} + = "_expand_${stmt}_clause_${clause}"; + } +} + sub new { my $self = shift; my $class = ref($self) || $self; @@ -193,52 +266,9 @@ sub new { $opt{expand_unary} = {}; - $opt{expand} = { - not => '_expand_not', - bool => '_expand_bool', - and => '_expand_op_andor', - or => '_expand_op_andor', - nest => '_expand_nest', - bind => '_expand_bind', - in => '_expand_in', - not_in => '_expand_in', - row => '_expand_row', - between => '_expand_between', - not_between => '_expand_between', - op => '_expand_op', - (map +($_ => '_expand_op_is'), ('is', 'is_not')), - ident => '_expand_ident', - value => '_expand_value', - func => '_expand_func', - }; - - $opt{expand_op} = { - 'between' => '_expand_between', - 'not_between' => '_expand_between', - 'in' => '_expand_in', - 'not_in' => '_expand_in', - 'nest' => '_expand_nest', - (map +($_ => '_expand_op_andor'), ('and', 'or')), - (map +($_ => '_expand_op_is'), ('is', 'is_not')), - 'ident' => '_expand_ident', - 'value' => '_expand_value', - }; - - $opt{render} = { - (map +($_, "_render_$_"), qw(op func bind ident literal row)), - %{$opt{render}||{}} - }; - - $opt{render_op} = { - (map +($_ => '_render_op_between'), 'between', 'not_between'), - (map +($_ => '_render_op_in'), 'in', 'not_in'), - (map +($_ => '_render_unop_postfix'), - 'is_null', 'is_not_null', 'asc', 'desc', - ), - (not => '_render_unop_paren'), - (map +($_ => '_render_op_andor'), qw(and or)), - ',' => '_render_op_multop', - }; + foreach my $name (sort keys %Defaults) { + $opt{$name} = { %{$Defaults{$name}} }; + } if ($opt{lazy_join_sql_parts}) { my $mod = Module::Runtime::use_module('SQL::Abstract::Parts'); @@ -268,25 +298,46 @@ sub _assert_pass_injection_guard { #====================================================================== sub insert { - my $self = shift; - my $table = $self->_table(shift); - my $data = shift || return; - my $options = shift; + my ($self, $table, $data, $options) = @_; - my $fields; + my $stmt = do { + if (ref($table) eq 'HASH') { + $table; + } else { + my %clauses = (target => $table, values => $data, %{$options||{}}); + \%clauses; + } + }; + my @rendered = $self->render_statement({ -insert => $stmt }); + return wantarray ? @rendered : $rendered[0]; +} - my ($f_aqt, $v_aqt) = $self->_expand_insert_values($data); +sub _expand_insert_clause_target { + +(target => $_[0]->_expand_maybe_list_expr($_[2], -ident)); +} - my @parts = ([ $self->_sqlcase('insert into').' '.$table ]); - push @parts, $self->render_aqt($f_aqt) if $f_aqt; - push @parts, [ $self->_sqlcase('values') ], $self->render_aqt($v_aqt); +sub _expand_insert_clause_fields { + return +{ -row => [ + $_[0]->_expand_maybe_list_expr($_[2], -ident) + ] } if ref($_[2]) eq 'ARRAY'; + return $_[2]; # should maybe still expand somewhat? +} - if ($options->{returning}) { - push @parts, [ $self->_insert_returning($options) ]; +sub _expand_insert_clause_from { + my ($self, undef, $data) = @_; + if (ref($data) eq 'HASH' and (keys(%$data))[0] =~ /^-/) { + return $self->expand_expr($data); } + return $data if ref($data) eq 'HASH' and $data->{-row}; + my ($f_aqt, $v_aqt) = $self->_expand_insert_values($data); + return ( + from => { -values => [ $v_aqt ] }, + ($f_aqt ? (fields => $f_aqt) : ()), + ); +} - my ($sql, @bind) = @{ $self->join_query_parts(' ', @parts) }; - return wantarray ? ($sql, @bind) : $sql; +sub _expand_insert_clause_returning { + +(returning => $_[0]->_expand_maybe_list_expr($_[2], -ident)); } sub _expand_insert_values { @@ -319,10 +370,28 @@ sub _expand_insert_values { } } +sub _render_insert_clause_fields { + return $_[0]->render_aqt($_[2]); +} + +sub _render_insert_clause_target { + my ($self, undef, $from) = @_; + $self->join_query_parts(' ', $self->format_keyword('insert into'), $from); +} + +sub _render_insert_clause_from { + return $_[0]->render_aqt($_[2], 1); +} + # So that subclasses can override INSERT ... RETURNING separately from # UPDATE and DELETE (e.g. DBIx::Class::SQLMaker::Oracle does this) sub _insert_returning { shift->_returning(@_) } +sub _redispatch_returning { + my ($self, $type, undef, $returning) = @_; + [ $self->${\"_${type}_returning"}({ returning => $returning }) ]; +} + sub _returning { my ($self, $options) = @_; @@ -365,35 +434,28 @@ sub _expand_insert_value { # UPDATE methods #====================================================================== - sub update { - my $self = shift; - my $table = $self->_table(shift); - my $data = shift || return; - my $where = shift; - my $options = shift; + my ($self, $table, $set, $where, $options) = @_; - # first build the 'SET' part of the sql statement - puke "Unsupported data type specified to \$sql->update" - unless ref $data eq 'HASH'; - - my ($sql, @all_bind) = $self->_update_set_values($data); - $sql = $self->_sqlcase('update ') . $table . $self->_sqlcase(' set ') - . $sql; - - if ($where) { - my($where_sql, @where_bind) = $self->where($where); - $sql .= $where_sql; - push @all_bind, @where_bind; - } - - if ($options->{returning}) { - my ($returning_sql, @returning_bind) = $self->_update_returning($options); - $sql .= $returning_sql; - push @all_bind, @returning_bind; - } + my $stmt = do { + if (ref($table) eq 'HASH') { + $table + } else { + my %clauses; + @clauses{qw(target set where)} = ($table, $set, $where); + puke "Unsupported data type specified to \$sql->update" + unless ref($clauses{set}) eq 'HASH'; + @clauses{keys %$options} = values %$options; + \%clauses; + } + }; + my @rendered = $self->render_statement({ -update => $stmt }); + return wantarray ? @rendered : $rendered[0]; +} - return wantarray ? ($sql, @all_bind) : $sql; +sub _render_update_clause_target { + my ($self, undef, $target) = @_; + $self->join_query_parts(' ', $self->format_keyword('update'), $target); } sub _update_set_values { @@ -428,6 +490,24 @@ sub _expand_update_set_values { ] ); } +sub _expand_update_clause_target { + my ($self, undef, $target) = @_; + +(target => $self->_expand_maybe_list_expr($target, -ident)); +} + +sub _expand_update_clause_set { + return $_[2] if ref($_[2]) eq 'HASH' and ($_[2]->{-op}||[''])->[0] eq ','; + +(set => $_[0]->_expand_update_set_values($_[1], $_[2])); +} + +sub _expand_update_clause_where { + +(where => $_[0]->expand_expr($_[2])); +} + +sub _expand_update_clause_returning { + +(returning => $_[0]->_expand_maybe_list_expr($_[2], -ident)); +} + # So that subclasses can override UPDATE ... RETURNING separately from # INSERT and DELETE sub _update_returning { shift->_returning(@_) } @@ -470,30 +550,39 @@ sub _select_fields { # DELETE #====================================================================== - sub delete { - my $self = shift; - my $table = $self->_table(shift); - my $where = shift; - my $options = shift; + my ($self, $table, $where, $options) = @_; - my($where_sql, @bind) = $self->where($where); - my $sql = $self->_sqlcase('delete from ') . $table . $where_sql; - - if ($options->{returning}) { - my ($returning_sql, @returning_bind) = $self->_delete_returning($options); - $sql .= $returning_sql; - push @bind, @returning_bind; - } - - return wantarray ? ($sql, @bind) : $sql; + my $stmt = do { + if (ref($table) eq 'HASH') { + $table; + } else { + my %clauses = (target => $table, where => $where, %{$options||{}}); + \%clauses; + } + }; + my @rendered = $self->render_statement({ -delete => $stmt }); + return wantarray ? @rendered : $rendered[0]; } # So that subclasses can override DELETE ... RETURNING separately from # INSERT and UPDATE sub _delete_returning { shift->_returning(@_) } +sub _expand_delete_clause_target { + +(target => $_[0]->_expand_maybe_list_expr($_[2], -ident)); +} + +sub _expand_delete_clause_where { +(where => $_[0]->expand_expr($_[2])); } +sub _expand_delete_clause_returning { + +(returning => $_[0]->_expand_maybe_list_expr($_[2], -ident)); +} + +sub _render_delete_clause_target { + my ($self, undef, $from) = @_; + $self->join_query_parts(' ', $self->format_keyword('delete from'), $from); +} #====================================================================== # WHERE: entry point @@ -532,11 +621,12 @@ sub expand_expr { } sub render_aqt { - my ($self, $aqt) = @_; + my ($self, $aqt, $top_level) = @_; my ($k, $v, @rest) = %$aqt; die "No" if @rest; die "Not a node type: $k" unless $k =~ s/^-//; if (my $meth = $self->{render}{$k}) { + local our $Render_Top_Level = $top_level; return $self->$meth($k, $v); } die "notreached: $k"; @@ -549,6 +639,61 @@ sub render_expr { ) }; } +sub render_statement { + my ($self, $expr, $default_scalar_to) = @_; + @{$self->render_aqt( + $self->expand_expr($expr, $default_scalar_to), 1 + )}; +} + +sub _expand_statement { + my ($self, $type, $args) = @_; + my $ec = $self->{expand_clause}; + if ($args->{_}) { + $args = { %$args }; + $args->{$type} = delete $args->{_} + } + return +{ "-${type}" => +{ + map { + my $val = $args->{$_}; + if (defined($val) and my $exp = $ec->{"${type}.$_"}) { + if ((my (@exp) = $self->$exp($_ => $val)) == 1) { + ($_ => $exp[0]) + } else { + @exp + } + } else { + ($_ => $self->expand_expr($val)) + } + } sort keys %$args + } }; +} + +sub _render_statement { + my ($self, $type, $args) = @_; + my @parts; + foreach my $clause (@{$self->{clauses_of}{$type}}) { + next unless my $clause_expr = $args->{$clause}; + my $part = do { + if (my $rdr = $self->{render_clause}{"${type}.${clause}"}) { + $self->$rdr($clause, $clause_expr); + } else { + my $r = $self->render_aqt($clause_expr, 1); + next unless defined $r->[0] and length $r->[0]; + $self->join_query_parts(' ', + $self->format_keyword($clause), + $r + ); + } + }; + push @parts, $part; + } + my $q = $self->join_query_parts(' ', @parts); + return $self->join_query_parts('', + (our $Render_Top_Level ? $q : ('(', $q, ')')) + ); +} + sub _normalize_op { my ($self, $raw) = @_; my $op = lc $raw; @@ -1057,6 +1202,17 @@ sub _expand_bind { return { -bind => $bind }; } +sub _expand_values { + my ($self, undef, $values) = @_; + return { -values => [ + map +( + ref($_) eq 'HASH' + ? $self->expand_expr($_) + : +{ -row => [ map $self->expand_expr($_), @$_ ] } + ), ref($values) eq 'ARRAY' ? @$values : $values + ] }; +} + sub _recurse_where { my ($self, $where, $logic) = @_; @@ -1209,6 +1365,19 @@ sub _render_op_multop { return $self->join_query_parts($join, @parts); } +sub _render_values { + my ($self, undef, $values) = @_; + my $inner = $self->join_query_parts(' ', + $self->format_keyword('values'), + $self->join_query_parts(', ', + ref($values) eq 'ARRAY' ? @$values : $values + ), + ); + return $self->join_query_parts('', + (our $Render_Top_Level ? $inner : ('(', $inner, ')')) + ); +} + sub join_query_parts { my ($self, $join, @parts) = @_; my @final = map +(