X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FData%2FQuery%2FRenderer%2FSQL%2FNaive.pm;h=56cdb3b4b3648f8617369924df6cdc9c5c68db0c;hb=07b0f7e08e4306ff180223809842668e4837865b;hp=1049b4674ea575ed507c3ba27e07c998f5ebd689;hpb=839687b2c07760f1cd638836f36e87a69df5ed5b;p=dbsrgits%2FData-Query.git diff --git a/lib/Data/Query/Renderer/SQL/Naive.pm b/lib/Data/Query/Renderer/SQL/Naive.pm index 1049b46..56cdb3b 100644 --- a/lib/Data/Query/Renderer/SQL/Naive.pm +++ b/lib/Data/Query/Renderer/SQL/Naive.pm @@ -1,25 +1,34 @@ package Data::Query::Renderer::SQL::Naive; use strictures 1; + +sub intersperse { my $i = shift; my @i = map +($_, $i), @_; pop @i; @i } + use SQL::ReservedWords; use Data::Query::Constants qw( DQ_IDENTIFIER DQ_OPERATOR DQ_VALUE DQ_JOIN DQ_ALIAS DQ_ORDER DQ_LITERAL + DQ_GROUP ); -sub new { - bless({ %{$_[1]||{}} }, (ref($_[0])||$_[0]))->BUILDALL; -} +use Moo; -sub BUILDALL { - my $self = shift; - $self->{reserved_ident_parts} - ||= ( - our $_DEFAULT_RESERVED ||= { map +($_ => 1), SQL::ReservedWords->words } - ); - $self->{quote_chars}||=['']; - $self->{simple_ops}||=$self->_default_simple_ops; - return $self; -} +has reserved_ident_parts => ( + is => 'ro', default => sub { + our $_DEFAULT_RESERVED ||= { map +($_ => 1), SQL::ReservedWords->words } + } +); + +has quote_chars => (is => 'ro', default => sub { [''] }); + +has identifier_sep => (is => 'ro', default => sub { '.' }); + +has simple_ops => (is => 'ro', builder => '_default_simple_ops'); + +has lc_keywords => (is => 'ro', default => sub { 0 }); + +has always_quote => (is => 'ro', default => sub { 0 }); + +has collapse_aliases => (is => 'ro', default => sub { 1 }); sub _default_simple_ops { +{ @@ -70,7 +79,7 @@ sub _flatten_structure { # # FEH. -sub _format_keyword { $_[0]->{lc_keywords} ? lc($_[1]) : $_[1] } +sub _format_keyword { $_[0]->lc_keywords ? lc($_[1]) : $_[1] } sub _render { $_[0]->${\"_render_${\(lc($_[1]->{type})||'broken')}"}($_[1]); @@ -86,17 +95,17 @@ sub _render_identifier { die "Unidentified identifier (SQL can no has \$_)" unless my @i = @{$_[1]->{elements}}; # handle single or paired quote chars - my ($q1, $q2) = @{$_[0]->{quote_chars}}[0,-1]; - my $always_quote = $_[0]->{always_quote}; - my $res_check = $_[0]->{reserved_ident_parts}; + my ($q1, $q2) = @{$_[0]->quote_chars}[0,-1]; + my $always_quote = $_[0]->always_quote; + my $res_check = $_[0]->reserved_ident_parts; return [ join - $_[0]->{identifier_sep}||'.', + $_[0]->identifier_sep, map +( $_ eq '*' # Yes, this means you can't have a column just called '*'. ? $_ # Yes, this is a feature. Go shoot the DBA if he disagrees. : ( # reserved are stored uc, quote if non-word - $always_quote || $res_check->{+uc} || /\W/ + ($always_quote and $q1) || $res_check->{+uc} || /\W/ ? $q1.$_.$q2 : $_ ) @@ -105,9 +114,7 @@ sub _render_identifier { } sub _render_value { - defined($_[1]->{value}) - ? [ '?', $_[1] ] - : [ 'NULL' ]; + [ '?', $_[1] ] } sub _operator_type { 'SQL.Naive' } @@ -119,7 +126,7 @@ sub _render_operator { $op->{$self->_operator_type} = $self->_convert_op($dq); } my $op_name = $op->{$self->_operator_type}; - if (my $op_type = $self->{simple_ops}{$op_name}) { + if (my $op_type = $self->simple_ops->{$op_name}) { return $self->${\"_handle_op_type_${op_type}"}($op_name, $dq); } elsif (my $meth = $self->can("_handle_op_special_${op_name}")) { return $self->$meth($dq); @@ -192,17 +199,24 @@ sub _handle_op_type_flatten { push @arg_final, $arg; } } - my @sql = ('(', map +($self->_render($_), $op_name), @arg_final); - $sql[-1] = ')'; # replace final AND or whatever with ) - \@sql; + [ '(', + intersperse( + $self->_format_keyword($op_name), + map $self->_render($_), @arg_final + ), + ')' + ]; } sub _handle_op_type_in { my ($self, $op, $dq) = @_; my ($lhs, @in) = @{$dq->{args}}; - my @rhs = ('(', map +($self->_render($_), ','), @in); - $rhs[-1] = ')'; - [ $self->_render($lhs), $op, @rhs ]; + [ $self->_render($lhs), + $op, + '(', + intersperse(',', map $self->_render($_), @in), + ')' + ]; } sub _handle_op_type_between { @@ -234,7 +248,7 @@ sub _handle_op_type_apply { }; [ "$ident(", - (map $self->_render($_), @args), + intersperse(',', map $self->_render($_), @args), ')' ] } @@ -259,19 +273,11 @@ sub _render_select { # it is, in fact, completely valid for there to be nothing for us # to project from since many databases handle 'SELECT 1;' fine - my @select = map [ - ($_->{type} eq DQ_ALIAS - ? $self->_render_alias($_, $self->_format_keyword('AS')) - : $self->_render($_) - ), - ',' - ], @{$dq->{select}}; - - # we put the commas inside the [] for each entry as a hint to the pretty - # printer downstream so now we need to eliminate the comma from the last - # entry - we know there always is one due to the die guard at the top - - pop @{$select[-1]}; + my @select = intersperse(',', + map +($_->{type} eq DQ_ALIAS + ? $self->_render_alias($_, $self->_format_keyword('AS')) + : $self->_render($_)), @{$dq->{select}} + ); return [ $self->_format_keyword('SELECT'), @@ -289,15 +295,17 @@ sub _render_alias { my ($self, $dq, $as) = @_; # FROM foo foo -> FROM foo # FROM foo.bar bar -> FROM foo.bar - if ($dq->{alias}{type} eq DQ_IDENTIFIER) { - if ($dq->{alias}{elements}[-1] eq $dq->{as}) { - return $self->_render($dq->{alias}); + if ($self->collapse_aliases) { + if ($dq->{from}{type} eq DQ_IDENTIFIER) { + if ($dq->{from}{elements}[-1] eq $dq->{to}) { + return $self->_render($dq->{from}); + } } } return [ - $self->_render($dq->{alias}), + $self->_render($dq->{from}), $as || ' ', - $self->_render_identifier({ elements => [ $dq->{as} ] }) + $self->_render_identifier({ elements => [ $dq->{to} ] }) ]; } @@ -319,18 +327,31 @@ sub _render_literal { sub _render_join { my ($self, $dq) = @_; - my ($left, $right) = @{$dq->{join}}; - die "No support for ON yet" if $dq->{on}; - die "No support for LEFT/RIGHT yet" if $dq->{outer}; - [ $self->_render($left), ',', $self->_render($right) ]; + my ($left, $right) = @{$dq}{qw(left right)}; + my $join = do { + if ($dq->{outer}) { + $self->_format_keyword(uc($dq->{outer}).' JOIN'); + } elsif ($dq->{on}) { + $self->_format_keyword('JOIN'); + } else { + ',' + } + }; + [ + $self->_render($left), $join, $self->_render($right), + ($dq->{on} + ? ($self->_format_keyword('ON'), $self->_render($dq->{on})) + : ()) + ]; } sub _render_where { my ($self, $dq) = @_; my ($from, $where) = @{$dq}{qw(from where)}; + my $keyword = ($from && $from->{type} eq DQ_GROUP) ? 'HAVING' : 'WHERE'; [ ($from ? $self->_render($from) : ()), - $self->_format_keyword('WHERE'), + $self->_format_keyword($keyword), $self->_render($where) ] } @@ -356,4 +377,74 @@ sub _render_order { \@ret; } +sub _render_group { + my ($self, $dq) = @_; + # this could also squash like order does. but I dunno whether that should + # move somewhere else just yet. + my @ret = ( + ($dq->{from} ? $self->_render($dq->{from}) : ()), + $self->_format_keyword('GROUP BY'), + intersperse(',', map $self->_render($_), @{$dq->{by}}) + ); + \@ret; +} + +sub _render_delete { + my ($self, $dq) = @_; + my ($target, $where) = @{$dq}{qw(target where)}; + [ $self->_format_keyword('DELETE FROM'), + $self->_render($target), + ($where + ? ($self->_format_keyword('WHERE'), $self->_render($where)) + : ()) + ]; +} + +sub _render_update { + my ($self, $dq) = @_; + my ($target, $set, $where) = @{$dq}{qw(target set where)}; + unless ($set) { + die "Must have set key - names+value keys not yet tested"; + my ($names, $value) = @{$dq}{qw(names value)}; + die "Must have names and value or set" unless $names and $value; + die "names and value must be same size" unless @$names == @$value; + $set = [ map [ $names->[$_], $value->[$_] ], 0..$#$names ]; + } + my @rendered_set = intersperse( + ',', map [ intersperse('=', map $self->_render($_), @$_) ], @{$set} + ); + [ $self->_format_keyword('UPDATE'), + $self->_render($target), + $self->_format_keyword('SET'), + @rendered_set, + ($where + ? ($self->_format_keyword('WHERE'), $self->_render($where)) + : ()) + ]; +} + +sub _render_insert { + my ($self, $dq) = @_; + my ($target, $names, $values, $returning) + = @{$dq}{qw(target names values returning)}; + unless ($values) { + die "Must have values key - sets key not yet implemented"; + } + [ $self->_format_keyword('INSERT INTO'), + $self->_render($target), + ($names + ? ('(', intersperse(',', map $self->_render($_), @$names), ')') + : ()), + $self->_format_keyword('VALUES'), + intersperse(',', + map [ '(', intersperse(',', map $self->_render($_), @$_), ')' ], + @$values + ), + ($returning + ? ($self->_format_keyword('RETURNING'), + intersperse(',', map $self->_render($_), @$returning)) + : ()), + ]; +} + 1;