a3add768f853fff9ffa550a3500b5a87ed26980f
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / SQLMaker / SQLStatement.pm
1 package # Hide from PAUSE
2    DBIx::Class::SQLMaker::SQLStatement;
3
4 use parent 'DBIx::Class::SQLMaker';
5
6 # SQL::Statement does not understand
7 # INSERT INTO $table DEFAULT VALUES
8 # Adjust SQL here instead
9 sub insert {  # basically just a copy of the MySQL version...
10    my $self = shift;
11
12    if (! $_[1] or (ref $_[1] eq 'HASH' and !keys %{$_[1]} ) ) {
13       my $table = $self->_quote($_[0]);
14       return "INSERT INTO ${table} (1) VALUES (1)"
15    }
16
17    return $self->next::method (@_);
18 }
19
20 # SQL::Statement does not understand
21 # SELECT ... FOR UPDATE
22 # Disable it here
23 sub _lock_select () { '' };
24
25 1;
26
27 # SQL::Statement can't handle more than
28 # one ANSI join, so just convert them all
29 # to Oracle 8i-style WHERE-clause joins
30
31 # (As such, we are stealing globs of code from OracleJoins.pm...)
32
33 sub select {
34    my ($self, $table, $fields, $where, $rs_attrs, @rest) = @_;
35
36    if (ref $table eq 'ARRAY') {
37       # count tables accurately
38       my ($cnt, @node) = (0, @$table);
39       while (my $tbl = shift @node) {
40          my $r = ref $tbl;
41          if    ($r eq 'ARRAY') { push(@node, @$tbl); }
42          elsif ($r eq 'HASH')  { $cnt++ if ($tbl->{'-rsrc'}); }
43       }
44
45       # pull out all join conds as regular WHEREs from all extra tables
46       # (but only if we're joining more than 2 tables)
47       if ($cnt > 2) {
48          $where = $self->_where_joins($where, @{ $table }[ 1 .. $#$table ]);
49       }
50    }
51
52    return $self->next::method($table, $fields, $where, $rs_attrs, @rest);
53 }
54
55 sub _recurse_from {
56    my ($self, $from, @join) = @_;
57
58    # check for a single JOIN
59    unless (@join > 1) {
60       my $sql = $self->next::method($from, @join);
61
62       # S:S still doesn't like the JOIN X ON ( Y ) syntax with the parens
63       $sql =~ s/JOIN (.+) ON \( (.+) \)/JOIN $1 ON $2/;
64       return $sql;
65    }
66
67    my @sqlf = $self->_from_chunk_to_sql($from);
68
69    for (@join) {
70       my ($to, $on) = @$_;
71
72       push (@sqlf, (ref $to eq 'ARRAY') ?
73          $self->_recurse_from(@$to) :
74          $self->_from_chunk_to_sql($to)
75       );
76    }
77
78    return join q{, }, @sqlf;
79 }
80
81 sub _where_joins {
82    my ($self, $where, @join) = @_;
83    my $join_where = $self->_recurse_where_joins(@join);
84
85    if (keys %$join_where) {
86       unless (defined $where) { $where = $join_where; }
87       else {
88          $where = { -or  => $where } if (ref $where eq 'ARRAY');
89          $where = { -and => [ $join_where, $where ] };
90       }
91    }
92    return $where;
93 }
94
95 sub _recurse_where_joins {
96    my $self = shift;
97
98    my @where;
99    foreach my $j (@_) {
100       my ($to, $on) = @$j;
101
102       push @where, $self->_recurse_where_joins(@$to) if (ref $to eq 'ARRAY');
103
104       my $join_opts = ref $to eq 'ARRAY' ? $to->[0] : $to;
105       if (ref $join_opts eq 'HASH' and my $jt = $join_opts->{-join_type}) {
106          # TODO: Figure out a weird way to support ANSI joins and WHERE joins at the same time.
107          # (Though, time would be better spent just fixing SQL::Parser to not require this stuff.)
108
109          $self->throw_exception("Can't handle non-inner, non-ANSI joins in SQL::Statement SQL yet!\n")
110             if $jt =~ /NATURAL|LEFT|RIGHT|FULL|CROSS|UNION/i;
111       }
112
113       # sadly SQLA treats where($scalar) as literal, so we need to jump some hoops
114       push @where, map { \sprintf ('%s = %s',
115          ref $_        ? $self->_recurse_where($_)        : $self->_quote($_),
116          ref $on->{$_} ? $self->_recurse_where($on->{$_}) : $self->_quote($on->{$_}),
117       ) } keys %$on;
118    }
119
120    return { -and => \@where };
121 }
122
123 1;