basic expression rendering for SQL::Naive
[dbsrgits/Data-Query.git] / lib / Data / Query / Renderer / SQL / Naive.pm
1 package Data::Query::Renderer::SQL::Naive;
2
3 use strictures 1;
4 use SQL::ReservedWords;
5 use Data::Query::Constants qw(DQ_IDENTIFIER DQ_OPERATOR DQ_VALUE);
6
7 sub new {
8   bless({ %{$_[1]||{}} }, (ref($_[0])||$_[0]))->BUILDALL;
9 }
10
11 sub BUILDALL {
12   my $self = shift;
13   $self->{reserved_ident_parts}
14     ||= (
15       our $_DEFAULT_RESERVED ||= { map +($_ => 1), SQL::ReservedWords->words }
16     );
17   $self->{quote_chars}||=[''];
18   $self->{simple_ops}||=$self->_default_simple_ops;
19   return $self;
20 }
21
22 sub _default_simple_ops {
23   +{
24     (map +($_ => 'binop'), qw(= > < >= <=) ),
25     (map +($_ => 'unop'), (qw(NOT)) ),
26     (map +($_ => 'flatten'), qw(AND OR) ),
27   }
28 }
29
30 sub render {
31   my $self = shift;
32   $self->_flatten_structure($self->_render(@_))
33 }
34
35 sub _flatten_structure {
36   my ($self, $struct) = @_;
37   my @bind;
38   [ (join ' ', map {
39     my $r = ref;
40     if (!$r) { $_ }
41     elsif ($r eq 'ARRAY') {
42       my ($sql, @b) = @{$self->_flatten_structure($_)};
43       push @bind, @b;
44       $sql;
45     }
46     elsif ($r eq 'HASH') { push @bind, $_; () }
47     else { die "_flatten_structure can't handle ref type $r for $_" }
48   } @$struct), @bind ];
49 }
50
51 sub _render {
52   $_[0]->${\"_render_${\lc($_[1]->{type})}"}($_[1]);
53 }
54
55 sub _render_identifier {
56   die "Unidentified identifier (SQL can no has \$_)"
57     unless my @i = @{$_[1]->{elements}};
58   # handle single or paired quote chars
59   my ($q1, $q2) = @{$_[0]->{quote_chars}}[0,-1];
60   my $always_quote = $_[0]->{always_quote};
61   my $res_check = $_[0]->{reserved_ident_parts};
62   return [
63     join
64       $_[0]->{identifier_sep}||'.',
65       map +(
66         $_ eq '*' # Yes, this means you can't have a column just called '*'.
67           ? $_    # Yes, this is a feature. Go shoot the DBA if he disagrees.
68           : ( # reserved are stored uc, quote if non-word
69               $always_quote || $res_check->{+uc} || /\W/
70                 ? $q1.$_.$q2
71                 : $_
72             )
73       ), @i
74   ];
75 }
76
77 sub _render_value {
78   [ '?', $_[1] ];
79 }
80
81 sub _render_operator {
82   my ($self, $dq) = @_;
83   my $op = $dq->{operator};
84   unless (exists $op->{'SQL.Naive'}) {
85     $op->{'SQL.Naive'} = $self->_convert_op($dq);
86   }
87   if (my $op_type = $self->{simple_ops}{my $op_name = $op->{'SQL.Naive'}}) {
88     return $self->${\"_handle_op_type_${op_type}"}($op_name, $dq);
89   }
90   die "Couldn't render operator ".$op->{'SQL.Naive'};
91 }
92
93 sub _handle_op_type_binop {
94   my ($self, $op_name, $dq) = @_;
95   die "${op_name} registered as binary op but args contain "
96       .scalar(@{$dq->{args}})." entries"
97     unless @{$dq->{args}} == 2;
98   [
99     $self->_render($dq->{args}[0]),
100     $op_name,
101     $self->_render($dq->{args}[1]),
102   ]
103 }
104
105 sub _handle_op_type_unop {
106   my ($self, $op_name, $dq) = @_;
107   die "${op_name} registered as unary op but args contain "
108       .scalar(@{$dq->{args}})." entries"
109     unless @{$dq->{args}} == 1;
110   [
111     [ $op_name ],
112     $self->_render($dq->{args}[0]),
113   ]
114 }
115
116 sub _handle_op_type_flatten {
117   my ($self, $op_name, $dq) = @_;
118   my @argq = @{$dq->{args}};
119   my @arg_final;
120   while (my $arg = shift @argq) {
121
122     unless ($arg->{type} eq DQ_OPERATOR) {
123       push @arg_final, $arg;
124       next;
125     }
126
127     my $op = $arg->{operator};
128     unless (exists $op->{'SQL.Naive'}) {
129       $op->{'SQL.Naive'} = $self->_convert_op($arg);
130     }
131   
132     if ($op->{'SQL.Naive'} eq $op_name) {
133       unshift @argq, @{$arg->{args}};
134     } else {
135       push @arg_final, $arg;
136     }
137   }
138   my @sql = ('(', map +($self->_render($_), $op_name), @arg_final);
139   $sql[-1] = ')'; # replace final AND or whatever with )
140   \@sql;
141 }
142
143 sub _convert_op {
144   my ($self, $dq) = @_;
145   if (my $perl_op = $dq->{'operator'}->{'Perl'}) {
146     for ($perl_op) {
147       $_ eq '==' and return '=';
148       $_ eq 'eq' and return '=';
149       $_ eq '!' and return 'NOT';
150     }
151     return uc $perl_op; # hope!
152   }
153   die "Can't convert non-perl op yet";
154 }
155   
156 1;