X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FData%2FQuery%2FRenderer%2FSQL%2FNaive.pm;h=b8ad97926894257fa6e581482d6d1b2d66457c48;hb=d17f527f40d5126c57a87bdeb56a342049cc0a79;hp=0c6d24237ba3bdccf0190670e6bd522296468bea;hpb=6e055841e915c371077c5ff82268762668135a81;p=dbsrgits%2FData-Query.git diff --git a/lib/Data/Query/Renderer/SQL/Naive.pm b/lib/Data/Query/Renderer/SQL/Naive.pm index 0c6d242..b8ad979 100644 --- a/lib/Data/Query/Renderer/SQL/Naive.pm +++ b/lib/Data/Query/Renderer/SQL/Naive.pm @@ -1,25 +1,31 @@ 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_IDENTIFIER DQ_OPERATOR DQ_VALUE DQ_JOIN DQ_ALIAS DQ_ORDER DQ_LITERAL ); -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 }); sub _default_simple_ops { +{ @@ -61,12 +67,16 @@ sub _flatten_structure { ]; } -# I present this to permit strange people to easily supply a patch to lc() +# I presented this to permit strange people to easily supply a patch to lc() # their keywords, as I have heard many desire to do, lest they infect me # with whatever malady caused this desire by their continued proximity for # want of such a feature. +# +# Then I realised that SQL::Abstract compatibility work required it. +# +# FEH. -sub _format_keyword { $_[1] } +sub _format_keyword { $_[0]->lc_keywords ? lc($_[1]) : $_[1] } sub _render { $_[0]->${\"_render_${\(lc($_[1]->{type})||'broken')}"}($_[1]); @@ -82,12 +92,12 @@ 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. @@ -101,9 +111,7 @@ sub _render_identifier { } sub _render_value { - defined($_[1]->{value}) - ? [ '?', $_[1] ] - : [ 'NULL' ]; + [ '?', $_[1] ] } sub _operator_type { 'SQL.Naive' } @@ -115,7 +123,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); @@ -188,23 +196,38 @@ 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 { my ($self, $op_name, $dq) = @_; - my ($lhs, $rhs1, $rhs2) = (map $self->_render($_), @{$dq->{args}}); - [ $lhs, $op_name, $rhs1, 'AND', $rhs2 ]; + my @args = @{$dq->{args}}; + if (@args == 3) { + my ($lhs, $rhs1, $rhs2) = (map $self->_render($_), @args); + [ '(', $lhs, $op_name, $rhs1, 'AND', $rhs2, ')' ]; + } elsif (@args == 2 and $args[1]->{type} eq DQ_LITERAL) { + my ($lhs, $rhs) = (map $self->_render($_), @args); + [ '(', $lhs, $op_name, $rhs, ')' ]; + } else { + die "Invalid args for between: ${\scalar @args} given"; + } } sub _handle_op_type_apply { @@ -212,10 +235,17 @@ sub _handle_op_type_apply { my ($func, @args) = @{$dq->{args}}; die "Function name must be identifier" unless $func->{type} eq DQ_IDENTIFIER; - my $ident = $self->_render($func)->[0]; + my $ident = do { + # The problem we have here is that built-ins can't be quoted, generally. + # I rather wonder if things like MAX(...) need to -not- be handled as + # an apply and instead of something else, maybe a parenop type - but + # as an explicitly Naive renderer this seems like a reasonable answer. + local @{$self}{qw(reserved_ident_parts always_quote)}; + $self->_render_identifier($func)->[0]; + }; [ "$ident(", - (map $self->_render($_), @args), + intersperse(',', map $self->_render($_), @args), ')' ] } @@ -240,19 +270,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'), @@ -287,9 +309,15 @@ sub _render_literal { unless ($dq->{subtype} eq 'SQL') { die "Can't render non-SQL literal"; } - return [ - $dq->{literal}, @{$dq->{values}||[]} - ]; + if ($dq->{literal}) { + return [ + $dq->{literal}, @{$dq->{values}||[]} + ]; + } elsif ($dq->{parts}) { + return [ map $self->_render($_), @{$dq->{parts}} ]; + } else { + die "Invalid SQL literal - neither 'literal' nor 'parts' found"; + } } sub _render_join { @@ -331,4 +359,62 @@ sub _render_order { \@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;