line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Class::DBI::SQL::Transformer::Quotify; |
2
|
|
|
|
|
|
|
|
3
|
2
|
|
|
2
|
|
54593
|
use warnings; |
|
2
|
|
|
|
|
6
|
|
|
2
|
|
|
|
|
79
|
|
4
|
2
|
|
|
2
|
|
12
|
use strict; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
91
|
|
5
|
|
|
|
|
|
|
|
6
|
2
|
|
|
2
|
|
14
|
use base qw/Class::DBI::SQL::Transformer/; |
|
2
|
|
|
|
|
15
|
|
|
2
|
|
|
|
|
2245
|
|
7
|
|
|
|
|
|
|
our $VERSION = '0.02'; |
8
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
sub _expand_table { |
10
|
3
|
|
|
3
|
|
6
|
my $self = shift; |
11
|
3
|
|
|
|
|
6
|
my $s = shift; |
12
|
3
|
100
|
|
|
|
16
|
my ($class, $alias) = split /=/, defined($s)?$s:'', 2; |
13
|
3
|
|
|
|
|
11
|
my $caller = $self->{_caller}; |
14
|
3
|
100
|
|
|
|
19
|
my $table = $class ? $class->table : $caller->table; |
15
|
3
|
|
33
|
|
|
73
|
$self->{cmap}{ $alias || $table } = $class || ref $caller || $caller; |
|
|
|
66
|
|
|
|
|
16
|
3
|
|
50
|
|
|
16
|
($alias ||= "") &&= " ".$caller->db_Main->quote_identifier($alias); |
|
|
|
33
|
|
|
|
|
17
|
3
|
|
|
|
|
9
|
return $caller->db_Main->quote_identifier($table) . $alias; |
18
|
|
|
|
|
|
|
} |
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
sub _expand_join { |
21
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
22
|
0
|
|
|
|
|
0
|
my $joins = shift; |
23
|
0
|
|
|
|
|
0
|
my @table = split /\s+/, $joins; |
24
|
|
|
|
|
|
|
|
25
|
0
|
|
|
|
|
0
|
my $caller = $self->{_caller}; |
26
|
0
|
|
|
|
|
0
|
my %tojoin = map { $table[$_] => $table[ $_ + 1 ] } 0 .. $#table - 1; |
|
0
|
|
|
|
|
0
|
|
27
|
0
|
|
|
|
|
0
|
my @sql; |
28
|
0
|
|
|
|
|
0
|
while (my ($t1, $t2) = each %tojoin) { |
29
|
0
|
|
0
|
|
|
0
|
my ($c1, $c2) = map $self->{cmap}{$_} |
30
|
|
|
|
|
|
|
|| $caller->_croak("Don't understand table '$_' in JOIN"), ($t1, $t2); |
31
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
my $join_col = sub { |
33
|
0
|
|
|
0
|
|
0
|
my ($c1, $c2) = @_; |
34
|
0
|
|
|
|
|
0
|
my $meta = $c1->meta_info('has_a'); |
35
|
0
|
|
|
|
|
0
|
my ($col) = grep $meta->{$_}->foreign_class eq $c2, keys %$meta; |
36
|
0
|
|
|
|
|
0
|
$col; |
37
|
0
|
|
|
|
|
0
|
}; |
38
|
|
|
|
|
|
|
|
39
|
0
|
|
0
|
|
|
0
|
my $col = $join_col->($c1 => $c2) || do { |
40
|
|
|
|
|
|
|
($c1, $c2) = ($c2, $c1); |
41
|
|
|
|
|
|
|
($t1, $t2) = ($t2, $t1); |
42
|
|
|
|
|
|
|
$join_col->($c1 => $c2); |
43
|
|
|
|
|
|
|
}; |
44
|
|
|
|
|
|
|
|
45
|
0
|
0
|
|
|
|
0
|
$caller->_croak("Don't know how to join $c1 to $c2") unless $col; |
46
|
0
|
|
|
|
|
0
|
push @sql, sprintf " %s = %s ", |
47
|
|
|
|
|
|
|
$caller->db_Main->quote_identifier($t1, $col), |
48
|
|
|
|
|
|
|
$caller->db_Main->quote_identifier($t2, $c2->primary_column); |
49
|
|
|
|
|
|
|
} |
50
|
0
|
|
|
|
|
0
|
return join " AND ", @sql; |
51
|
|
|
|
|
|
|
} |
52
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
sub _backtickify_arg { |
54
|
360
|
|
|
360
|
|
474
|
my $self = shift; |
55
|
360
|
|
|
|
|
596
|
my $caller = $self->{_caller}; |
56
|
360
|
|
50
|
|
|
928
|
my $char = $caller->db_Main->get_info(29) || q{"}; # SQL_IDENTIFIER_QUOTE_CHAR |
57
|
360
|
100
|
|
|
|
18655
|
return $_[0] if $_[0] =~ /^$char[^$char]*$char$/; # return if already quoted |
58
|
297
|
|
|
|
|
920
|
my @cols = $_[1] |
59
|
486
|
|
|
|
|
790
|
? @{$_[1]} # use what's given us (in the recursion cases) |
60
|
|
|
|
|
|
|
# or (the initial case) use all cols, sorted longest to shortest |
61
|
|
|
|
|
|
|
# This is necessary so that 'foo bar' gets processed before 'foo', |
62
|
|
|
|
|
|
|
# so that if you have "foo bar" it doesn't become "`foo` bar" |
63
|
324
|
100
|
|
|
|
1046
|
: sort { length $b <=> length $a } map { "$_" } $caller->all_columns |
|
243
|
|
|
|
|
13557
|
|
64
|
|
|
|
|
|
|
; |
65
|
324
|
100
|
|
|
|
881
|
return $_[0] unless @cols; |
66
|
287
|
|
|
|
|
490
|
my $c = shift @cols; # process first col |
67
|
287
|
|
|
|
|
766
|
my $quoted = $caller->db_Main->quote_identifier($c); |
68
|
287
|
|
|
|
|
15854
|
$_[0] =~ s/\b(?
|
69
|
|
|
|
|
|
|
# Recurse on all the pieces w/the remaining columns to process. |
70
|
|
|
|
|
|
|
# Note the the quoted ones will just return right way. |
71
|
287
|
|
|
|
|
3665
|
my @s = map { $self->_backtickify_arg($_,\@cols) } split /($quoted)/, $_[0]; |
|
324
|
|
|
|
|
866
|
|
72
|
287
|
|
|
|
|
551
|
$_[0] = join '', @s; |
73
|
287
|
|
|
|
|
1025
|
return $_[0]; |
74
|
|
|
|
|
|
|
} |
75
|
|
|
|
|
|
|
|
76
|
|
|
|
|
|
|
sub _do_transformation { |
77
|
59
|
|
|
59
|
|
67687
|
my $me = shift; |
78
|
59
|
|
|
|
|
837
|
my $sql = $me->{_sql}; |
79
|
59
|
|
|
|
|
87
|
my @args = @{ $me->{_args} }; |
|
59
|
|
|
|
|
144
|
|
80
|
59
|
|
|
|
|
106
|
my $caller = $me->{_caller}; |
81
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
# Each entry in @args is a SQL fragment. This will bugger with fragments that |
83
|
|
|
|
|
|
|
# contain strings that match column names but are not supposed to be column names. |
84
|
59
|
|
|
|
|
170
|
$me->_backtickify_arg($_) for @args; |
85
|
|
|
|
|
|
|
|
86
|
59
|
|
|
|
|
140
|
$sql =~ s/__TABLE(?:\((.+?)\))?__/$me->_expand_table($1)/eg; |
|
3
|
|
|
|
|
11
|
|
87
|
59
|
|
|
|
|
207
|
$sql =~ s/__JOIN\((.+?)\)__/$me->_expand_join($1)/eg; |
|
0
|
|
|
|
|
0
|
|
88
|
59
|
|
|
|
|
111
|
$sql =~ s/__ESSENTIAL__/join ", ", map { $caller->db_Main->quote_identifier($_) } $caller->_essential/eg; |
|
1
|
|
|
|
|
10
|
|
|
9
|
|
|
|
|
457
|
|
89
|
59
|
|
|
|
|
141
|
$sql =~ |
90
|
2
|
|
|
|
|
10
|
s/__ESSENTIAL\((.+?)\)__/join ", ", map $caller->db_Main->quote_identifier($1,$_), $caller->_essential/eg; |
91
|
59
|
100
|
|
|
|
1369
|
if ($sql =~ /__IDENTIFIER__/) { |
92
|
1
|
|
|
|
|
13
|
my $key_sql = join " AND ", map $caller->db_Main->quote_identifier($_).'=?', $caller->primary_columns; |
93
|
1
|
|
|
|
|
95
|
$sql =~ s/__IDENTIFIER__/$key_sql/g; |
94
|
|
|
|
|
|
|
} |
95
|
|
|
|
|
|
|
|
96
|
59
|
|
|
|
|
134
|
$me->{_transformed_sql} = $sql; |
97
|
59
|
|
|
|
|
159
|
$me->{_transformed_args} = [@args]; |
98
|
59
|
|
|
|
|
96
|
$me->{_transformed} = 1; |
99
|
59
|
|
|
|
|
1021
|
return 1; |
100
|
|
|
|
|
|
|
} |
101
|
|
|
|
|
|
|
|
102
|
|
|
|
|
|
|
1; |
103
|
|
|
|
|
|
|
|
104
|
|
|
|
|
|
|
=pod |
105
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
=head1 NAME |
107
|
|
|
|
|
|
|
|
108
|
|
|
|
|
|
|
Class::DBI::SQL::Transformer::Quotify - Quote column and table names in Class::DBI-generated SQL |
109
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
=head1 VERSION |
111
|
|
|
|
|
|
|
|
112
|
|
|
|
|
|
|
Version 0.02 |
113
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
=head1 SYNOPSIS |
115
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
package Foo; |
117
|
|
|
|
|
|
|
use base qw/Class::DBI/; |
118
|
|
|
|
|
|
|
__PACKAGE__->connection('DBI:Mock:', '', ''); |
119
|
|
|
|
|
|
|
__PACKAGE__->sql_transformer_class('Class::DBI::SQL::Transformer::Quotify'); |
120
|
|
|
|
|
|
|
__PACKAGE__->table('table name'); |
121
|
|
|
|
|
|
|
__PACKAGE__->columns( Essential => 'my id', 'my name' ); |
122
|
|
|
|
|
|
|
package main; |
123
|
|
|
|
|
|
|
my $row = Foo->retrieve( 3 ); |
124
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
=head1 DESCRIPTION |
126
|
|
|
|
|
|
|
|
127
|
|
|
|
|
|
|
This is an attempt to solve the problem of spaces and/or reserved words in table and/or column names. Normally, Class::DBI does not quote these, so it results in sql such as the following (which clearly will error out): |
128
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
SELECT my id, my name |
130
|
|
|
|
|
|
|
FROM table name |
131
|
|
|
|
|
|
|
WHERE my id = ? |
132
|
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
This is implemented by subclassing L and notifying L via its C attribute. Note that some of the methods are completely replaced. |
134
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
=head1 BACKGROUND/EVOLUTION |
136
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
I first came upon L, which worked great, except the naming of the schema was so bad I hit an edge case that needed fixing first, which got me looking under the hood: L |
138
|
|
|
|
|
|
|
|
139
|
|
|
|
|
|
|
Since that version of Class::DBI::Plugin::Backtickify, Class::DBI (as of v3.0.8) had refactored the Class::DBI::SQL::Transformer class and introduced the Class::DBIsql_transformer_class() method. Which is why this module has the namespace it does instead of Class::DBI::Plugin:: and why I didn't just submit a patch for Backtickify. |
140
|
|
|
|
|
|
|
|
141
|
|
|
|
|
|
|
Drawing heavily from Backtickify, i generalized it to this module by using L::quote_identifier() instead of a hardcoded backtick. |
142
|
|
|
|
|
|
|
|
143
|
|
|
|
|
|
|
This potentially is (at least a partial) solution (or workaround) for Class::DBI RT ticket 7715 I: L |
144
|
|
|
|
|
|
|
|
145
|
|
|
|
|
|
|
In the course of investigation, also reported this Class::DBI issue, which this module also resolves: L |
146
|
|
|
|
|
|
|
|
147
|
|
|
|
|
|
|
=head1 AUTHOR |
148
|
|
|
|
|
|
|
|
149
|
|
|
|
|
|
|
David Westbrook (CPAN: davidrw), C<< >> |
150
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
=head1 BUGS |
152
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
Please report any bugs or feature requests to C, or through |
154
|
|
|
|
|
|
|
the web interface at L. I will be notified, and then you'll |
155
|
|
|
|
|
|
|
automatically be notified of progress on your bug as I make changes. |
156
|
|
|
|
|
|
|
|
157
|
|
|
|
|
|
|
=head1 SUPPORT |
158
|
|
|
|
|
|
|
|
159
|
|
|
|
|
|
|
You can find documentation for this module with the perldoc command. |
160
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
perldoc Class::DBI::SQL::Transformer::Quotify |
162
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
You can also look for information at: |
164
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
=over 4 |
166
|
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
=item * RT: CPAN's request tracker |
168
|
|
|
|
|
|
|
|
169
|
|
|
|
|
|
|
L |
170
|
|
|
|
|
|
|
|
171
|
|
|
|
|
|
|
=item * AnnoCPAN: Annotated CPAN documentation |
172
|
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
L |
174
|
|
|
|
|
|
|
|
175
|
|
|
|
|
|
|
=item * CPAN Ratings |
176
|
|
|
|
|
|
|
|
177
|
|
|
|
|
|
|
L |
178
|
|
|
|
|
|
|
|
179
|
|
|
|
|
|
|
=item * Search CPAN |
180
|
|
|
|
|
|
|
|
181
|
|
|
|
|
|
|
L |
182
|
|
|
|
|
|
|
|
183
|
|
|
|
|
|
|
=back |
184
|
|
|
|
|
|
|
|
185
|
|
|
|
|
|
|
=head1 SEE ALSO |
186
|
|
|
|
|
|
|
|
187
|
|
|
|
|
|
|
L, L, L, L |
188
|
|
|
|
|
|
|
|
189
|
|
|
|
|
|
|
=head1 ACKNOWLEDGEMENTS |
190
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
David Baird for the groundwork of Class::DBI::Plugin::Backtickify |
192
|
|
|
|
|
|
|
|
193
|
|
|
|
|
|
|
=head1 COPYRIGHT & LICENSE |
194
|
|
|
|
|
|
|
|
195
|
|
|
|
|
|
|
Copyright 2008 David Westbrook, all rights reserved. |
196
|
|
|
|
|
|
|
|
197
|
|
|
|
|
|
|
This program is free software; you can redistribute it and/or modify it |
198
|
|
|
|
|
|
|
under the same terms as Perl itself. |
199
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
=cut |
201
|
|
|
|
|
|
|
|