line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Class::ReluctantORM::SQL; |
2
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
=head1 NAME |
4
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
Class::ReluctantORM::SQL - Represent SQL Statements |
6
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
=head1 SYNOPSIS |
8
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
use Class::ReluctantORM::SQL::Aliases; |
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
# Insert |
12
|
|
|
|
|
|
|
my $insert = Class::ReluctantORM::SQL->new('insert'); |
13
|
|
|
|
|
|
|
$insert->table(Table->new(table => 'table_name')); |
14
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
# TODO DOCS |
16
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
$sql->table(Table->new($tb_class); |
18
|
|
|
|
|
|
|
$sql->add_input($sql_column); |
19
|
|
|
|
|
|
|
$sql->add_output($sql_column); |
20
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
=head1 DESCRIPTION |
22
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
Represent SQL DML statements (INSERT, SELECT, UPDATE, and DELETE) in an abstract, driver-independent way. Class::ReluctantORM uses this suite of classes to construct each statement that it executes, then passes it to the Driver for rendering and execution. Results are then stored in the SQL object, and may be retrieved directly or inflated into Class::ReluctantORM objects. |
24
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
=head2 Clauses, Relations, and Expressions |
26
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
The SQL objects are loosely grouped into 4 categories: |
28
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
=over |
30
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
=item Statement - Class::ReluctantORM::SQL |
32
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
Represents a DML SQL statement, its parameters and bindings, and output columns and fetched values. |
34
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
Provides a location for the clauses, whether as strings or as objects. |
36
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
=item Clauses - Where, From, OrderBy, Limit |
38
|
|
|
|
|
|
|
|
39
|
|
|
|
|
|
|
Represents major portions of the statement. These clauses are independent objects which are built separately, then attached to the SQL statment object. |
40
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
=item Relations - Table, Join, SubQuery |
42
|
|
|
|
|
|
|
|
43
|
|
|
|
|
|
|
Represents a table-like entity. Relations share a common superclass (Class::ReluctantORM::SQL::Relation), know about their columns, and are used in From clauses. |
44
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
=item Expressions - Literal, FunctionCall, Column, Param |
46
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
Represents an expression, which may be used in numerous locations. |
48
|
|
|
|
|
|
|
|
49
|
|
|
|
|
|
|
=back |
50
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
|
52
|
|
|
|
|
|
|
=head2 Retrieving and Inflating Results |
53
|
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
Some SQL statement objects can have OutputColumn objects associated with them (this includes all SELECT statments, and INSERT and UPDATE statements with RETURNING clauses). As results are retrieved, the values are stored in these OutputColumns. |
55
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
If the statement is expected to only have one row of results, you can simply do this: |
57
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
$driver->run_sql($sql); |
59
|
|
|
|
|
|
|
foreach my $oc ($sql->output_columns) { |
60
|
|
|
|
|
|
|
# do something with $oc->output_value(); |
61
|
|
|
|
|
|
|
} |
62
|
|
|
|
|
|
|
|
63
|
|
|
|
|
|
|
If the statement is expected to return multiple rows, you should register a callback: |
64
|
|
|
|
|
|
|
|
65
|
|
|
|
|
|
|
my $handle_fetchrow = sub { |
66
|
|
|
|
|
|
|
my $sql = shift; |
67
|
|
|
|
|
|
|
foreach my $oc ($sql->output_columns) { |
68
|
|
|
|
|
|
|
# do something with $oc->output_value(); |
69
|
|
|
|
|
|
|
} |
70
|
|
|
|
|
|
|
}; |
71
|
|
|
|
|
|
|
$sql->add_fetchrow_listener($handle_fetchrow); |
72
|
|
|
|
|
|
|
$driver->run_sql($sql) |
73
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
If you are seeking Class::ReluctantORM model objects (like Ships and Pirates), you need to use the inflation facility: |
75
|
|
|
|
|
|
|
|
76
|
|
|
|
|
|
|
if ($sql->is_inflatable()) { |
77
|
|
|
|
|
|
|
@ships = $sql->inflate(); |
78
|
|
|
|
|
|
|
} else { |
79
|
|
|
|
|
|
|
# Too complex |
80
|
|
|
|
|
|
|
} |
81
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
=head2 Parsing Support |
83
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
Parsing support is provided by the Driver area. See Class::ReluctantORM::Driver. |
85
|
|
|
|
|
|
|
|
86
|
|
|
|
|
|
|
=head2 Non-Parsed SQL Support |
87
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
If you perform a query with 'parse_sql' false (or set that as a global default, see Class::ReluctantORM - Non-Parsed SQL Support), the SQL object still acts as the data object and provides execution and fetching services. Instead of populating the where attribute (which is expected to be a Where object), populate the raw_where attribute (which is expected to be a string, the SQL WHERE clause). |
89
|
|
|
|
|
|
|
|
90
|
|
|
|
|
|
|
You may build your SQL object out of a mix of objects and raw SQL, though this is less likely to work. |
91
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
Support is eventually planned for there to be a rw_from, raw_ordeR_by, raw_group_by, and raw_statement. For now, only raw_where is supported. |
93
|
|
|
|
|
|
|
|
94
|
|
|
|
|
|
|
|
95
|
|
|
|
|
|
|
|
96
|
|
|
|
|
|
|
=begin devdocs |
97
|
|
|
|
|
|
|
|
98
|
|
|
|
|
|
|
also provide raw_order_by raw_from raw_group_by and raw_statement |
99
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
=cut |
101
|
|
|
|
|
|
|
|
102
|
|
|
|
|
|
|
=head2 Annotate and Reconcile |
103
|
|
|
|
|
|
|
|
104
|
|
|
|
|
|
|
After constructing a SQL object, it will usually need some additional metadata associated with it before being executed. This metadata can generally be discovered automatically. |
105
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
The annotate() method is called internally (usually before an inflate()) to associate table references with classes in your model. |
107
|
|
|
|
|
|
|
|
108
|
|
|
|
|
|
|
The reconcile() method is called internally before the rendering process to ensure that all column and table references are resolvable and unambiguous. |
109
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
=head2 Auto-Aliasing of SQL Classes |
111
|
|
|
|
|
|
|
|
112
|
|
|
|
|
|
|
Because the class names tend to get rather long, this module by default |
113
|
|
|
|
|
|
|
exports subroutines whose return value is the name of a SQL class. For example: |
114
|
|
|
|
|
|
|
|
115
|
|
|
|
|
|
|
Table() # returns 'Class::ReluctantORM::SQL::Table'; |
116
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
This allows you to do this: |
118
|
|
|
|
|
|
|
|
119
|
|
|
|
|
|
|
my $table = Table->new(...); |
120
|
|
|
|
|
|
|
|
121
|
|
|
|
|
|
|
This functionality is very similar to that provided by the 'aliased' CPAN module, |
122
|
|
|
|
|
|
|
but here is provided automatically. |
123
|
|
|
|
|
|
|
|
124
|
|
|
|
|
|
|
=head2 Limitations |
125
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
This is not a general purpose SQL abstraction library, but it is close. |
127
|
|
|
|
|
|
|
Operations that are not supported by Class::ReluctantORM will generally not be well supported by this module. |
128
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
In particular: |
130
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
=over |
132
|
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
=item DML only |
134
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
No support for data definition language (CREATE TABLE, etc) is planned. |
136
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
=item Single-table INSERTs, UPDATEs, and DELETEs |
138
|
|
|
|
|
|
|
|
139
|
|
|
|
|
|
|
There is no support for UPDATE ... FROM, for example. |
140
|
|
|
|
|
|
|
|
141
|
|
|
|
|
|
|
=item Aggregate Support is in its infancy |
142
|
|
|
|
|
|
|
|
143
|
|
|
|
|
|
|
Aggregates are not supported in combination with JOINs. |
144
|
|
|
|
|
|
|
|
145
|
|
|
|
|
|
|
=back |
146
|
|
|
|
|
|
|
|
147
|
|
|
|
|
|
|
=cut |
148
|
|
|
|
|
|
|
|
149
|
1
|
|
|
1
|
|
5
|
use strict; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
29
|
|
150
|
1
|
|
|
1
|
|
5
|
use warnings; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
35
|
|
151
|
|
|
|
|
|
|
our $DEBUG ||=2; |
152
|
|
|
|
|
|
|
|
153
|
1
|
|
|
1
|
|
4
|
use Data::Dumper; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
53
|
|
154
|
1
|
|
|
1
|
|
6
|
use Scalar::Util qw(blessed); |
|
1
|
|
|
|
|
7
|
|
|
1
|
|
|
|
|
46
|
|
155
|
1
|
|
|
1
|
|
5
|
use Class::ReluctantORM::Utilities qw(check_args); |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
48
|
|
156
|
1
|
|
|
1
|
|
5
|
use Class::ReluctantORM::FetchDeep::Results qw(fd_inflate); |
|
1
|
|
|
|
|
9
|
|
|
1
|
|
|
|
|
45
|
|
157
|
|
|
|
|
|
|
|
158
|
1
|
|
|
1
|
|
5
|
use base 'Class::ReluctantORM::OriginSupport'; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
70
|
|
159
|
1
|
|
|
1
|
|
6
|
use base 'Class::Accessor::Fast'; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
59
|
|
160
|
|
|
|
|
|
|
|
161
|
1
|
|
|
1
|
|
5
|
use Class::ReluctantORM::SQL::Aliases; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
100
|
|
162
|
|
|
|
|
|
|
|
163
|
1
|
|
|
1
|
|
598
|
use Class::ReluctantORM::SQL::Column; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
8
|
|
164
|
1
|
|
|
1
|
|
564
|
use Class::ReluctantORM::SQL::Expression::Criterion; |
|
1
|
|
|
|
|
4
|
|
|
1
|
|
|
|
|
17
|
|
165
|
1
|
|
|
1
|
|
36
|
use Class::ReluctantORM::SQL::Expression; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
6
|
|
166
|
1
|
|
|
1
|
|
854
|
use Class::ReluctantORM::SQL::From; |
|
1
|
|
|
|
|
4
|
|
|
1
|
|
|
|
|
29
|
|
167
|
1
|
|
|
1
|
|
6
|
use Class::ReluctantORM::SQL::Function; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
8
|
|
168
|
1
|
|
|
1
|
|
23
|
use Class::ReluctantORM::SQL::Expression::FunctionCall; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
16
|
|
169
|
1
|
|
|
1
|
|
19
|
use Class::ReluctantORM::SQL::From::Join; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
4
|
|
170
|
1
|
|
|
1
|
|
20
|
use Class::ReluctantORM::SQL::Expression::Literal; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
11
|
|
171
|
1
|
|
|
1
|
|
580
|
use Class::ReluctantORM::SQL::OrderBy; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
22
|
|
172
|
1
|
|
|
1
|
|
506
|
use Class::ReluctantORM::SQL::OutputColumn; |
|
1
|
|
|
|
|
4
|
|
|
1
|
|
|
|
|
6
|
|
173
|
1
|
|
|
1
|
|
96
|
use Class::ReluctantORM::SQL::Param; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
7
|
|
174
|
1
|
|
|
1
|
|
21
|
use Class::ReluctantORM::SQL::From::Relation; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
7
|
|
175
|
1
|
|
|
1
|
|
570
|
use Class::ReluctantORM::SQL::SubQuery; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
10
|
|
176
|
1
|
|
|
1
|
|
23
|
use Class::ReluctantORM::SQL::Table; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
9
|
|
177
|
1
|
|
|
1
|
|
550
|
use Class::ReluctantORM::SQL::Where; |
|
1
|
|
|
|
|
4
|
|
|
1
|
|
|
|
|
8671
|
|
178
|
|
|
|
|
|
|
|
179
|
|
|
|
|
|
|
|
180
|
|
|
|
|
|
|
=head1 CONSTRUCTORS |
181
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
=cut |
183
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
=head2 $sql = SQL->new('operation'); |
185
|
|
|
|
|
|
|
|
186
|
|
|
|
|
|
|
Creates a new abstract SQL object. Operation must be one of |
187
|
|
|
|
|
|
|
INSERT, UPDATE, DELETE, or SELECT. Case is ignored. |
188
|
|
|
|
|
|
|
|
189
|
|
|
|
|
|
|
=cut |
190
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
our %OPERATIONS = map {uc($_) => 1} qw(select update delete insert); |
192
|
|
|
|
|
|
|
|
193
|
|
|
|
|
|
|
sub new { |
194
|
0
|
|
|
0
|
1
|
|
my $class = shift; |
195
|
0
|
|
|
|
|
|
my $op = shift; |
196
|
0
|
0
|
|
|
|
|
unless ($op) { Class::ReluctantORM::Exception::Param::Missing->croak(param => 'operation'); } |
|
0
|
|
|
|
|
|
|
197
|
0
|
0
|
|
|
|
|
unless (exists $OPERATIONS{uc($op)}) { |
198
|
0
|
|
|
|
|
|
Class::ReluctantORM::Exception::Param::BadValue->croak(param => 'operation', value => uc($op)); |
199
|
|
|
|
|
|
|
} |
200
|
|
|
|
|
|
|
|
201
|
0
|
|
|
|
|
|
my $self = bless {}, $class; |
202
|
0
|
|
|
|
|
|
$self->set('operation', uc($op)); |
203
|
0
|
|
|
|
|
|
$self->{outputs} = []; |
204
|
0
|
|
|
|
|
|
$self->{inputs} = []; |
205
|
0
|
|
|
|
|
|
$self->{fetchrow_listeners} = []; |
206
|
0
|
|
|
|
|
|
$self->{reconcile_options} = |
207
|
|
|
|
|
|
|
{ |
208
|
|
|
|
|
|
|
add_output_columns => 1, |
209
|
|
|
|
|
|
|
realias_raw_sql => 1, |
210
|
|
|
|
|
|
|
}; |
211
|
|
|
|
|
|
|
|
212
|
0
|
|
|
|
|
|
$self->__set_unique_alias_prefix(); |
213
|
0
|
|
|
|
|
|
$self->table_alias_counter(0); |
214
|
0
|
|
|
|
|
|
$self->column_alias_counter(0); |
215
|
0
|
|
|
|
|
|
$self->capture_origin(); |
216
|
|
|
|
|
|
|
|
217
|
0
|
|
|
|
|
|
return $self; |
218
|
|
|
|
|
|
|
} |
219
|
|
|
|
|
|
|
|
220
|
|
|
|
|
|
|
# Internal |
221
|
|
|
|
|
|
|
__PACKAGE__->mk_accessors(qw(unique_alias_prefix)); |
222
|
|
|
|
|
|
|
sub __set_unique_alias_prefix { |
223
|
0
|
|
|
0
|
|
|
my $self = shift; |
224
|
|
|
|
|
|
|
# Derive a unique prefix from the memory address of $self |
225
|
|
|
|
|
|
|
# using the last 4 digits of the address |
226
|
0
|
|
|
|
|
|
my ($address) = "$self" =~ /0x.+([a-f0-9]{4})\)$/; |
227
|
0
|
|
|
|
|
|
$self->unique_alias_prefix('_' . $address . '_'); |
228
|
|
|
|
|
|
|
} |
229
|
|
|
|
|
|
|
|
230
|
|
|
|
|
|
|
=head1 ACCESSORS AND MUTATORS |
231
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
=cut |
233
|
|
|
|
|
|
|
|
234
|
|
|
|
|
|
|
=head2 $sql->add_input($col, $param); |
235
|
|
|
|
|
|
|
|
236
|
|
|
|
|
|
|
Adds an input column to the statement. Valid only for |
237
|
|
|
|
|
|
|
insert and update operations. |
238
|
|
|
|
|
|
|
|
239
|
|
|
|
|
|
|
Arguments are the SQL::Column that should get the value stored to it, |
240
|
|
|
|
|
|
|
and the SQL::Param that will carry the value. |
241
|
|
|
|
|
|
|
|
242
|
|
|
|
|
|
|
=cut |
243
|
|
|
|
|
|
|
|
244
|
|
|
|
|
|
|
sub add_input { |
245
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
246
|
0
|
|
|
|
|
|
my $col = shift; |
247
|
0
|
|
|
|
|
|
my $param = shift; |
248
|
|
|
|
|
|
|
|
249
|
0
|
|
|
|
|
|
my %permitted = map {uc($_) => 1} qw(update insert); |
|
0
|
|
|
|
|
|
|
250
|
|
|
|
|
|
|
|
251
|
0
|
0
|
|
|
|
|
unless (exists $permitted{$self->operation}) { |
252
|
0
|
|
|
|
|
|
Class::ReluctantORM::Exception::Call::NotPermitted->croak('add_input is only permitted for UPDATE and INSERT operations'); |
253
|
|
|
|
|
|
|
} |
254
|
0
|
0
|
0
|
|
|
|
unless (blessed($col) && $col->isa('Class::ReluctantORM::SQL::Column')) { |
255
|
0
|
|
|
|
|
|
Class::ReluctantORM::Exception::Param::WrongType->croak(param => 'column', expected => 'Class::ReluctantORM::SQL::Column'); |
256
|
|
|
|
|
|
|
} |
257
|
|
|
|
|
|
|
|
258
|
0
|
0
|
|
|
|
|
unless ($self->input_subquery) { |
259
|
0
|
0
|
0
|
|
|
|
unless (blessed($param) && $param->isa('Class::ReluctantORM::SQL::Param')) { |
260
|
0
|
|
|
|
|
|
Class::ReluctantORM::Exception::Param::WrongType->croak(param => 'param', expected => 'Class::ReluctantORM::SQL::Param'); |
261
|
|
|
|
|
|
|
} |
262
|
|
|
|
|
|
|
} |
263
|
|
|
|
|
|
|
|
264
|
0
|
|
|
|
|
|
push @{$self->{inputs}}, {column => $col, param => $param}; |
|
0
|
|
|
|
|
|
|
265
|
|
|
|
|
|
|
|
266
|
0
|
|
|
|
|
|
return 1; |
267
|
|
|
|
|
|
|
|
268
|
|
|
|
|
|
|
} |
269
|
|
|
|
|
|
|
|
270
|
|
|
|
|
|
|
=head2 $oc = $sql->add_output($output_column); |
271
|
|
|
|
|
|
|
|
272
|
|
|
|
|
|
|
=head2 $oc = $sql->add_output($column); |
273
|
|
|
|
|
|
|
|
274
|
|
|
|
|
|
|
=head2 $oc = $sql->add_output($expression); |
275
|
|
|
|
|
|
|
|
276
|
|
|
|
|
|
|
Adds an output column to the statement. Valid only for |
277
|
|
|
|
|
|
|
insert, select and update operations. |
278
|
|
|
|
|
|
|
|
279
|
|
|
|
|
|
|
In the first form, an OutputColumn you have constructed is added to the list directly. |
280
|
|
|
|
|
|
|
|
281
|
|
|
|
|
|
|
In the second and third forms, the argument is first wrapped in a new OutputColumn object, then added. Note that a Column is a subclass of Expression, so this is really the same usage. |
282
|
|
|
|
|
|
|
|
283
|
|
|
|
|
|
|
The (possibly new) OutputColumn is returned. |
284
|
|
|
|
|
|
|
|
285
|
|
|
|
|
|
|
=cut |
286
|
|
|
|
|
|
|
|
287
|
|
|
|
|
|
|
sub add_output { |
288
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
289
|
0
|
|
|
|
|
|
my $oc = shift; |
290
|
|
|
|
|
|
|
|
291
|
0
|
0
|
0
|
|
|
|
if (blessed($oc) && $oc->isa(Expression)) { |
|
|
0
|
0
|
|
|
|
|
292
|
0
|
|
|
|
|
|
$oc = OutputColumn->new($oc); |
293
|
|
|
|
|
|
|
} elsif (!(blessed($oc) && $oc->isa(OutputColumn))) { |
294
|
0
|
|
|
|
|
|
Class::ReluctantORM::Exception::Param::WrongType->croak(param => 'expression', expected => Expression, error => "need an Expression or a OutputColumn object"); |
295
|
|
|
|
|
|
|
} |
296
|
|
|
|
|
|
|
|
297
|
0
|
|
|
|
|
|
my %permitted = map {uc($_) => 1} qw(update insert select); |
|
0
|
|
|
|
|
|
|
298
|
|
|
|
|
|
|
|
299
|
0
|
0
|
|
|
|
|
unless (exists $permitted{$self->operation}) { |
300
|
0
|
|
|
|
|
|
Class::ReluctantORM::Exception::Call::NotPermitted->croak('add_output is only permitted for SELECT, UPDATE and INSERT operations'); |
301
|
|
|
|
|
|
|
} |
302
|
0
|
|
|
|
|
|
push @{$self->{outputs}}, $oc; |
|
0
|
|
|
|
|
|
|
303
|
|
|
|
|
|
|
|
304
|
0
|
|
|
|
|
|
return $oc; |
305
|
|
|
|
|
|
|
} |
306
|
|
|
|
|
|
|
|
307
|
|
|
|
|
|
|
=head2 $sql->remove_all_outputs(); |
308
|
|
|
|
|
|
|
|
309
|
|
|
|
|
|
|
Removes all output columns from the SQL statement. |
310
|
|
|
|
|
|
|
|
311
|
|
|
|
|
|
|
=cut |
312
|
|
|
|
|
|
|
|
313
|
|
|
|
|
|
|
sub remove_all_outputs { |
314
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
315
|
0
|
|
|
|
|
|
$self->{outputs} = []; |
316
|
|
|
|
|
|
|
} |
317
|
|
|
|
|
|
|
|
318
|
|
|
|
|
|
|
# Internal |
319
|
|
|
|
|
|
|
__PACKAGE__->mk_accessors(qw(table_alias_counter)); |
320
|
|
|
|
|
|
|
|
321
|
|
|
|
|
|
|
# Internal SQL-to-Driver linkage |
322
|
|
|
|
|
|
|
__PACKAGE__->mk_accessors(qw(_sth _sql_string _execution_driver)); |
323
|
|
|
|
|
|
|
|
324
|
|
|
|
|
|
|
=head2 $str = $sql->new_table_alias(); |
325
|
|
|
|
|
|
|
|
326
|
|
|
|
|
|
|
Get a table alias that is certainly unique within this SQL statement, and probaby unique accross substatements (and superstatments, if you will). |
327
|
|
|
|
|
|
|
|
328
|
|
|
|
|
|
|
=cut |
329
|
|
|
|
|
|
|
|
330
|
|
|
|
|
|
|
sub new_table_alias { |
331
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
332
|
0
|
|
|
|
|
|
my $counter = $self->table_alias_counter($self->table_alias_counter() + 1); |
333
|
0
|
|
|
|
|
|
my $pfx = $self->unique_alias_prefix(); |
334
|
0
|
|
|
|
|
|
return 'tx' . $pfx . sprintf('%04d', $counter); |
335
|
|
|
|
|
|
|
} |
336
|
|
|
|
|
|
|
|
337
|
|
|
|
|
|
|
# Internal |
338
|
|
|
|
|
|
|
__PACKAGE__->mk_accessors(qw(column_alias_counter)); |
339
|
|
|
|
|
|
|
|
340
|
|
|
|
|
|
|
=head2 $str = $sql->column_table_alias(); |
341
|
|
|
|
|
|
|
|
342
|
|
|
|
|
|
|
Get a column alias that is certainly unique within this SQL statement, and probaby unique accross substatements (and superstatments, if you will). |
343
|
|
|
|
|
|
|
|
344
|
|
|
|
|
|
|
=cut |
345
|
|
|
|
|
|
|
|
346
|
|
|
|
|
|
|
sub new_column_alias { |
347
|
0
|
|
|
0
|
0
|
|
my $self = shift; |
348
|
0
|
|
|
|
|
|
my $counter = $self->column_alias_counter($self->column_alias_counter() + 1); |
349
|
0
|
|
|
|
|
|
my $pfx = $self->unique_alias_prefix(); |
350
|
0
|
|
|
|
|
|
return 'cx' . $pfx . sprintf('%04d', $counter); |
351
|
|
|
|
|
|
|
} |
352
|
|
|
|
|
|
|
|
353
|
|
|
|
|
|
|
|
354
|
|
|
|
|
|
|
=head2 @bindings = $sql->get_bind_values(); |
355
|
|
|
|
|
|
|
|
356
|
|
|
|
|
|
|
Returns an array of values bound to the |
357
|
|
|
|
|
|
|
parameters of the query, in query placeholder order. |
358
|
|
|
|
|
|
|
|
359
|
|
|
|
|
|
|
This will include input bindings first, followed by where clause bindings. |
360
|
|
|
|
|
|
|
|
361
|
|
|
|
|
|
|
=cut |
362
|
|
|
|
|
|
|
|
363
|
|
|
|
|
|
|
sub get_bind_values { |
364
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
365
|
0
|
|
|
|
|
|
my @binds = ( |
366
|
0
|
|
|
|
|
|
(map { $_->bind_value } $self->input_params), |
367
|
0
|
|
|
|
|
|
($self->raw_where ? map { $_->bind_value } $self->_raw_where_params : ()), |
368
|
0
|
0
|
|
|
|
|
($self->where ? map { $_->bind_value } $self->where->params : ()), |
|
|
0
|
|
|
|
|
|
369
|
|
|
|
|
|
|
); |
370
|
0
|
|
|
|
|
|
return @binds; |
371
|
|
|
|
|
|
|
} |
372
|
|
|
|
|
|
|
|
373
|
|
|
|
|
|
|
sub params { |
374
|
0
|
|
|
0
|
0
|
|
my $self = shift; |
375
|
0
|
0
|
|
|
|
|
my @params = ( |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
376
|
|
|
|
|
|
|
$self->input_params, |
377
|
|
|
|
|
|
|
($self->input_subquery ? $self->input_subquery->params : ()), |
378
|
|
|
|
|
|
|
($self->raw_where ? $self->_raw_where_params : ()), |
379
|
|
|
|
|
|
|
($self->where ? $self->where->params : ()), |
380
|
|
|
|
|
|
|
); |
381
|
0
|
|
|
|
|
|
return @params; |
382
|
|
|
|
|
|
|
} |
383
|
|
|
|
|
|
|
|
384
|
|
|
|
|
|
|
=head2 $q = $sql->input_subquery(); |
385
|
|
|
|
|
|
|
|
386
|
|
|
|
|
|
|
=head2 $sql->input_subquery($subquery); |
387
|
|
|
|
|
|
|
|
388
|
|
|
|
|
|
|
|
389
|
|
|
|
|
|
|
Applicable only to INSERT statements. Sets a SubQuery to use as the source for INSERT ... SELECT statements. |
390
|
|
|
|
|
|
|
|
391
|
|
|
|
|
|
|
=cut |
392
|
|
|
|
|
|
|
|
393
|
|
|
|
|
|
|
sub input_subquery { |
394
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
395
|
0
|
0
|
|
|
|
|
if (@_) { |
396
|
0
|
|
|
|
|
|
my $sq = shift; |
397
|
0
|
0
|
0
|
|
|
|
unless (blessed($sq) && $sq->isa(SubQuery)) { |
398
|
0
|
|
|
|
|
|
Class::ReluctantORM::Exception::Param::WrongType->croak(param => 'subquery', expected => SubQuery, value => $sq); |
399
|
|
|
|
|
|
|
} |
400
|
0
|
0
|
|
|
|
|
unless ($self->operation() eq 'INSERT') { |
401
|
0
|
|
|
|
|
|
Class::ReluctantORM::Exception::Call::NotPermitted->croak('You may only set an input_subquery on an INSERT statment. This is a ' . $self->operation . " statement."); |
402
|
|
|
|
|
|
|
} |
403
|
0
|
|
|
|
|
|
$self->set('input_subquery', $sq); |
404
|
|
|
|
|
|
|
} |
405
|
0
|
|
|
|
|
|
return $self->get('input_subquery'); |
406
|
|
|
|
|
|
|
|
407
|
|
|
|
|
|
|
} |
408
|
|
|
|
|
|
|
|
409
|
|
|
|
|
|
|
=head2 $sql->set_bind_values($val1, $val2,...); |
410
|
|
|
|
|
|
|
|
411
|
|
|
|
|
|
|
Binds the given values to the parameters in the where clause. |
412
|
|
|
|
|
|
|
|
413
|
|
|
|
|
|
|
=cut |
414
|
|
|
|
|
|
|
|
415
|
|
|
|
|
|
|
sub set_bind_values { |
416
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
417
|
0
|
|
|
|
|
|
my @vals = @_; |
418
|
0
|
|
|
|
|
|
my @params = $self->params(); |
419
|
0
|
0
|
|
|
|
|
if (@vals < @params) { |
|
|
0
|
|
|
|
|
|
420
|
0
|
|
|
|
|
|
Class::ReluctantORM::Exception::Param::Missing->croak('The number of values must match the number of parameters in the where clause.'); |
421
|
|
|
|
|
|
|
} elsif (@vals > @params) { |
422
|
0
|
|
|
|
|
|
Class::ReluctantORM::Exception::Param::Spurious->croak('The number of values must match the number of parameters in the where clause.'); |
423
|
|
|
|
|
|
|
} |
424
|
0
|
|
|
|
|
|
for my $i (0..(@params - 1)) { |
425
|
0
|
|
|
|
|
|
$params[$i]->bind_value($vals[$i]); |
426
|
|
|
|
|
|
|
} |
427
|
|
|
|
|
|
|
} |
428
|
|
|
|
|
|
|
|
429
|
|
|
|
|
|
|
|
430
|
|
|
|
|
|
|
=head2 $from = $sql->from(); |
431
|
|
|
|
|
|
|
|
432
|
|
|
|
|
|
|
=head2 $sql->from($sql_FROM_object); |
433
|
|
|
|
|
|
|
|
434
|
|
|
|
|
|
|
Gets or sets the FROM clause of the query. The argument is a |
435
|
|
|
|
|
|
|
Class::ReluctantORM::SQL::From . |
436
|
|
|
|
|
|
|
|
437
|
|
|
|
|
|
|
=cut |
438
|
|
|
|
|
|
|
|
439
|
|
|
|
|
|
|
sub from { |
440
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
441
|
0
|
0
|
|
|
|
|
if (@_) { |
442
|
0
|
|
|
|
|
|
my $thing = shift; |
443
|
0
|
0
|
|
|
|
|
if (!ref($thing)) { |
|
|
0
|
|
|
|
|
|
444
|
|
|
|
|
|
|
# Setting raw_from via from() - kinda sloppy |
445
|
0
|
|
|
|
|
|
$self->raw_from($thing); |
446
|
|
|
|
|
|
|
} elsif ($thing->isa(From)) { |
447
|
|
|
|
|
|
|
# Clear raw_from |
448
|
0
|
|
|
|
|
|
$self->raw_from(undef); |
449
|
0
|
|
|
|
|
|
$self->set('from', $thing); |
450
|
|
|
|
|
|
|
} else { |
451
|
0
|
|
|
|
|
|
Class::ReluctantORM::Exception::Param::WrongType->croak |
452
|
|
|
|
|
|
|
( |
453
|
|
|
|
|
|
|
param => 'from', |
454
|
|
|
|
|
|
|
expected => From . ' or raw SQL string', |
455
|
|
|
|
|
|
|
value => $thing, |
456
|
|
|
|
|
|
|
); |
457
|
|
|
|
|
|
|
} |
458
|
|
|
|
|
|
|
} |
459
|
0
|
|
|
|
|
|
return $self->get('from'); |
460
|
|
|
|
|
|
|
} |
461
|
|
|
|
|
|
|
|
462
|
|
|
|
|
|
|
=begin vaporware |
463
|
|
|
|
|
|
|
|
464
|
|
|
|
|
|
|
=head2 $str = $sql->raw_from(); |
465
|
|
|
|
|
|
|
|
466
|
|
|
|
|
|
|
=head2 $sql->raw_from(); |
467
|
|
|
|
|
|
|
|
468
|
|
|
|
|
|
|
If you choose not to (or are unable to) use the From object to represent your FROM clause, you can use this facility to pass in a raw SQL string that will be used as the from clause. |
469
|
|
|
|
|
|
|
|
470
|
|
|
|
|
|
|
It will not pass through unmolested - see Class::ReluctantORM::Driver - Raw SQL Mangling . |
471
|
|
|
|
|
|
|
|
472
|
|
|
|
|
|
|
=cut |
473
|
|
|
|
|
|
|
|
474
|
|
|
|
|
|
|
sub raw_from { |
475
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
476
|
0
|
0
|
|
|
|
|
if (@_) { |
477
|
0
|
|
|
|
|
|
my $thing = shift(); |
478
|
0
|
0
|
|
|
|
|
if (!defined($thing)) { |
|
|
0
|
|
|
|
|
|
479
|
|
|
|
|
|
|
# OK, clearing |
480
|
0
|
|
|
|
|
|
$self->set('raw_from', undef); |
481
|
|
|
|
|
|
|
} elsif (!ref($thing)) { |
482
|
0
|
|
|
|
|
|
$self->set('from', undef); |
483
|
0
|
|
|
|
|
|
$self->set('raw_from', $thing); |
484
|
|
|
|
|
|
|
} else { |
485
|
0
|
|
|
|
|
|
Class::ReluctantORM::Exception::Param::WrongType->croak |
486
|
|
|
|
|
|
|
( |
487
|
|
|
|
|
|
|
param => 'raw_from', |
488
|
|
|
|
|
|
|
expected => 'raw SQL string', |
489
|
|
|
|
|
|
|
value => $thing, |
490
|
|
|
|
|
|
|
); |
491
|
|
|
|
|
|
|
} |
492
|
|
|
|
|
|
|
} |
493
|
0
|
|
|
|
|
|
return $self->get('raw_from'); |
494
|
|
|
|
|
|
|
} |
495
|
|
|
|
|
|
|
|
496
|
|
|
|
|
|
|
|
497
|
|
|
|
|
|
|
|
498
|
|
|
|
|
|
|
=head2 @pairs = $sql->inputs(); |
499
|
|
|
|
|
|
|
|
500
|
|
|
|
|
|
|
Returns the list of inputs as an array of hashrefs. Each hashref has keys 'column' and 'param'. |
501
|
|
|
|
|
|
|
|
502
|
|
|
|
|
|
|
Only valid for INSERT and UPDATE statements. |
503
|
|
|
|
|
|
|
|
504
|
|
|
|
|
|
|
=cut |
505
|
|
|
|
|
|
|
|
506
|
|
|
|
|
|
|
sub inputs { |
507
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
508
|
0
|
0
|
0
|
|
|
|
unless ($self->operation eq 'INSERT' || $self->operation eq 'UPDATE' ) { Class::ReluctantORM::Exception::Call::NotPermitted->croak('May only call inputs() on an INSERT or UPDATE statement. Use input_params instead.'); } |
|
0
|
|
|
|
|
|
|
509
|
0
|
0
|
|
|
|
|
return @{$self->{inputs} || []}; |
|
0
|
|
|
|
|
|
|
510
|
|
|
|
|
|
|
} |
511
|
|
|
|
|
|
|
|
512
|
|
|
|
|
|
|
=head2 @params = $sql->input_params(); |
513
|
|
|
|
|
|
|
|
514
|
|
|
|
|
|
|
Returns the list of input params as an array. |
515
|
|
|
|
|
|
|
|
516
|
|
|
|
|
|
|
To get where clause params, call $sql->where->params(); |
517
|
|
|
|
|
|
|
|
518
|
|
|
|
|
|
|
=cut |
519
|
|
|
|
|
|
|
|
520
|
|
|
|
|
|
|
sub input_params { |
521
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
522
|
0
|
0
|
0
|
|
|
|
if ($self->operation eq 'INSERT' || $self->operation eq 'UPDATE') { |
523
|
0
|
0
|
|
|
|
|
if ($self->input_subquery) { |
524
|
0
|
|
|
|
|
|
return $self->input_subquery->statement->params(); |
525
|
|
|
|
|
|
|
} else { |
526
|
0
|
|
|
|
|
|
return map { $_->{param} } $self->inputs; |
|
0
|
|
|
|
|
|
|
527
|
|
|
|
|
|
|
} |
528
|
|
|
|
|
|
|
} else { |
529
|
0
|
|
|
|
|
|
return (); |
530
|
|
|
|
|
|
|
} |
531
|
|
|
|
|
|
|
} |
532
|
|
|
|
|
|
|
|
533
|
|
|
|
|
|
|
|
534
|
|
|
|
|
|
|
=head2 $int = $sql->limit(); |
535
|
|
|
|
|
|
|
|
536
|
|
|
|
|
|
|
=head2 $sql->limit($int); |
537
|
|
|
|
|
|
|
|
538
|
|
|
|
|
|
|
=head2 $sql->limit(undef); |
539
|
|
|
|
|
|
|
|
540
|
|
|
|
|
|
|
Reads, sets, or clears the LIMIT clause of the statement. |
541
|
|
|
|
|
|
|
|
542
|
|
|
|
|
|
|
=cut |
543
|
|
|
|
|
|
|
|
544
|
|
|
|
|
|
|
__PACKAGE__->mk_accessors(qw(limit)); |
545
|
|
|
|
|
|
|
|
546
|
|
|
|
|
|
|
=head2 $int = $sql->offset(); |
547
|
|
|
|
|
|
|
|
548
|
|
|
|
|
|
|
=head2 $sql->offset($int); |
549
|
|
|
|
|
|
|
|
550
|
|
|
|
|
|
|
=head2 $sql->offset(undef); |
551
|
|
|
|
|
|
|
|
552
|
|
|
|
|
|
|
Reads, sets, or clears the OFFSET clause of the statement. |
553
|
|
|
|
|
|
|
|
554
|
|
|
|
|
|
|
=cut |
555
|
|
|
|
|
|
|
|
556
|
|
|
|
|
|
|
__PACKAGE__->mk_accessors(qw(offset)); |
557
|
|
|
|
|
|
|
|
558
|
|
|
|
|
|
|
|
559
|
|
|
|
|
|
|
|
560
|
|
|
|
|
|
|
|
561
|
|
|
|
|
|
|
=head2 $op = $sql->operation(); |
562
|
|
|
|
|
|
|
|
563
|
|
|
|
|
|
|
Reads the operation (command) of the SQL statement. Result |
564
|
|
|
|
|
|
|
will be one of INSERT, DELETE, SELECT, or UPDATE. |
565
|
|
|
|
|
|
|
|
566
|
|
|
|
|
|
|
=cut |
567
|
|
|
|
|
|
|
|
568
|
|
|
|
|
|
|
sub operation { |
569
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
570
|
0
|
0
|
|
|
|
|
if (@_) { Class::ReluctantORM::Exception::Call::NotMutator->croak(); } |
|
0
|
|
|
|
|
|
|
571
|
0
|
|
|
|
|
|
return $self->get('operation'); |
572
|
|
|
|
|
|
|
} |
573
|
|
|
|
|
|
|
|
574
|
|
|
|
|
|
|
=head2 $where = $sql->order_by(); |
575
|
|
|
|
|
|
|
|
576
|
|
|
|
|
|
|
=head2 $sql->order_by($order); |
577
|
|
|
|
|
|
|
|
578
|
|
|
|
|
|
|
Sets the optional ORDER BY clause of the query. The argument is a |
579
|
|
|
|
|
|
|
Class::ReluctantORM::SQL::OrderBy . |
580
|
|
|
|
|
|
|
|
581
|
|
|
|
|
|
|
=cut |
582
|
|
|
|
|
|
|
|
583
|
|
|
|
|
|
|
sub order_by { |
584
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
585
|
0
|
0
|
|
|
|
|
if (@_) { |
586
|
0
|
|
|
|
|
|
$self->set('order_by', shift); |
587
|
|
|
|
|
|
|
} |
588
|
0
|
|
|
|
|
|
my $ob = $self->get('order_by'); |
589
|
0
|
0
|
|
|
|
|
unless ($ob) { |
590
|
0
|
|
|
|
|
|
$ob = OrderBy->new(); |
591
|
0
|
|
|
|
|
|
$self->set('order_by', $ob); |
592
|
|
|
|
|
|
|
} |
593
|
|
|
|
|
|
|
|
594
|
0
|
|
|
|
|
|
return $ob; |
595
|
|
|
|
|
|
|
} |
596
|
|
|
|
|
|
|
|
597
|
|
|
|
|
|
|
|
598
|
|
|
|
|
|
|
|
599
|
|
|
|
|
|
|
=head2 @cols = $sql->output_columns(); |
600
|
|
|
|
|
|
|
|
601
|
|
|
|
|
|
|
Returns the list of output columns as OutputColumns. |
602
|
|
|
|
|
|
|
|
603
|
|
|
|
|
|
|
=cut |
604
|
|
|
|
|
|
|
|
605
|
|
|
|
|
|
|
sub output_columns { |
606
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
607
|
0
|
|
|
|
|
|
return @{$self->{outputs}}; |
|
0
|
|
|
|
|
|
|
608
|
|
|
|
|
|
|
} |
609
|
|
|
|
|
|
|
|
610
|
|
|
|
|
|
|
=head2 $table = $sql->table(); |
611
|
|
|
|
|
|
|
|
612
|
|
|
|
|
|
|
=head2 $sql->table($table); |
613
|
|
|
|
|
|
|
|
614
|
|
|
|
|
|
|
Reads or sets the target table for use with INSERT, UPDATE, and DELETE queries. |
615
|
|
|
|
|
|
|
It is invalid to call this on a SELECT query (use from() to set a From clause, instead). |
616
|
|
|
|
|
|
|
|
617
|
|
|
|
|
|
|
=cut |
618
|
|
|
|
|
|
|
|
619
|
|
|
|
|
|
|
sub table { |
620
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
621
|
0
|
0
|
|
|
|
|
if ($self->operation eq 'SELECT') { Class::ReluctantORM::Exception::Call::NotPermitted->croak('Do not call table() on a SELECT query. Use tables() to read tables and from() to set a from clause.'); } |
|
0
|
|
|
|
|
|
|
622
|
0
|
0
|
|
|
|
|
if (@_) { |
623
|
0
|
|
|
|
|
|
my $t = shift; |
624
|
0
|
0
|
0
|
|
|
|
unless (blessed($t) && $t->isa(Table)) { Class::ReluctantORM::Exception::Param::WrongType->croak(expected => Table, value => $t); } |
|
0
|
|
|
|
|
|
|
625
|
0
|
|
|
|
|
|
$self->set('table', $t); |
626
|
|
|
|
|
|
|
} |
627
|
0
|
|
|
|
|
|
return $self->get('table'); |
628
|
|
|
|
|
|
|
} |
629
|
|
|
|
|
|
|
|
630
|
|
|
|
|
|
|
=head2 $table = $sql->base_table(); |
631
|
|
|
|
|
|
|
|
632
|
|
|
|
|
|
|
=cut |
633
|
|
|
|
|
|
|
|
634
|
|
|
|
|
|
|
sub base_table { |
635
|
0
|
|
|
0
|
1
|
|
my $sql = shift; |
636
|
0
|
0
|
|
|
|
|
if (@_) { |
637
|
0
|
|
|
|
|
|
Class::ReluctantORM::Exception::Call::NotMutator->croak(); |
638
|
|
|
|
|
|
|
} |
639
|
0
|
0
|
|
|
|
|
if ($sql->operation() eq 'SELECT') { |
640
|
0
|
|
|
|
|
|
return $sql->from()->root_relation()->leftmost_table(); |
641
|
|
|
|
|
|
|
} else { |
642
|
0
|
|
|
|
|
|
return $sql->table(); |
643
|
|
|
|
|
|
|
} |
644
|
|
|
|
|
|
|
} |
645
|
|
|
|
|
|
|
|
646
|
|
|
|
|
|
|
=head2 @tables = $sql->tables(%opts); |
647
|
|
|
|
|
|
|
|
648
|
|
|
|
|
|
|
Returns an array of all tables involved in the query, both from the from clause and the where clause. |
649
|
|
|
|
|
|
|
|
650
|
|
|
|
|
|
|
Supported options: |
651
|
|
|
|
|
|
|
|
652
|
|
|
|
|
|
|
=over |
653
|
|
|
|
|
|
|
|
654
|
|
|
|
|
|
|
=item exclude_subqueries |
655
|
|
|
|
|
|
|
|
656
|
|
|
|
|
|
|
Optional boolean, default false. If true, tables mentioned only in subqueries will not be included. |
657
|
|
|
|
|
|
|
|
658
|
|
|
|
|
|
|
=back |
659
|
|
|
|
|
|
|
|
660
|
|
|
|
|
|
|
=cut |
661
|
|
|
|
|
|
|
|
662
|
|
|
|
|
|
|
sub tables { |
663
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
664
|
0
|
|
|
|
|
|
my %opts = check_args(args => \@_, optional => [qw(exclude_subqueries)]); |
665
|
|
|
|
|
|
|
|
666
|
0
|
|
|
|
|
|
my @from_tables; |
667
|
0
|
0
|
|
|
|
|
if ($self->operation eq 'SELECT') { |
668
|
0
|
0
|
|
|
|
|
unless ($self->from) { |
669
|
0
|
|
|
|
|
|
Class::ReluctantORM::Exception::Call::NotPermitted->croak('For SELECT statements, you must set the FROM clause using from() before calling tables().'); |
670
|
|
|
|
|
|
|
} |
671
|
0
|
0
|
|
|
|
|
@from_tables = $self->from ? $self->from->tables(%opts) : (); |
672
|
|
|
|
|
|
|
} else { |
673
|
0
|
0
|
|
|
|
|
@from_tables = $self->table ? ($self->table()) : (); |
674
|
|
|
|
|
|
|
} |
675
|
|
|
|
|
|
|
|
676
|
0
|
0
|
|
|
|
|
my @where_tables = $self->where ? $self->where->tables(%opts) : (); |
677
|
|
|
|
|
|
|
|
678
|
|
|
|
|
|
|
# Unique-ify this list using their memory addresses |
679
|
0
|
|
|
|
|
|
my %tables = map {('' . $_ . '') => $_ } (@from_tables, @where_tables); |
|
0
|
|
|
|
|
|
|
680
|
0
|
|
|
|
|
|
return values %tables; |
681
|
|
|
|
|
|
|
} |
682
|
|
|
|
|
|
|
|
683
|
|
|
|
|
|
|
|
684
|
|
|
|
|
|
|
=head2 $where = $sql->where(); |
685
|
|
|
|
|
|
|
|
686
|
|
|
|
|
|
|
=head2 $sql->where($sql_where); |
687
|
|
|
|
|
|
|
|
688
|
|
|
|
|
|
|
Reads or sets the WHERE clause of the query. The argument is a |
689
|
|
|
|
|
|
|
Class::ReluctantORM::SQL::Where . |
690
|
|
|
|
|
|
|
|
691
|
|
|
|
|
|
|
=cut |
692
|
|
|
|
|
|
|
|
693
|
|
|
|
|
|
|
sub where { |
694
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
695
|
0
|
0
|
|
|
|
|
if (@_) { |
696
|
0
|
|
|
|
|
|
my $thing = shift; |
697
|
0
|
0
|
|
|
|
|
if (!ref($thing)) { |
|
|
0
|
|
|
|
|
|
698
|
|
|
|
|
|
|
# Setting raw_where via where() - kinda sloppy |
699
|
0
|
|
|
|
|
|
$self->raw_where($thing); |
700
|
|
|
|
|
|
|
} elsif ($thing->isa(Where)) { |
701
|
|
|
|
|
|
|
# Clear raw_where |
702
|
0
|
|
|
|
|
|
$self->raw_where(undef); |
703
|
0
|
|
|
|
|
|
$self->set('where', $thing); |
704
|
|
|
|
|
|
|
} else { |
705
|
0
|
|
|
|
|
|
Class::ReluctantORM::Exception::Param::WrongType->croak |
706
|
|
|
|
|
|
|
( |
707
|
|
|
|
|
|
|
param => 'where', |
708
|
|
|
|
|
|
|
expected => Where . ' or raw SQL string', |
709
|
|
|
|
|
|
|
value => $thing, |
710
|
|
|
|
|
|
|
); |
711
|
|
|
|
|
|
|
} |
712
|
|
|
|
|
|
|
} |
713
|
0
|
|
|
|
|
|
return $self->get('where'); |
714
|
|
|
|
|
|
|
} |
715
|
|
|
|
|
|
|
|
716
|
|
|
|
|
|
|
=head2 $str = $sql->raw_where(); |
717
|
|
|
|
|
|
|
|
718
|
|
|
|
|
|
|
=head2 $sql->raw_where(); |
719
|
|
|
|
|
|
|
|
720
|
|
|
|
|
|
|
If you choose not to (or are unable to) use the Where object to represent your WHERE clause, you can use this facility to pass in a raw SQL string that will be used as the where clause. |
721
|
|
|
|
|
|
|
|
722
|
|
|
|
|
|
|
It will not pass through unmolested - see Class::ReluctantORM::Driver - Raw SQL Mangling . |
723
|
|
|
|
|
|
|
|
724
|
|
|
|
|
|
|
=cut |
725
|
|
|
|
|
|
|
|
726
|
|
|
|
|
|
|
sub raw_where { |
727
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
728
|
0
|
0
|
|
|
|
|
if (@_) { |
729
|
0
|
|
|
|
|
|
my $thing = shift(); |
730
|
0
|
0
|
|
|
|
|
if (!defined($thing)) { |
|
|
0
|
|
|
|
|
|
731
|
|
|
|
|
|
|
# OK, clearing |
732
|
0
|
|
|
|
|
|
$self->set('raw_where', undef); |
733
|
|
|
|
|
|
|
} elsif (!ref($thing)) { |
734
|
0
|
|
|
|
|
|
$self->set('where', undef); |
735
|
0
|
|
|
|
|
|
$self->__find_raw_where_params($thing); |
736
|
0
|
|
|
|
|
|
$self->set('raw_where', $thing); |
737
|
|
|
|
|
|
|
} else { |
738
|
0
|
|
|
|
|
|
Class::ReluctantORM::Exception::Param::WrongType->croak |
739
|
|
|
|
|
|
|
( |
740
|
|
|
|
|
|
|
param => 'raw_where', |
741
|
|
|
|
|
|
|
expected => 'raw SQL string', |
742
|
|
|
|
|
|
|
value => $thing, |
743
|
|
|
|
|
|
|
); |
744
|
|
|
|
|
|
|
} |
745
|
|
|
|
|
|
|
} |
746
|
0
|
|
|
|
|
|
return $self->get('raw_where'); |
747
|
|
|
|
|
|
|
} |
748
|
|
|
|
|
|
|
|
749
|
|
|
|
|
|
|
sub _raw_where_execargs { |
750
|
0
|
|
|
0
|
|
|
my $self = shift; |
751
|
0
|
0
|
|
|
|
|
if (@_) { |
752
|
0
|
|
|
|
|
|
$self->set('raw_where_execargs', shift); |
753
|
|
|
|
|
|
|
} |
754
|
0
|
|
|
|
|
|
return $self->get('raw_where_execargs'); |
755
|
|
|
|
|
|
|
} |
756
|
|
|
|
|
|
|
|
757
|
|
|
|
|
|
|
sub _raw_where_pristine { |
758
|
0
|
|
|
0
|
|
|
my $self = shift; |
759
|
0
|
0
|
|
|
|
|
if (@_) { |
760
|
0
|
|
|
|
|
|
$self->set_reconcile_option('realias_raw_sql', !shift); |
761
|
|
|
|
|
|
|
} |
762
|
|
|
|
|
|
|
} |
763
|
|
|
|
|
|
|
|
764
|
|
|
|
|
|
|
sub _cooked_where { |
765
|
0
|
|
|
0
|
|
|
my $self = shift; |
766
|
0
|
0
|
|
|
|
|
if (@_) { |
767
|
0
|
|
|
|
|
|
$self->set('cooked_where', shift); |
768
|
|
|
|
|
|
|
} |
769
|
0
|
|
|
|
|
|
return $self->get('cooked_where'); |
770
|
|
|
|
|
|
|
} |
771
|
|
|
|
|
|
|
|
772
|
|
|
|
|
|
|
sub __find_raw_where_params { |
773
|
0
|
|
|
0
|
|
|
my $self = shift; |
774
|
0
|
|
|
|
|
|
my $raw = shift; |
775
|
|
|
|
|
|
|
# TODO - check for ?'s in quoted strings more effectively |
776
|
0
|
|
|
|
|
|
while ($raw =~ s{'.*?'}{}g) { } # Crudely delete all quoted strings from the SQL |
777
|
0
|
|
|
|
|
|
my @params = map { Param->new() } $raw =~ m{(\?)}g; |
|
0
|
|
|
|
|
|
|
778
|
0
|
|
|
|
|
|
$self->_raw_where_params(\@params); |
779
|
|
|
|
|
|
|
} |
780
|
|
|
|
|
|
|
|
781
|
|
|
|
|
|
|
sub _raw_where_params { |
782
|
0
|
|
|
0
|
|
|
my $self = shift; |
783
|
0
|
0
|
|
|
|
|
if (@_) { |
784
|
0
|
|
|
|
|
|
$self->set('raw_where_params', shift); |
785
|
|
|
|
|
|
|
} |
786
|
0
|
0
|
|
|
|
|
return @{$self->get('raw_where_params') || []}; |
|
0
|
|
|
|
|
|
|
787
|
|
|
|
|
|
|
} |
788
|
|
|
|
|
|
|
|
789
|
|
|
|
|
|
|
|
790
|
|
|
|
|
|
|
|
791
|
|
|
|
|
|
|
#========================================================# |
792
|
|
|
|
|
|
|
# Inflation Support |
793
|
|
|
|
|
|
|
#========================================================# |
794
|
|
|
|
|
|
|
|
795
|
|
|
|
|
|
|
=head1 INFLATION SUPPORT |
796
|
|
|
|
|
|
|
|
797
|
|
|
|
|
|
|
These methods implement the ability to create CRO model objects from a SQL query object. |
798
|
|
|
|
|
|
|
|
799
|
|
|
|
|
|
|
=cut |
800
|
|
|
|
|
|
|
|
801
|
|
|
|
|
|
|
=head2 $bool = $sql->is_inflatable(%make_inflatable_opts); |
802
|
|
|
|
|
|
|
|
803
|
|
|
|
|
|
|
=head2 ($bool, $exception) = $sql->is_inflatable(%make_inflatable_opts); |
804
|
|
|
|
|
|
|
|
805
|
|
|
|
|
|
|
Analyzes the SQL statement and tries to determine if it |
806
|
|
|
|
|
|
|
can be successfully used to inflate CRO model objects after |
807
|
|
|
|
|
|
|
execution. Calls make_inflatable before performing the analysis, passing on any options. |
808
|
|
|
|
|
|
|
|
809
|
|
|
|
|
|
|
This captures any exception from the analysis, and optionally returns it in the second form. |
810
|
|
|
|
|
|
|
|
811
|
|
|
|
|
|
|
A false return from is_inflatable indicates that inflate() will certainly fail before executing. |
812
|
|
|
|
|
|
|
|
813
|
|
|
|
|
|
|
A true return indicates that inflate() will survive at least until execution |
814
|
|
|
|
|
|
|
(a runtime database error may still occur). |
815
|
|
|
|
|
|
|
|
816
|
|
|
|
|
|
|
=cut |
817
|
|
|
|
|
|
|
|
818
|
|
|
|
|
|
|
sub is_inflatable { |
819
|
0
|
|
|
0
|
1
|
|
my $sql = shift; |
820
|
0
|
|
|
|
|
|
my %args = check_args(args => \@_, optional => [qw(auto_annotate auto_reconcile add_output_columns)]); |
821
|
0
|
0
|
|
|
|
|
unless (defined($args{auto_annotate})) { $args{auto_annotate} = 1; } |
|
0
|
|
|
|
|
|
|
822
|
0
|
0
|
|
|
|
|
unless (defined($args{auto_reconcile})) { $args{auto_reconcile} = 1; } |
|
0
|
|
|
|
|
|
|
823
|
0
|
0
|
|
|
|
|
unless (defined($args{add_output_columns})) { $args{add_output_columns} = 1; } |
|
0
|
|
|
|
|
|
|
824
|
|
|
|
|
|
|
|
825
|
0
|
|
|
|
|
|
eval { |
826
|
0
|
|
|
|
|
|
$sql->make_inflatable(%args); |
827
|
|
|
|
|
|
|
}; |
828
|
0
|
0
|
|
|
|
|
if ($@) { |
829
|
0
|
0
|
|
|
|
|
return wantarray ? (0, $@) : 0; |
830
|
|
|
|
|
|
|
} |
831
|
|
|
|
|
|
|
|
832
|
|
|
|
|
|
|
# Inflatability checks |
833
|
0
|
|
|
|
|
|
my @checks = |
834
|
|
|
|
|
|
|
( |
835
|
|
|
|
|
|
|
'__is_inflatable_find_base_class', |
836
|
|
|
|
|
|
|
'__is_inflatable_has_output_columns', |
837
|
|
|
|
|
|
|
'__is_inflatable_all_non_join_tables_are_in_relationships', |
838
|
|
|
|
|
|
|
'__is_inflatable_all_joins_have_relationships', |
839
|
|
|
|
|
|
|
'__is_inflatable_essential_output_columns_present_and_reconciled', |
840
|
|
|
|
|
|
|
); |
841
|
|
|
|
|
|
|
|
842
|
0
|
|
|
|
|
|
my $inflatable = 1; |
843
|
0
|
|
|
|
|
|
my $exception = undef; |
844
|
0
|
|
|
|
|
|
foreach my $check (@checks) { |
845
|
0
|
0
|
|
|
|
|
if ($inflatable) { |
846
|
0
|
|
|
|
|
|
my $check_result = 1; |
847
|
0
|
|
|
|
|
|
($check_result, $exception) = $sql->$check; |
848
|
0
|
|
0
|
|
|
|
$inflatable &&= $check_result; |
849
|
|
|
|
|
|
|
} |
850
|
|
|
|
|
|
|
} |
851
|
|
|
|
|
|
|
|
852
|
0
|
0
|
|
|
|
|
return wantarray ? ($inflatable, $exception) : $inflatable; |
853
|
|
|
|
|
|
|
} |
854
|
|
|
|
|
|
|
|
855
|
|
|
|
|
|
|
|
856
|
|
|
|
|
|
|
sub __is_inflatable_find_base_class { |
857
|
0
|
|
|
0
|
|
|
my $sql = shift; |
858
|
0
|
|
|
|
|
|
my $base_table = $sql->base_table(); |
859
|
0
|
0
|
|
|
|
|
return $base_table->class() ? (1, undef) : (0, Class::ReluctantORM::Exception::SQL::NotInflatable->new(error => 'Base table does not have a class associated with it', sql => $sql)); |
860
|
|
|
|
|
|
|
} |
861
|
|
|
|
|
|
|
|
862
|
|
|
|
|
|
|
sub __is_inflatable_has_output_columns { |
863
|
0
|
|
|
0
|
|
|
my $sql = shift; |
864
|
0
|
0
|
|
|
|
|
return (scalar $sql->output_columns) ? (1, undef) : (0, Class::ReluctantORM::Exception::SQL::NotInflatable->new(error => 'SQL object has no output columns', sql => $sql)); |
865
|
|
|
|
|
|
|
} |
866
|
|
|
|
|
|
|
|
867
|
|
|
|
|
|
|
sub __is_inflatable_essential_output_columns_present_and_reconciled { |
868
|
0
|
|
|
0
|
|
|
my $sql = shift; |
869
|
|
|
|
|
|
|
|
870
|
0
|
|
|
|
|
|
my $ok = 1; |
871
|
0
|
|
|
|
|
|
my $check = 1; |
872
|
0
|
|
|
|
|
|
my $exception = undef; |
873
|
|
|
|
|
|
|
|
874
|
0
|
|
|
|
|
|
my %cache = |
875
|
0
|
|
|
|
|
|
map { __is_inflatable_EOCPAR_column_name($_) => $_} |
876
|
|
|
|
|
|
|
$sql->output_columns(); |
877
|
|
|
|
|
|
|
|
878
|
|
|
|
|
|
|
# Check the base table |
879
|
0
|
|
|
|
|
|
my $base = $sql->base_table(); |
880
|
0
|
|
|
|
|
|
($check, $exception) = __is_inflatable_EOCPAR_columns_present_for_table($base, \%cache); |
881
|
0
|
|
0
|
|
|
|
$ok &&= $check; |
882
|
|
|
|
|
|
|
|
883
|
|
|
|
|
|
|
# Check all relationships |
884
|
0
|
0
|
|
|
|
|
if ($sql->from()) { |
885
|
0
|
|
|
|
|
|
foreach my $rel ($sql->from->relationships()) { |
886
|
0
|
0
|
|
|
|
|
last unless $ok; |
887
|
0
|
|
|
|
|
|
my @tables = ($rel->local_sql_table(), $rel->remote_sql_table()); |
888
|
|
|
|
|
|
|
|
889
|
0
|
|
|
|
|
|
foreach my $table (@tables) { |
890
|
0
|
0
|
|
|
|
|
next unless $ok; |
891
|
0
|
0
|
|
|
|
|
next unless $table; |
892
|
|
|
|
|
|
|
# May seem odd, but it's actually OK for a relationship to be |
893
|
|
|
|
|
|
|
# present while missing the local or remote table IFF the relationship has a join depth > 1 |
894
|
|
|
|
|
|
|
# (relied on by HasManyMany->fetch_all()) |
895
|
0
|
0
|
0
|
|
|
|
next if ($rel->join_depth > 1 && !grep { $_->is_the_same_table($table) } $sql->tables(exclude_subqueries => 1)); |
|
0
|
|
|
|
|
|
|
896
|
|
|
|
|
|
|
|
897
|
0
|
|
|
|
|
|
($check, $exception) = __is_inflatable_EOCPAR_columns_present_for_table($table, \%cache); |
898
|
0
|
|
0
|
|
|
|
$ok &&= $check; |
899
|
|
|
|
|
|
|
} |
900
|
|
|
|
|
|
|
} |
901
|
|
|
|
|
|
|
} |
902
|
|
|
|
|
|
|
|
903
|
0
|
0
|
|
|
|
|
return (($ok ? 1 : 0), $exception); |
904
|
|
|
|
|
|
|
} |
905
|
|
|
|
|
|
|
|
906
|
|
|
|
|
|
|
sub __is_inflatable_EOCPAR_column_name { |
907
|
0
|
|
|
0
|
|
|
my $oc = shift; |
908
|
0
|
0
|
|
|
|
|
if ($oc->expression->is_column()) { |
909
|
0
|
|
|
|
|
|
my $col = $oc->expression(); |
910
|
0
|
0
|
|
|
|
|
if ($col->table) { |
911
|
0
|
0
|
|
|
|
|
if ($col->table->schema) { |
912
|
0
|
|
|
|
|
|
return $col->table->schema . '.' . $col->table->table . '.' . $col->column; |
913
|
|
|
|
|
|
|
} else { |
914
|
0
|
|
|
|
|
|
return '(unknown schema).' . $col->table->table . '.' . $col->column; |
915
|
|
|
|
|
|
|
} |
916
|
|
|
|
|
|
|
|
917
|
|
|
|
|
|
|
} else { |
918
|
0
|
|
|
|
|
|
return '(unknown table).' . $col->column(); |
919
|
|
|
|
|
|
|
} |
920
|
|
|
|
|
|
|
} else { |
921
|
0
|
|
|
|
|
|
return '(expression)'; |
922
|
|
|
|
|
|
|
} |
923
|
|
|
|
|
|
|
} |
924
|
|
|
|
|
|
|
|
925
|
|
|
|
|
|
|
|
926
|
|
|
|
|
|
|
sub __is_inflatable_EOCPAR_columns_present_for_table { |
927
|
0
|
|
|
0
|
|
|
my $table = shift; |
928
|
0
|
|
|
|
|
|
my $column_lookup = shift; |
929
|
0
|
|
|
|
|
|
my $sql = shift; |
930
|
|
|
|
|
|
|
|
931
|
0
|
|
|
|
|
|
my $ok = 1; |
932
|
0
|
|
|
|
|
|
my $check = 1; |
933
|
0
|
|
|
|
|
|
my $exception = undef; |
934
|
|
|
|
|
|
|
|
935
|
0
|
|
|
|
|
|
my $class = $table->class(); |
936
|
0
|
|
|
|
|
|
foreach my $ec ($class->essential_sql_columns($table)) { |
937
|
0
|
0
|
|
|
|
|
last unless $ok; |
938
|
0
|
|
|
|
|
|
my $eoc = OutputColumn->new(expression => $ec); |
939
|
0
|
|
|
|
|
|
$check = exists $column_lookup->{__is_inflatable_EOCPAR_column_name($eoc)}; |
940
|
0
|
0
|
|
|
|
|
unless ($check) { |
941
|
0
|
|
|
|
|
|
$exception = Class::ReluctantORM::Exception::SQL::NotInflatable::MissingColumn->new |
942
|
|
|
|
|
|
|
( |
943
|
|
|
|
|
|
|
table => $table->schema . '.' . $table->table(), |
944
|
|
|
|
|
|
|
column => $ec->column, |
945
|
|
|
|
|
|
|
sql => $sql, |
946
|
|
|
|
|
|
|
); |
947
|
|
|
|
|
|
|
} |
948
|
0
|
|
0
|
|
|
|
$ok &&= $check; |
949
|
|
|
|
|
|
|
} |
950
|
0
|
|
|
|
|
|
return ($ok, $exception); |
951
|
|
|
|
|
|
|
} |
952
|
|
|
|
|
|
|
|
953
|
|
|
|
|
|
|
sub __is_inflatable_all_non_join_tables_are_in_relationships { |
954
|
0
|
|
|
0
|
|
|
my $sql = shift; |
955
|
|
|
|
|
|
|
|
956
|
0
|
|
|
|
|
|
my @non_join_tables = |
957
|
0
|
|
|
|
|
|
grep { ! Class::ReluctantORM->_is_join_table(table_obj => $_) } |
958
|
|
|
|
|
|
|
$sql->tables(exclude_subqueries => 1); |
959
|
|
|
|
|
|
|
|
960
|
|
|
|
|
|
|
# We're OK if it's just the base table left |
961
|
0
|
0
|
0
|
|
|
|
if (@non_join_tables == 1 && $non_join_tables[0]->is_the_same_table($sql->base_table)) { |
962
|
0
|
|
|
|
|
|
return (1, undef); |
963
|
|
|
|
|
|
|
} |
964
|
|
|
|
|
|
|
|
965
|
0
|
0
|
|
|
|
|
unless ($sql->from) { |
966
|
|
|
|
|
|
|
# WTF - has multiple tables, but no FROM clause? |
967
|
0
|
|
|
|
|
|
return (0, Class::ReluctantORM::Exception::SQL::NotInflatable->new(sql => $sql, error => "Multiple tables, but no from clause... confused am I!")); |
968
|
|
|
|
|
|
|
} |
969
|
|
|
|
|
|
|
|
970
|
0
|
|
|
|
|
|
my @rels = $sql->from->relationships(); |
971
|
|
|
|
|
|
|
TABLE: |
972
|
0
|
|
|
|
|
|
foreach my $table (@non_join_tables) { |
973
|
0
|
|
|
|
|
|
foreach my $rel (@rels) { |
974
|
0
|
|
|
|
|
|
foreach my $end (qw(local_sql_table remote_sql_table)) { |
975
|
0
|
|
|
|
|
|
my $rel_table = $rel->$end(); |
976
|
0
|
0
|
0
|
|
|
|
if ($rel_table && $table->is_the_same_table($rel_table)) { |
977
|
0
|
|
|
|
|
|
next TABLE; |
978
|
|
|
|
|
|
|
} |
979
|
|
|
|
|
|
|
} |
980
|
|
|
|
|
|
|
} |
981
|
|
|
|
|
|
|
# Been through all the relations and didn't a rel for this table |
982
|
0
|
|
|
|
|
|
return (0, Class::ReluctantORM::Exception::SQL::NotInflatable::ExtraTable->new(sql => $sql, error => "A table is neither an intermediate join table, nor does it appear at either end of any relationships", table => $table)); |
983
|
|
|
|
|
|
|
} |
984
|
|
|
|
|
|
|
|
985
|
|
|
|
|
|
|
# All tables check out.... |
986
|
0
|
|
|
|
|
|
return (1, undef); |
987
|
|
|
|
|
|
|
} |
988
|
|
|
|
|
|
|
|
989
|
|
|
|
|
|
|
sub __is_inflatable_all_joins_have_relationships { |
990
|
0
|
|
|
0
|
|
|
my $sql = shift; |
991
|
0
|
0
|
|
|
|
|
unless ($sql->from) { return (1, undef); } |
|
0
|
|
|
|
|
|
|
992
|
0
|
|
|
|
|
|
my @joins = $sql->from->joins(); |
993
|
0
|
|
|
|
|
|
foreach my $j (@joins) { |
994
|
0
|
0
|
|
|
|
|
unless ($j->relationship()) { |
995
|
0
|
|
|
|
|
|
return (0, Class::ReluctantORM::Exception::SQL::NotInflatable::VagueJoin->new(sql => $sql, error => "A join does not have a Relationship associated with it", join => $j)); |
996
|
|
|
|
|
|
|
} |
997
|
|
|
|
|
|
|
} |
998
|
|
|
|
|
|
|
|
999
|
|
|
|
|
|
|
# A-OK |
1000
|
0
|
|
|
|
|
|
return (1, undef); |
1001
|
|
|
|
|
|
|
} |
1002
|
|
|
|
|
|
|
|
1003
|
|
|
|
|
|
|
=head2 $sql->make_inflatable(%opts); |
1004
|
|
|
|
|
|
|
|
1005
|
|
|
|
|
|
|
Performs various actions to increase the inflatability of the SQL object. Calls annotate and reconcile. If any exceptions are thrown, they are passed on. |
1006
|
|
|
|
|
|
|
|
1007
|
|
|
|
|
|
|
Compare to is_inflatable, which optionally calls make_inflatable but captures any exception. |
1008
|
|
|
|
|
|
|
|
1009
|
|
|
|
|
|
|
Currently supported options: |
1010
|
|
|
|
|
|
|
|
1011
|
|
|
|
|
|
|
=over |
1012
|
|
|
|
|
|
|
|
1013
|
|
|
|
|
|
|
=item auto_annotate |
1014
|
|
|
|
|
|
|
|
1015
|
|
|
|
|
|
|
Optional boolean, default true. If true, call annotate() before performing the analysis. If false, you are saying that you have already attached any model metadata. |
1016
|
|
|
|
|
|
|
|
1017
|
|
|
|
|
|
|
=item auto_reconcile |
1018
|
|
|
|
|
|
|
|
1019
|
|
|
|
|
|
|
Optional boolean, default true. If true, call reconcile() before performing the analysis. |
1020
|
|
|
|
|
|
|
|
1021
|
|
|
|
|
|
|
=item add_output_columns |
1022
|
|
|
|
|
|
|
|
1023
|
|
|
|
|
|
|
Optional boolean, default true. If auto_reconcile is true, output columns will be added to the query to ensure that all essential (non-lazy) columns are present in the query. If auto_reconcile is false, has no effect. |
1024
|
|
|
|
|
|
|
|
1025
|
|
|
|
|
|
|
=back |
1026
|
|
|
|
|
|
|
|
1027
|
|
|
|
|
|
|
=cut |
1028
|
|
|
|
|
|
|
|
1029
|
|
|
|
|
|
|
sub make_inflatable { |
1030
|
0
|
|
|
0
|
1
|
|
my $sql = shift; |
1031
|
0
|
|
|
|
|
|
my %args = check_args(args => \@_, optional => [qw(auto_annotate auto_reconcile add_output_columns)]); |
1032
|
0
|
0
|
|
|
|
|
unless (defined($args{auto_annotate})) { $args{auto_annotate} = 1; } |
|
0
|
|
|
|
|
|
|
1033
|
0
|
0
|
|
|
|
|
unless (defined($args{auto_reconcile})) { $args{auto_reconcile} = 1; } |
|
0
|
|
|
|
|
|
|
1034
|
0
|
0
|
|
|
|
|
unless (defined($args{add_output_columns})) { $args{add_output_columns} = 1; } |
|
0
|
|
|
|
|
|
|
1035
|
|
|
|
|
|
|
|
1036
|
0
|
0
|
|
|
|
|
if ($args{auto_annotate}) { |
1037
|
0
|
|
|
|
|
|
$sql->annotate(); |
1038
|
|
|
|
|
|
|
} |
1039
|
0
|
0
|
|
|
|
|
if ($args{auto_reconcile}) { |
1040
|
0
|
|
|
|
|
|
$sql->reconcile(add_output_columns => $args{add_output_columns}); |
1041
|
|
|
|
|
|
|
} |
1042
|
|
|
|
|
|
|
} |
1043
|
|
|
|
|
|
|
|
1044
|
|
|
|
|
|
|
=head2 $sql->annotate(); |
1045
|
|
|
|
|
|
|
|
1046
|
|
|
|
|
|
|
Scans the SQL tree and attaches Tables and Relationships where they can be determined. |
1047
|
|
|
|
|
|
|
|
1048
|
|
|
|
|
|
|
=cut |
1049
|
|
|
|
|
|
|
|
1050
|
|
|
|
|
|
|
sub annotate { |
1051
|
0
|
|
|
0
|
1
|
|
my $sql = shift; |
1052
|
|
|
|
|
|
|
|
1053
|
|
|
|
|
|
|
TABLE: |
1054
|
0
|
|
|
|
|
|
foreach my $table ($sql->tables()) { |
1055
|
0
|
0
|
|
|
|
|
if (Class::ReluctantORM->_is_join_table(table_obj => $table)) { |
|
|
0
|
|
|
|
|
|
1056
|
0
|
|
|
|
|
|
my $jst = Class::ReluctantORM->_find_sql_table_for_join_table(table_obj => $table); |
1057
|
0
|
|
|
|
|
|
$table->_copy_manual_columns($jst); |
1058
|
0
|
|
|
|
|
|
$table->schema($jst->schema()); |
1059
|
|
|
|
|
|
|
} elsif (!$table->class()) { |
1060
|
0
|
|
|
|
|
|
my $class = Class::ReluctantORM->_find_class_by_table(table_obj => $table); |
1061
|
|
|
|
|
|
|
# might not be found (alias macro, for example) |
1062
|
|
|
|
|
|
|
# alias macros will get resolved during reconciliation anyway |
1063
|
0
|
0
|
|
|
|
|
if ($class) { |
1064
|
0
|
|
|
|
|
|
$table->class($class); |
1065
|
|
|
|
|
|
|
} |
1066
|
|
|
|
|
|
|
} |
1067
|
|
|
|
|
|
|
} |
1068
|
|
|
|
|
|
|
|
1069
|
|
|
|
|
|
|
# Hunt for relationships in the joins |
1070
|
0
|
0
|
|
|
|
|
if ($sql->from) { |
1071
|
0
|
|
|
|
|
|
$sql->__annotate_find_relationships(); |
1072
|
|
|
|
|
|
|
} |
1073
|
|
|
|
|
|
|
|
1074
|
|
|
|
|
|
|
|
1075
|
|
|
|
|
|
|
# Anything else? |
1076
|
|
|
|
|
|
|
|
1077
|
|
|
|
|
|
|
} |
1078
|
|
|
|
|
|
|
|
1079
|
|
|
|
|
|
|
sub __annotate_find_relationships { |
1080
|
0
|
|
|
0
|
|
|
my $sql = shift; |
1081
|
0
|
|
|
|
|
|
__annotate_FR_recursor($sql, $sql->from->root_relation()); |
1082
|
|
|
|
|
|
|
} |
1083
|
|
|
|
|
|
|
|
1084
|
|
|
|
|
|
|
sub __annotate_FR_recursor { |
1085
|
0
|
|
|
0
|
|
|
my $sql = shift; |
1086
|
0
|
|
|
|
|
|
my $rel = shift; |
1087
|
0
|
0
|
|
|
|
|
unless ($rel->is_join) { return; } |
|
0
|
|
|
|
|
|
|
1088
|
0
|
|
|
|
|
|
my $join = $rel; |
1089
|
|
|
|
|
|
|
|
1090
|
0
|
|
|
|
|
|
my ($right_rel, $left_rel) = ($join->right_relation(), $join->left_relation()); |
1091
|
|
|
|
|
|
|
|
1092
|
|
|
|
|
|
|
# Maybe it's already set? |
1093
|
0
|
0
|
|
|
|
|
if ($join->relationship()) { |
1094
|
|
|
|
|
|
|
# Just recurse and return |
1095
|
0
|
|
|
|
|
|
__annotate_FR_recursor($sql, $left_rel); |
1096
|
0
|
|
|
|
|
|
__annotate_FR_recursor($sql, $right_rel); |
1097
|
0
|
|
|
|
|
|
return; |
1098
|
|
|
|
|
|
|
} |
1099
|
|
|
|
|
|
|
|
1100
|
|
|
|
|
|
|
# Find the leftmost table on the each side |
1101
|
0
|
|
|
|
|
|
my $left_table = $left_rel->leftmost_table(); |
1102
|
0
|
|
|
|
|
|
my $right_table = $right_rel->leftmost_table(); |
1103
|
0
|
|
|
|
|
|
my @candidates; |
1104
|
|
|
|
|
|
|
|
1105
|
|
|
|
|
|
|
# Look for a relationship in which the local table of the relationship is the left table |
1106
|
|
|
|
|
|
|
# and the right table is either the remote table or the join table |
1107
|
0
|
0
|
|
|
|
|
if (@candidates == 0) { |
1108
|
0
|
|
|
|
|
|
@candidates = Class::ReluctantORM->_find_relationships_by_local_table(table_obj => $left_table); |
1109
|
0
|
0
|
0
|
|
|
|
@candidates = |
|
|
|
0
|
|
|
|
|
1110
|
|
|
|
|
|
|
grep { |
1111
|
0
|
|
|
|
|
|
($_->remote_sql_table && $right_table->is_the_same_table($_->remote_sql_table, 0)) || |
1112
|
|
|
|
|
|
|
($_->join_sql_table && $right_table->is_the_same_table($_->join_sql_table, 0)) |
1113
|
|
|
|
|
|
|
} |
1114
|
|
|
|
|
|
|
@candidates; |
1115
|
|
|
|
|
|
|
} |
1116
|
|
|
|
|
|
|
|
1117
|
|
|
|
|
|
|
# Look for a relationship in which the local table of the relationship is the right table |
1118
|
|
|
|
|
|
|
# and the left table is either the remote table or the join table |
1119
|
0
|
0
|
|
|
|
|
if (@candidates == 0) { |
1120
|
0
|
|
|
|
|
|
@candidates = Class::ReluctantORM->_find_relationships_by_local_table(table_obj => $right_table); |
1121
|
0
|
0
|
0
|
|
|
|
@candidates = |
|
|
|
0
|
|
|
|
|
1122
|
|
|
|
|
|
|
grep { |
1123
|
0
|
|
|
|
|
|
($_->remote_sql_table && $left_table->is_the_same_table($_->remote_sql_table, 0)) || |
1124
|
|
|
|
|
|
|
($_->join_sql_table && $left_table->is_the_same_table($_->join_sql_table, 0)) |
1125
|
|
|
|
|
|
|
} |
1126
|
|
|
|
|
|
|
@candidates; |
1127
|
|
|
|
|
|
|
} |
1128
|
|
|
|
|
|
|
|
1129
|
|
|
|
|
|
|
# Try desperate measures? |
1130
|
0
|
|
|
|
|
|
if (1) { |
1131
|
|
|
|
|
|
|
|
1132
|
|
|
|
|
|
|
# Look for a relationship in which the remote table of the relationship is the left table |
1133
|
|
|
|
|
|
|
# and the right table is either the local table or the join table |
1134
|
0
|
0
|
|
|
|
|
if (@candidates == 0) { |
1135
|
0
|
|
|
|
|
|
@candidates = Class::ReluctantORM->_find_relationships_by_remote_table(table_obj => $left_table); |
1136
|
0
|
0
|
0
|
|
|
|
@candidates = |
|
|
|
0
|
|
|
|
|
1137
|
|
|
|
|
|
|
grep { |
1138
|
0
|
|
|
|
|
|
($_->remote_sql_table && $right_table->is_the_same_table($_->local_sql_table, 0)) || |
1139
|
|
|
|
|
|
|
($_->join_sql_table && $right_table->is_the_same_table($_->join_sql_table, 0)) |
1140
|
|
|
|
|
|
|
} |
1141
|
|
|
|
|
|
|
@candidates; |
1142
|
|
|
|
|
|
|
} |
1143
|
|
|
|
|
|
|
|
1144
|
|
|
|
|
|
|
# Look for a relationship in which the remote table of the relationship is the right table |
1145
|
|
|
|
|
|
|
# and the left table is either the local table or the join table |
1146
|
0
|
0
|
|
|
|
|
if (@candidates == 0) { |
1147
|
0
|
|
|
|
|
|
@candidates = Class::ReluctantORM->_find_relationships_by_remote_table(table_obj => $right_table); |
1148
|
0
|
0
|
0
|
|
|
|
@candidates = |
|
|
|
0
|
|
|
|
|
1149
|
|
|
|
|
|
|
grep { |
1150
|
0
|
|
|
|
|
|
($_->remote_sql_table && $left_table->is_the_same_table($_->local_sql_table, 0)) || |
1151
|
|
|
|
|
|
|
($_->join_sql_table && $left_table->is_the_same_table($_->join_sql_table, 0)) |
1152
|
|
|
|
|
|
|
} |
1153
|
|
|
|
|
|
|
@candidates; |
1154
|
|
|
|
|
|
|
} |
1155
|
|
|
|
|
|
|
} |
1156
|
|
|
|
|
|
|
|
1157
|
|
|
|
|
|
|
|
1158
|
|
|
|
|
|
|
# The candidate relationships must have a criterion that is equivalent to the one on the join |
1159
|
0
|
|
|
|
|
|
@candidates = grep { $_->matches_join_criterion($join->criterion()) } @candidates; |
|
0
|
|
|
|
|
|
|
1160
|
0
|
|
|
|
|
|
my %unique_candidates = map { $_->method_name => $_ } @candidates; |
|
0
|
|
|
|
|
|
|
1161
|
0
|
|
|
|
|
|
@candidates = values %unique_candidates; |
1162
|
|
|
|
|
|
|
|
1163
|
0
|
0
|
|
|
|
|
if (@candidates == 0) { |
|
|
0
|
|
|
|
|
|
1164
|
0
|
|
|
|
|
|
Class::ReluctantORM::Exception::SQL::NotInflatable::VagueJoin->croak(join => $join, error => "Could not find any relationships that matched the tables on the ends of this Join", sql => $sql); |
1165
|
|
|
|
|
|
|
} elsif (@candidates > 1) { |
1166
|
0
|
|
|
|
|
|
Class::ReluctantORM::Exception::SQL::NotInflatable::VagueJoin->croak(join => $join, error => "Could not find a unique relationship that matched the tables on the ends of this Join", sql => $sql); |
1167
|
|
|
|
|
|
|
} else { |
1168
|
|
|
|
|
|
|
# Yay, exactly one relationship matched |
1169
|
0
|
|
|
|
|
|
$join->relationship($candidates[0]); |
1170
|
|
|
|
|
|
|
} |
1171
|
|
|
|
|
|
|
|
1172
|
|
|
|
|
|
|
# Recurse |
1173
|
0
|
|
|
|
|
|
__annotate_FR_recursor($sql, $left_rel); |
1174
|
0
|
|
|
|
|
|
__annotate_FR_recursor($sql, $right_rel); |
1175
|
|
|
|
|
|
|
|
1176
|
|
|
|
|
|
|
} |
1177
|
|
|
|
|
|
|
|
1178
|
|
|
|
|
|
|
=head2 @objects = $sql->inflate(); |
1179
|
|
|
|
|
|
|
|
1180
|
|
|
|
|
|
|
Executes the given query, and builds Class::ReluctantORM model objects directly from the results. |
1181
|
|
|
|
|
|
|
|
1182
|
|
|
|
|
|
|
This does not call is_inflatable() or make_inflatable() for you. See those methods to increase your chances of success. |
1183
|
|
|
|
|
|
|
|
1184
|
|
|
|
|
|
|
=cut |
1185
|
|
|
|
|
|
|
|
1186
|
|
|
|
|
|
|
sub inflate { |
1187
|
0
|
|
|
0
|
1
|
|
my $sql = shift; |
1188
|
0
|
|
|
|
|
|
my @results = fd_inflate($sql); # yipes |
1189
|
0
|
|
|
|
|
|
return @results; |
1190
|
|
|
|
|
|
|
} |
1191
|
|
|
|
|
|
|
|
1192
|
|
|
|
|
|
|
|
1193
|
|
|
|
|
|
|
|
1194
|
|
|
|
|
|
|
|
1195
|
|
|
|
|
|
|
#========================================================# |
1196
|
|
|
|
|
|
|
# Column Disambiguation |
1197
|
|
|
|
|
|
|
#========================================================# |
1198
|
|
|
|
|
|
|
|
1199
|
|
|
|
|
|
|
=begin devdocs |
1200
|
|
|
|
|
|
|
|
1201
|
|
|
|
|
|
|
=head2 $sql->set_reconcile_option(option => $value); |
1202
|
|
|
|
|
|
|
|
1203
|
|
|
|
|
|
|
This might go public one day, but for now it's best left to those who read the source. |
1204
|
|
|
|
|
|
|
|
1205
|
|
|
|
|
|
|
You can use this to set reconciliation options. Read reconcile() to see what they do. |
1206
|
|
|
|
|
|
|
|
1207
|
|
|
|
|
|
|
=cut |
1208
|
|
|
|
|
|
|
|
1209
|
|
|
|
|
|
|
sub set_reconcile_option { |
1210
|
0
|
|
|
0
|
1
|
|
my $sql = shift; |
1211
|
0
|
|
|
|
|
|
my %opts = @_; |
1212
|
0
|
|
|
|
|
|
foreach my $opt (keys %opts) { |
1213
|
0
|
|
|
|
|
|
$sql->{reconcile_options}{$opt} = $opts{$opt}; |
1214
|
|
|
|
|
|
|
} |
1215
|
|
|
|
|
|
|
} |
1216
|
|
|
|
|
|
|
|
1217
|
|
|
|
|
|
|
|
1218
|
|
|
|
|
|
|
=head2 $sql->reconcile(); |
1219
|
|
|
|
|
|
|
|
1220
|
|
|
|
|
|
|
Prepares the SQL object for rendering. This includes: |
1221
|
|
|
|
|
|
|
|
1222
|
|
|
|
|
|
|
=over |
1223
|
|
|
|
|
|
|
|
1224
|
|
|
|
|
|
|
=item ensure output columns are generated |
1225
|
|
|
|
|
|
|
|
1226
|
|
|
|
|
|
|
=item disambiguate column references in the WHERE and ORDER BY clauses |
1227
|
|
|
|
|
|
|
|
1228
|
|
|
|
|
|
|
=back |
1229
|
|
|
|
|
|
|
|
1230
|
|
|
|
|
|
|
There is no harm in calling this method multiple times. This method will |
1231
|
|
|
|
|
|
|
throw exceptions if it encounters irreconcilable ambiguities. |
1232
|
|
|
|
|
|
|
|
1233
|
|
|
|
|
|
|
=cut |
1234
|
|
|
|
|
|
|
|
1235
|
|
|
|
|
|
|
sub reconcile { |
1236
|
0
|
|
|
0
|
1
|
|
my $sql = shift; |
1237
|
0
|
|
|
|
|
|
my %args = check_args(args => \@_, optional => [qw(add_output_columns realias_raw_sql)]); |
1238
|
|
|
|
|
|
|
|
1239
|
0
|
|
|
|
|
|
my %instance_options = %{$sql->{reconcile_options}}; |
|
0
|
|
|
|
|
|
|
1240
|
0
|
|
|
|
|
|
my %options = (%instance_options, %args); |
1241
|
|
|
|
|
|
|
|
1242
|
0
|
|
|
|
|
|
$sql->__reconcile_in_subqueries(); |
1243
|
|
|
|
|
|
|
|
1244
|
0
|
|
|
|
|
|
$sql->__build_reconciliation_cache(); |
1245
|
0
|
|
|
|
|
|
$sql->__disambiguate_columns_in_from(); |
1246
|
0
|
|
|
|
|
|
$sql->__set_default_table_aliases(); |
1247
|
0
|
|
|
|
|
|
$sql->__build_reconciliation_cache(); # Rebuild needed after setting defualt table aliases |
1248
|
|
|
|
|
|
|
|
1249
|
0
|
|
|
|
|
|
$sql->__resolve_alias_macros(); |
1250
|
0
|
|
|
|
|
|
$sql->__disambiguate_columns_in_where(); |
1251
|
|
|
|
|
|
|
|
1252
|
0
|
0
|
|
|
|
|
if ($options{add_output_columns}) { |
1253
|
0
|
|
|
|
|
|
$sql->__add_output_columns(); |
1254
|
|
|
|
|
|
|
} |
1255
|
0
|
|
|
|
|
|
$sql->__disambiguate_columns_in_output(); |
1256
|
0
|
|
|
|
|
|
$sql->__disambiguate_columns_in_order_by(); |
1257
|
|
|
|
|
|
|
|
1258
|
0
|
|
|
|
|
|
delete $sql->{_rc}; |
1259
|
0
|
|
|
|
|
|
return 1; |
1260
|
|
|
|
|
|
|
|
1261
|
|
|
|
|
|
|
} |
1262
|
|
|
|
|
|
|
|
1263
|
|
|
|
|
|
|
|
1264
|
|
|
|
|
|
|
sub __add_output_columns { |
1265
|
0
|
|
|
0
|
|
|
my $sql = shift; |
1266
|
|
|
|
|
|
|
|
1267
|
0
|
0
|
|
|
|
|
if ($sql->operation eq 'DELETE') { return; } |
|
0
|
|
|
|
|
|
|
1268
|
|
|
|
|
|
|
|
1269
|
|
|
|
|
|
|
# Add base columns for tables |
1270
|
0
|
|
|
|
|
|
foreach my $table ($sql->tables) { |
1271
|
0
|
0
|
|
|
|
|
if ($table->class) { |
1272
|
0
|
|
|
|
|
|
foreach my $col ($table->class->essential_sql_columns($table)) { |
1273
|
0
|
|
|
|
|
|
$sql->add_output($col); |
1274
|
|
|
|
|
|
|
} |
1275
|
|
|
|
|
|
|
} |
1276
|
|
|
|
|
|
|
} |
1277
|
|
|
|
|
|
|
|
1278
|
|
|
|
|
|
|
# Add extra columns for relations |
1279
|
0
|
0
|
|
|
|
|
if ($sql->from) { |
1280
|
0
|
|
|
|
|
|
foreach my $relship ($sql->from->relationships) { |
1281
|
0
|
|
|
|
|
|
foreach my $col ($relship->additional_output_sql_columns) { |
1282
|
0
|
|
|
|
|
|
$sql->add_output($col); |
1283
|
|
|
|
|
|
|
} |
1284
|
|
|
|
|
|
|
} |
1285
|
|
|
|
|
|
|
} |
1286
|
|
|
|
|
|
|
} |
1287
|
|
|
|
|
|
|
|
1288
|
|
|
|
|
|
|
sub __build_reconciliation_cache { |
1289
|
0
|
|
|
0
|
|
|
my $self = shift; |
1290
|
0
|
|
|
|
|
|
my @from_tables; |
1291
|
0
|
0
|
|
|
|
|
if ($self->operation eq 'SELECT') { |
1292
|
0
|
0
|
|
|
|
|
@from_tables = $self->from ? $self->from->tables() : (); |
1293
|
|
|
|
|
|
|
} else { |
1294
|
0
|
0
|
|
|
|
|
@from_tables = $self->table ? ($self->table()) : (); |
1295
|
|
|
|
|
|
|
} |
1296
|
0
|
|
|
|
|
|
my %tables_by_alias = map { $_->alias => $_ } grep { defined($_->alias) } @from_tables; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
1297
|
0
|
|
|
|
|
|
my %tables_by_mem = map { ('' . $_ . '') => $_ } @from_tables; |
|
0
|
|
|
|
|
|
|
1298
|
0
|
|
|
|
|
|
my %tables_by_schema = map { ($_->schema . '.' . $_->table) => $_ } grep { defined($_->schema) } @from_tables; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
1299
|
0
|
|
|
|
|
|
my %tables_by_name; |
1300
|
0
|
|
|
|
|
|
foreach my $table (@from_tables) { |
1301
|
0
|
|
0
|
|
|
|
$tables_by_name{$table->table} ||= []; |
1302
|
0
|
|
|
|
|
|
push @{$tables_by_name{$table->table}}, $table; |
|
0
|
|
|
|
|
|
|
1303
|
|
|
|
|
|
|
} |
1304
|
0
|
|
|
|
|
|
my %tables_by_column; |
1305
|
0
|
|
|
|
|
|
foreach my $table (grep { $_->knows_any_columns } @from_tables) { |
|
0
|
|
|
|
|
|
|
1306
|
0
|
|
|
|
|
|
my @col_names = map { lc($_->column) } $table->columns; |
|
0
|
|
|
|
|
|
|
1307
|
0
|
|
|
|
|
|
foreach my $col_name (@col_names) { |
1308
|
0
|
|
0
|
|
|
|
$tables_by_column{$col_name} ||= []; |
1309
|
0
|
|
|
|
|
|
push @{$tables_by_column{$col_name}}, $table; |
|
0
|
|
|
|
|
|
|
1310
|
|
|
|
|
|
|
} |
1311
|
|
|
|
|
|
|
} |
1312
|
0
|
|
|
|
|
|
my %tables_by_relation; |
1313
|
0
|
0
|
|
|
|
|
if ($self->operation eq 'SELECT') { # assumes only selects have FROMs |
1314
|
0
|
|
|
|
|
|
foreach my $join ($self->from->joins()) { |
1315
|
0
|
0
|
|
|
|
|
if ($join->relationship()) { |
1316
|
0
|
|
|
|
|
|
my $rel = $join->relationship(); |
1317
|
0
|
|
|
|
|
|
my $relname = $rel->method_name(); |
1318
|
0
|
|
|
|
|
|
$tables_by_relation{$relname} = {}; |
1319
|
0
|
|
|
|
|
|
$tables_by_relation{$relname}{parent} = $join->_find_earliest_table($rel->local_sql_table()); |
1320
|
0
|
|
|
|
|
|
$tables_by_relation{$relname}{child} = $join->_find_latest_table($rel->remote_sql_table()); |
1321
|
0
|
0
|
|
|
|
|
if ($rel->join_depth > 1) { |
1322
|
0
|
|
|
|
|
|
$tables_by_relation{$relname}{join} = $join->_find_latest_table($rel->join_sql_table()); |
1323
|
|
|
|
|
|
|
} |
1324
|
|
|
|
|
|
|
} |
1325
|
|
|
|
|
|
|
} |
1326
|
|
|
|
|
|
|
} |
1327
|
|
|
|
|
|
|
|
1328
|
|
|
|
|
|
|
|
1329
|
0
|
|
|
|
|
|
$self->{_rc} = { |
1330
|
|
|
|
|
|
|
by_alias => \%tables_by_alias, |
1331
|
|
|
|
|
|
|
by_mem => \%tables_by_mem, |
1332
|
|
|
|
|
|
|
by_schema => \%tables_by_schema, |
1333
|
|
|
|
|
|
|
by_name => \%tables_by_name, |
1334
|
|
|
|
|
|
|
by_column => \%tables_by_column, |
1335
|
|
|
|
|
|
|
by_relationship => \%tables_by_relation |
1336
|
|
|
|
|
|
|
}; |
1337
|
|
|
|
|
|
|
} |
1338
|
|
|
|
|
|
|
|
1339
|
|
|
|
|
|
|
sub __reconcile_in_subqueries { |
1340
|
0
|
|
|
0
|
|
|
my $sql = shift; |
1341
|
|
|
|
|
|
|
|
1342
|
|
|
|
|
|
|
# Never add output columns to a subquery |
1343
|
0
|
|
|
|
|
|
my %opts = (%{$sql->{reconcile_options}}, add_output_columns => 0); |
|
0
|
|
|
|
|
|
|
1344
|
|
|
|
|
|
|
|
1345
|
|
|
|
|
|
|
my $reconciler = sub { |
1346
|
0
|
|
|
0
|
|
|
my $thing = shift; |
1347
|
0
|
0
|
|
|
|
|
if ($thing->is_subquery()) { |
1348
|
0
|
|
|
|
|
|
my $st = $thing->statement(); |
1349
|
0
|
|
|
|
|
|
$st->reconcile(%opts); |
1350
|
|
|
|
|
|
|
} |
1351
|
0
|
|
|
|
|
|
}; |
1352
|
|
|
|
|
|
|
|
1353
|
|
|
|
|
|
|
# Look for subqueries in output columns |
1354
|
0
|
|
|
|
|
|
foreach my $oc ($sql->output_columns) { |
1355
|
0
|
|
|
|
|
|
$oc->expression->walk_leaf_expressions($reconciler); |
1356
|
|
|
|
|
|
|
} |
1357
|
|
|
|
|
|
|
|
1358
|
|
|
|
|
|
|
# Look for subqueries in from |
1359
|
0
|
0
|
|
|
|
|
if ($sql->from) { |
|
|
0
|
|
|
|
|
|
1360
|
0
|
|
|
|
|
|
$sql->from->root_relation->walk_leaf_relations($reconciler); |
1361
|
|
|
|
|
|
|
} elsif ($sql->table) { |
1362
|
0
|
|
|
|
|
|
$sql->table->walk_leaf_relations($reconciler); |
1363
|
|
|
|
|
|
|
} |
1364
|
|
|
|
|
|
|
|
1365
|
|
|
|
|
|
|
# Look for subqueries in where |
1366
|
0
|
0
|
|
|
|
|
if ($sql->where) { |
1367
|
0
|
|
|
|
|
|
$sql->where->root_criterion->walk_leaf_expressions($reconciler); |
1368
|
|
|
|
|
|
|
} |
1369
|
|
|
|
|
|
|
|
1370
|
|
|
|
|
|
|
# Might have a input subquery (INSERTs only) |
1371
|
0
|
0
|
|
|
|
|
if ($sql->input_subquery) { |
1372
|
0
|
|
|
|
|
|
$sql->input_subquery->statement->reconcile(%opts); |
1373
|
|
|
|
|
|
|
} |
1374
|
|
|
|
|
|
|
|
1375
|
|
|
|
|
|
|
} |
1376
|
|
|
|
|
|
|
|
1377
|
|
|
|
|
|
|
# See 'Alias Macros' in Class/ReluctantORM/Manual/SQL.pod |
1378
|
|
|
|
|
|
|
sub __resolve_alias_macros { |
1379
|
0
|
|
|
0
|
|
|
my $sql = shift; |
1380
|
|
|
|
|
|
|
|
1381
|
0
|
0
|
|
|
|
|
my @cols = ( |
|
|
0
|
|
|
|
|
|
1382
|
|
|
|
|
|
|
($sql->where ? $sql->where->columns : ()), |
1383
|
|
|
|
|
|
|
($sql->order_by ? $sql->order_by->columns : ()), |
1384
|
|
|
|
|
|
|
); |
1385
|
|
|
|
|
|
|
|
1386
|
0
|
|
|
|
|
|
foreach my $column (@cols) { |
1387
|
0
|
|
|
|
|
|
my $info = $sql->__looks_like_alias_macro($column); |
1388
|
0
|
0
|
|
|
|
|
next unless $info; |
1389
|
0
|
|
|
|
|
|
my $table; |
1390
|
|
|
|
|
|
|
|
1391
|
0
|
0
|
|
|
|
|
if ($info->{type} eq 'base') { |
1392
|
0
|
|
|
|
|
|
$table = $sql->from->root_relation->leftmost_table(); |
1393
|
|
|
|
|
|
|
} else { |
1394
|
|
|
|
|
|
|
|
1395
|
|
|
|
|
|
|
# Find the referred-to relationship |
1396
|
0
|
|
|
|
|
|
my @matching_relations = grep { $_->method_name eq $info->{relname} } $sql->from->relationships(); |
|
0
|
|
|
|
|
|
|
1397
|
0
|
0
|
|
|
|
|
if (@matching_relations != 1) { |
1398
|
0
|
|
|
|
|
|
Class::ReluctantORM::Exception::SQL::AmbiguousReference->croak |
1399
|
|
|
|
|
|
|
("Must have exactly one reference to the relationship '$info->{relname}' to use a alias macro"); |
1400
|
|
|
|
|
|
|
} |
1401
|
0
|
|
|
|
|
|
my $relationship = $matching_relations[0]; |
1402
|
|
|
|
|
|
|
|
1403
|
|
|
|
|
|
|
# OK, find the JOIN that uses that relationship... |
1404
|
0
|
0
|
|
|
|
|
my ($join) = |
1405
|
0
|
|
|
|
|
|
grep { $_->relationship && $_->relationship->method_name eq $relationship->method_name } |
1406
|
|
|
|
|
|
|
$sql->from->joins(); |
1407
|
|
|
|
|
|
|
|
1408
|
0
|
0
|
|
|
|
|
if ($info->{type} eq 'parent') { |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
1409
|
|
|
|
|
|
|
# Hunt down the left-branch of the JOIN, looking for the LINKING table |
1410
|
0
|
|
|
|
|
|
my $seek = Table->new($relationship->linking_class()); |
1411
|
0
|
|
|
|
|
|
$table = $join->_find_earliest_table($seek); |
1412
|
|
|
|
|
|
|
} elsif ($info->{type} eq 'child') { |
1413
|
|
|
|
|
|
|
# Hunt down the right-branch of the JOIN, looking for the LINKED table |
1414
|
0
|
|
|
|
|
|
my $seek = Table->new($relationship->linked_class()); |
1415
|
0
|
|
|
|
|
|
$table = $join->_find_latest_table($seek); |
1416
|
|
|
|
|
|
|
} elsif ($info->{type} eq 'join') { |
1417
|
0
|
|
|
|
|
|
my $seek = $relationship->join_sql_table(); |
1418
|
0
|
|
|
|
|
|
$table = $join->_find_latest_table($seek); |
1419
|
|
|
|
|
|
|
} else { |
1420
|
0
|
|
|
|
|
|
Class::ReluctantORM::Exception::NotImplemented->croak |
1421
|
|
|
|
|
|
|
("Don't know how to handle an alias macro of type '$info->{type}'"); |
1422
|
|
|
|
|
|
|
} |
1423
|
|
|
|
|
|
|
} |
1424
|
|
|
|
|
|
|
|
1425
|
0
|
0
|
|
|
|
|
unless ($table) { |
1426
|
0
|
|
|
|
|
|
Class::ReluctantORM::Exception::SQL::TooComplex->croak |
1427
|
|
|
|
|
|
|
("Unable to resolve alias macro '" . $column->table->table . "' -- try simplifying?"); |
1428
|
|
|
|
|
|
|
} |
1429
|
|
|
|
|
|
|
|
1430
|
|
|
|
|
|
|
# Finally |
1431
|
0
|
|
|
|
|
|
$column->table($table); |
1432
|
|
|
|
|
|
|
|
1433
|
|
|
|
|
|
|
} |
1434
|
|
|
|
|
|
|
} |
1435
|
|
|
|
|
|
|
|
1436
|
|
|
|
|
|
|
my @ALIAS_MACRO_PATTERNS = ( |
1437
|
|
|
|
|
|
|
# Make these case-insensitive, since SQL::Statement will uppercase them |
1438
|
|
|
|
|
|
|
qr(MACRO__(base)__)i, |
1439
|
|
|
|
|
|
|
qr(MACRO__(parent)__(.+)__)i, |
1440
|
|
|
|
|
|
|
qr(MACRO__(child)__(.+)__)i, |
1441
|
|
|
|
|
|
|
qr(MACRO__(join)__(.+)__)i, |
1442
|
|
|
|
|
|
|
); |
1443
|
|
|
|
|
|
|
|
1444
|
|
|
|
|
|
|
sub __looks_like_alias_macro { |
1445
|
0
|
|
|
0
|
|
|
my $sql = shift; |
1446
|
0
|
|
|
|
|
|
my $column = shift; |
1447
|
0
|
|
|
|
|
|
my $table = $column->table(); |
1448
|
0
|
0
|
|
|
|
|
unless ($table) { return undef; } |
|
0
|
|
|
|
|
|
|
1449
|
0
|
|
|
|
|
|
my $name = $table->table(); # Don't use alias here - it may have been set by __set_default_table_aliases(); |
1450
|
0
|
0
|
|
|
|
|
unless ($name) { return undef; } |
|
0
|
|
|
|
|
|
|
1451
|
0
|
|
|
|
|
|
foreach my $pat (@ALIAS_MACRO_PATTERNS) { |
1452
|
0
|
|
|
|
|
|
my ($type, $relname) = $name =~ $pat; |
1453
|
0
|
0
|
|
|
|
|
if ($type) { |
1454
|
0
|
|
|
|
|
|
$type = lc($type); |
1455
|
|
|
|
|
|
|
# Find the relationship |
1456
|
0
|
|
|
|
|
|
my $lcrelname = ''; |
1457
|
0
|
0
|
|
|
|
|
unless ($type eq 'base') { |
1458
|
0
|
0
|
|
|
|
|
if ($sql->from) { |
1459
|
0
|
|
|
|
|
|
($lcrelname) = grep { lc($relname) eq lc($_) } map { $_->method_name } $sql->from->relationships(); |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
1460
|
0
|
0
|
|
|
|
|
unless ($lcrelname) { |
1461
|
0
|
|
|
|
|
|
Class::ReluctantORM::Exception::SQL::AmbiguousReference->croak |
1462
|
|
|
|
|
|
|
("Could not resolve alias macro '$name' - no relationship with name '$relname' (looked case insensitively)"); |
1463
|
|
|
|
|
|
|
} |
1464
|
0
|
|
|
|
|
|
$relname = $lcrelname; |
1465
|
|
|
|
|
|
|
} |
1466
|
|
|
|
|
|
|
} |
1467
|
0
|
|
|
|
|
|
return { type => $type, relname => $relname }; |
1468
|
|
|
|
|
|
|
} |
1469
|
|
|
|
|
|
|
} |
1470
|
0
|
|
|
|
|
|
return undef; |
1471
|
|
|
|
|
|
|
} |
1472
|
|
|
|
|
|
|
|
1473
|
|
|
|
|
|
|
|
1474
|
|
|
|
|
|
|
sub __disambiguate_columns_in_where { |
1475
|
0
|
|
|
0
|
|
|
my $self = shift; |
1476
|
0
|
0
|
|
|
|
|
if ($self->raw_where) { |
|
|
0
|
|
|
|
|
|
1477
|
0
|
|
|
|
|
|
$self->__raw_where_bind_params(); |
1478
|
0
|
0
|
|
|
|
|
if ($self->{reconcile_options}{realias_raw_sql}) { |
1479
|
0
|
|
|
|
|
|
$self->__raw_where_realias(); # sets cooked_where |
1480
|
|
|
|
|
|
|
} else { |
1481
|
0
|
|
|
|
|
|
$self->_cooked_where($self->raw_where()); |
1482
|
|
|
|
|
|
|
} |
1483
|
|
|
|
|
|
|
} elsif ($self->where) { |
1484
|
0
|
|
|
|
|
|
foreach my $col ($self->where->columns) { |
1485
|
0
|
|
|
|
|
|
$self->__disambiguate_column($col); |
1486
|
|
|
|
|
|
|
} |
1487
|
|
|
|
|
|
|
} |
1488
|
|
|
|
|
|
|
} |
1489
|
|
|
|
|
|
|
|
1490
|
|
|
|
|
|
|
sub __raw_where_bind_params { |
1491
|
0
|
|
|
0
|
|
|
my $sql = shift; |
1492
|
|
|
|
|
|
|
|
1493
|
|
|
|
|
|
|
# This is kinda dumb - at this point, we're reconciling, |
1494
|
|
|
|
|
|
|
# and further changes to the SQL are not permitted. So if |
1495
|
|
|
|
|
|
|
# anyone called set_bind_params ALREADY, respect that. But |
1496
|
|
|
|
|
|
|
# if they didn't, notice that and make a last minute bind. |
1497
|
0
|
|
|
|
|
|
my $already_bound = 1; |
1498
|
0
|
|
0
|
|
|
|
for ($sql->_raw_where_params) { $already_bound &&= $_->has_bind_value(); } |
|
0
|
|
|
|
|
|
|
1499
|
0
|
0
|
|
|
|
|
return if $already_bound; |
1500
|
|
|
|
|
|
|
|
1501
|
0
|
0
|
|
|
|
|
return unless defined($sql->_raw_where_execargs()); # Uhh, should this be an exception? |
1502
|
|
|
|
|
|
|
|
1503
|
0
|
0
|
|
|
|
|
my @ea = @{$sql->_raw_where_execargs() || []}; |
|
0
|
|
|
|
|
|
|
1504
|
0
|
|
|
|
|
|
foreach my $p ($sql->_raw_where_params) { |
1505
|
0
|
|
|
|
|
|
$p->bind_value(shift @ea); |
1506
|
|
|
|
|
|
|
} |
1507
|
|
|
|
|
|
|
} |
1508
|
|
|
|
|
|
|
|
1509
|
|
|
|
|
|
|
|
1510
|
|
|
|
|
|
|
# This one is doing string replacements on a SQL string, not working with objects |
1511
|
|
|
|
|
|
|
|
1512
|
|
|
|
|
|
|
sub __raw_where_realias { |
1513
|
0
|
|
|
0
|
|
|
my $sql = shift; |
1514
|
0
|
|
|
|
|
|
my $raw = $sql->raw_where(); |
1515
|
0
|
|
|
|
|
|
my $working = $raw; |
1516
|
|
|
|
|
|
|
|
1517
|
|
|
|
|
|
|
# TODO - this whole method should probably be moved into Driver, |
1518
|
|
|
|
|
|
|
# or else provide a way to set the driver being used |
1519
|
0
|
|
|
|
|
|
my $driver_class = Class::ReluctantORM->default_driver_class(); |
1520
|
|
|
|
|
|
|
|
1521
|
0
|
0
|
|
|
|
|
if ($sql->operation eq 'SELECT') { |
1522
|
|
|
|
|
|
|
# Have to work hard - may have multiple source tables, perhaps even same table multiple times |
1523
|
|
|
|
|
|
|
# At this point, from() should be defined, annotated, and reconciled |
1524
|
|
|
|
|
|
|
|
1525
|
0
|
|
|
|
|
|
my %rels_by_name = map { $_->method_name => $_ } $sql->from->relationships(); |
|
0
|
|
|
|
|
|
|
1526
|
|
|
|
|
|
|
|
1527
|
|
|
|
|
|
|
# process alias macros |
1528
|
0
|
|
|
|
|
|
foreach my $amre (@ALIAS_MACRO_PATTERNS) { |
1529
|
0
|
0
|
|
|
|
|
if (my ($type, $relname) = $working =~ $amre) { |
1530
|
0
|
|
|
|
|
|
my $alias; |
1531
|
0
|
0
|
|
|
|
|
if ($type eq 'base') { |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
1532
|
0
|
|
|
|
|
|
$alias = $sql->base_table->alias(); |
1533
|
|
|
|
|
|
|
} elsif ($type eq 'parent') { |
1534
|
0
|
|
|
|
|
|
$alias = $sql->{_rc}{by_relationship}{$relname}{parent}->alias; |
1535
|
|
|
|
|
|
|
} elsif ($type eq 'child') { |
1536
|
0
|
|
|
|
|
|
$alias = $sql->{_rc}{by_relationship}{$relname}{child}->alias; |
1537
|
|
|
|
|
|
|
} elsif ($type eq 'join') { |
1538
|
0
|
|
|
|
|
|
$alias = $sql->{_rc}{by_relationship}{$relname}{join}->alias; |
1539
|
|
|
|
|
|
|
} |
1540
|
0
|
|
|
|
|
|
$working =~ s{$amre}{$alias}ge; |
|
0
|
|
|
|
|
|
|
1541
|
|
|
|
|
|
|
} |
1542
|
|
|
|
|
|
|
} |
1543
|
|
|
|
|
|
|
|
1544
|
|
|
|
|
|
|
# Now loop over the known tables in the query |
1545
|
|
|
|
|
|
|
# and look for anything that might be refering to that table |
1546
|
|
|
|
|
|
|
|
1547
|
0
|
|
|
|
|
|
my ($oq, $cq, $ns) = ($driver_class->open_quote, $driver_class->close_quote, $driver_class->name_separator); |
1548
|
0
|
|
|
|
|
|
foreach my $t ($sql->from->tables()) { |
1549
|
0
|
|
|
|
|
|
my $alias = $t->alias() . $ns; |
1550
|
|
|
|
|
|
|
|
1551
|
|
|
|
|
|
|
# "schema_name"."table". |
1552
|
0
|
0
|
|
|
|
|
if ($t->schema) { |
1553
|
0
|
|
|
|
|
|
my $re1 = '(' . $oq . $t->schema . $cq . '\\' . $ns . $oq . $t->table . $cq . '\\' . $ns . ')'; |
1554
|
0
|
|
|
|
|
|
$working =~ s/$re1/$alias/g; |
1555
|
0
|
0
|
|
|
|
|
if ($DEBUG > 2) { print STDERR __PACKAGE__ . ':' . __LINE__ . " - alias sub pass one:\nre:\t$re1\nadjusted where:\t$working\n"; } |
|
0
|
|
|
|
|
|
|
1556
|
|
|
|
|
|
|
} |
1557
|
|
|
|
|
|
|
|
1558
|
|
|
|
|
|
|
# schema.table. |
1559
|
0
|
0
|
|
|
|
|
if ($t->schema) { |
1560
|
0
|
|
|
|
|
|
my $re2 = '(' . $t->schema . '\\' . $ns . $t->table . '\\' . $ns . ')'; |
1561
|
0
|
|
|
|
|
|
$working =~ s/$re2/$alias/g; |
1562
|
0
|
0
|
|
|
|
|
if ($DEBUG > 2) { print STDERR __PACKAGE__ . ':' . __LINE__ . " - alias sub pass two:\nre:\t$re2\nadjusted where:\t$working\n"; } |
|
0
|
|
|
|
|
|
|
1563
|
|
|
|
|
|
|
} |
1564
|
|
|
|
|
|
|
|
1565
|
|
|
|
|
|
|
# "table". |
1566
|
0
|
|
|
|
|
|
my $re3 = '(' . $oq . $t->table . $cq . '\\' . $ns . ')'; |
1567
|
0
|
|
|
|
|
|
$working =~ s/$re3/$alias/g; |
1568
|
0
|
0
|
|
|
|
|
if ($DEBUG > 2) { print STDERR __PACKAGE__ . ':' . __LINE__ . " - alias sub pass 3:\nre:\t$re3\nadjusted where:\t$working\n"; } |
|
0
|
|
|
|
|
|
|
1569
|
|
|
|
|
|
|
|
1570
|
|
|
|
|
|
|
# table. |
1571
|
0
|
|
|
|
|
|
my $re4 = '(' . $t->table . '\\' . $ns . ')'; |
1572
|
0
|
|
|
|
|
|
$working =~ s/$re4/$alias/g; |
1573
|
0
|
0
|
|
|
|
|
if ($DEBUG > 2) { print STDERR __PACKAGE__ . ':' . __LINE__ . " - alias sub pass 4:\nre:\t$re4\nadjusted where:\t$working\n"; } |
|
0
|
|
|
|
|
|
|
1574
|
|
|
|
|
|
|
|
1575
|
|
|
|
|
|
|
} |
1576
|
|
|
|
|
|
|
# OK, at this point, $working is as good as we can practically make it. It may still have |
1577
|
|
|
|
|
|
|
# ambiguous table or column references, but if so, the user should use the alias macro facility. |
1578
|
|
|
|
|
|
|
|
1579
|
|
|
|
|
|
|
} else { |
1580
|
|
|
|
|
|
|
# Assume we don't support FROM (or USING) with UPDATE, INSERT, or DELETE |
1581
|
|
|
|
|
|
|
# so we only have one source table. Nothing to do. |
1582
|
|
|
|
|
|
|
} |
1583
|
|
|
|
|
|
|
|
1584
|
|
|
|
|
|
|
|
1585
|
0
|
|
|
|
|
|
$sql->_cooked_where($working); |
1586
|
|
|
|
|
|
|
|
1587
|
|
|
|
|
|
|
} |
1588
|
|
|
|
|
|
|
|
1589
|
|
|
|
|
|
|
|
1590
|
|
|
|
|
|
|
|
1591
|
|
|
|
|
|
|
sub __disambiguate_columns_in_output { |
1592
|
0
|
|
|
0
|
|
|
my $sql = shift; |
1593
|
|
|
|
|
|
|
|
1594
|
|
|
|
|
|
|
# Collect all Columns, even those buried in Expressions |
1595
|
0
|
|
|
|
|
|
my @columns; |
1596
|
|
|
|
|
|
|
my $walker = sub { |
1597
|
0
|
|
|
0
|
|
|
my $expr = shift; |
1598
|
0
|
0
|
|
|
|
|
if ($expr->is_column) { |
1599
|
0
|
|
|
|
|
|
push @columns, $expr; |
1600
|
|
|
|
|
|
|
} |
1601
|
0
|
|
|
|
|
|
}; |
1602
|
0
|
|
|
|
|
|
foreach my $expr (map { $_->expression } $sql->output_columns()) { |
|
0
|
|
|
|
|
|
|
1603
|
0
|
|
|
|
|
|
$expr->walk_leaf_expressions($walker); |
1604
|
|
|
|
|
|
|
} |
1605
|
|
|
|
|
|
|
|
1606
|
|
|
|
|
|
|
# Disambiguate the columns |
1607
|
0
|
|
|
|
|
|
foreach my $col (@columns) { |
1608
|
0
|
|
|
|
|
|
$sql->__disambiguate_column($col); |
1609
|
|
|
|
|
|
|
} |
1610
|
|
|
|
|
|
|
|
1611
|
|
|
|
|
|
|
# At this point, each column knows which table it goes with, but it |
1612
|
|
|
|
|
|
|
# may not have an alias, and it may be a duplicate. |
1613
|
|
|
|
|
|
|
|
1614
|
0
|
|
|
|
|
|
my @all_output_columns = $sql->output_columns; |
1615
|
0
|
|
|
|
|
|
my @simple_outputs = grep { $_->expression->is_column } @all_output_columns; |
|
0
|
|
|
|
|
|
|
1616
|
0
|
|
|
|
|
|
my @expression_outputs = grep { !$_->expression->is_column } @all_output_columns; |
|
0
|
|
|
|
|
|
|
1617
|
|
|
|
|
|
|
|
1618
|
|
|
|
|
|
|
# Even after uniqueification, we need to preserve order. |
1619
|
0
|
|
|
|
|
|
my %oc_order; |
1620
|
0
|
|
|
|
|
|
for (0..$#all_output_columns) { $oc_order{$all_output_columns[$_]} = $_; } |
|
0
|
|
|
|
|
|
|
1621
|
|
|
|
|
|
|
|
1622
|
|
|
|
|
|
|
# Filter out duplicates among the simple |
1623
|
|
|
|
|
|
|
# column outputs, using tablealias.columnname as the key |
1624
|
0
|
|
|
|
|
|
my %unique_simple_cols = |
1625
|
0
|
|
|
|
|
|
map { ($_->expression->table->alias . '.' . $_->expression->column) => $_ } |
1626
|
|
|
|
|
|
|
@simple_outputs; |
1627
|
|
|
|
|
|
|
|
1628
|
|
|
|
|
|
|
# We don't try to uniqueify any expression columns |
1629
|
0
|
|
|
|
|
|
my @unique_all_cols = (values(%unique_simple_cols), @expression_outputs); |
1630
|
|
|
|
|
|
|
|
1631
|
|
|
|
|
|
|
# OK, put them back in original order |
1632
|
0
|
|
|
|
|
|
my @ordered_unique_cols = |
1633
|
0
|
|
|
|
|
|
sort { $oc_order{$a} <=> $oc_order{$b} } |
1634
|
|
|
|
|
|
|
@unique_all_cols; |
1635
|
|
|
|
|
|
|
|
1636
|
0
|
|
|
|
|
|
$sql->{outputs} = \@ordered_unique_cols; |
1637
|
|
|
|
|
|
|
|
1638
|
|
|
|
|
|
|
# Now set column aliases |
1639
|
0
|
|
|
|
|
|
$sql->__set_default_column_aliases(); |
1640
|
|
|
|
|
|
|
|
1641
|
|
|
|
|
|
|
} |
1642
|
|
|
|
|
|
|
|
1643
|
|
|
|
|
|
|
sub __disambiguate_columns_in_order_by { |
1644
|
0
|
|
|
0
|
|
|
my $self = shift; |
1645
|
0
|
0
|
|
|
|
|
return unless $self->order_by; |
1646
|
0
|
|
|
|
|
|
foreach my $col ($self->order_by->columns) { |
1647
|
0
|
|
|
|
|
|
$self->__disambiguate_column($col); |
1648
|
|
|
|
|
|
|
} |
1649
|
|
|
|
|
|
|
} |
1650
|
|
|
|
|
|
|
|
1651
|
|
|
|
|
|
|
sub __disambiguate_columns_in_from { |
1652
|
0
|
|
|
0
|
|
|
my $sql = shift; |
1653
|
0
|
0
|
|
|
|
|
return unless $sql->from; |
1654
|
|
|
|
|
|
|
|
1655
|
|
|
|
|
|
|
# Look for criteria and resolve their columns |
1656
|
|
|
|
|
|
|
my $walker = sub { |
1657
|
0
|
|
|
0
|
|
|
my $expr = shift; |
1658
|
0
|
0
|
|
|
|
|
if ($expr->is_column()) { |
1659
|
0
|
|
|
|
|
|
$sql->__disambiguate_column($expr); |
1660
|
|
|
|
|
|
|
} |
1661
|
0
|
|
|
|
|
|
}; |
1662
|
0
|
|
|
|
|
|
foreach my $join ($sql->from->joins()) { |
1663
|
0
|
|
|
|
|
|
$join->criterion->walk_leaf_expressions($walker); |
1664
|
|
|
|
|
|
|
} |
1665
|
|
|
|
|
|
|
|
1666
|
|
|
|
|
|
|
|
1667
|
|
|
|
|
|
|
# This will disambiguate any columns in tables referenced in the from clause |
1668
|
0
|
|
|
|
|
|
foreach my $col ($sql->from->columns) { |
1669
|
0
|
|
|
|
|
|
$sql->__disambiguate_column($col); |
1670
|
|
|
|
|
|
|
} |
1671
|
|
|
|
|
|
|
} |
1672
|
|
|
|
|
|
|
|
1673
|
|
|
|
|
|
|
sub __disambiguate_columns_in_input { |
1674
|
0
|
|
|
0
|
|
|
my $self = shift; |
1675
|
0
|
|
|
|
|
|
foreach my $pair ($self->inputs) { |
1676
|
0
|
|
|
|
|
|
$self->__disambiguate_column($pair->{column}); |
1677
|
|
|
|
|
|
|
} |
1678
|
|
|
|
|
|
|
} |
1679
|
|
|
|
|
|
|
|
1680
|
|
|
|
|
|
|
|
1681
|
|
|
|
|
|
|
sub __disambiguate_column { |
1682
|
0
|
|
|
0
|
|
|
my $self = shift; |
1683
|
0
|
|
|
|
|
|
my $col = shift; |
1684
|
0
|
|
|
|
|
|
my $table = $col->table(); |
1685
|
0
|
|
|
|
|
|
my %cache = %{$self->{_rc}}; |
|
0
|
|
|
|
|
|
|
1686
|
|
|
|
|
|
|
|
1687
|
0
|
0
|
|
|
|
|
if ($table) { |
1688
|
|
|
|
|
|
|
|
1689
|
0
|
|
0
|
|
|
|
my $alias = $table->alias() || 'no alias'; |
1690
|
0
|
0
|
|
|
|
|
if ($DEBUG > 2) { print STDERR __PACKAGE__ . ':' . __LINE__ . "Have table " . $table->table . "($alias) for column " . $col->column . "\n"; } |
|
0
|
|
|
|
|
|
|
1691
|
|
|
|
|
|
|
|
1692
|
|
|
|
|
|
|
# If we have a table, look it up by memory address first; do nothing if found (already unambiguous) |
1693
|
0
|
0
|
|
|
|
|
if (exists $cache{by_mem}{'' . $table . ''}) { return; } |
|
0
|
|
|
|
|
|
|
1694
|
|
|
|
|
|
|
|
1695
|
|
|
|
|
|
|
# look up by alias and replace if found |
1696
|
0
|
0
|
0
|
|
|
|
if ($table->alias && exists($cache{by_alias}{$table->alias})) { |
1697
|
0
|
|
|
|
|
|
$col->table($cache{by_alias}{$table->alias}); |
1698
|
0
|
|
|
|
|
|
return; |
1699
|
|
|
|
|
|
|
} |
1700
|
|
|
|
|
|
|
|
1701
|
|
|
|
|
|
|
# look up by schema.table and replace if found |
1702
|
0
|
0
|
0
|
|
|
|
if ($table->schema && exists($cache{by_schema}{$table->schema . '.' . $table->table})) { |
1703
|
0
|
|
|
|
|
|
$col->table($cache{by_schema}{$table->schema . '.' . $table->table}); |
1704
|
0
|
|
|
|
|
|
return; |
1705
|
|
|
|
|
|
|
} |
1706
|
|
|
|
|
|
|
|
1707
|
|
|
|
|
|
|
# look up by table and replace if found; panic if more than one table with that name |
1708
|
0
|
0
|
|
|
|
|
my @tables_with_that_name = @{$cache{by_name}{$table->table} || []}; |
|
0
|
|
|
|
|
|
|
1709
|
0
|
0
|
|
|
|
|
if (@tables_with_that_name == 1) { |
|
|
0
|
|
|
|
|
|
1710
|
0
|
|
|
|
|
|
$col->table($tables_with_that_name[0]); |
1711
|
|
|
|
|
|
|
} elsif (@tables_with_that_name == 0) { |
1712
|
0
|
|
|
|
|
|
Class::ReluctantORM::Exception::SQL::AmbiguousReference->croak("The column " . $col->column . " apparently belongs to a table that is not referenced in the query (" . $table->table . ")"); |
1713
|
|
|
|
|
|
|
} else { |
1714
|
0
|
0
|
|
|
|
|
Class::ReluctantORM::Exception::SQL::AmbiguousReference->croak("The column " . $col->column . " could not be unambiguously assigned to a table - candidates: " . (join ',', map { ($_->schema ? ($_->schema . '.') : '') . $_->table } @tables_with_that_name)); |
|
0
|
|
|
|
|
|
|
1715
|
|
|
|
|
|
|
} |
1716
|
|
|
|
|
|
|
|
1717
|
|
|
|
|
|
|
} else { |
1718
|
|
|
|
|
|
|
# else no table, so look by column |
1719
|
0
|
0
|
|
|
|
|
my @tables_with_that_column = @{$cache{by_column}{lc($col->column)} || []}; |
|
0
|
|
|
|
|
|
|
1720
|
|
|
|
|
|
|
|
1721
|
0
|
0
|
|
|
|
|
if (@tables_with_that_column == 1) { |
|
|
0
|
|
|
|
|
|
1722
|
0
|
|
|
|
|
|
$col->table($tables_with_that_column[0]); |
1723
|
|
|
|
|
|
|
} elsif (@tables_with_that_column == 0) { |
1724
|
0
|
|
|
|
|
|
Class::ReluctantORM::Exception::SQL::AmbiguousReference->croak("The column " . $col->column . " has no table specified, and no table in the query could be found that has that column."); |
1725
|
|
|
|
|
|
|
} else { |
1726
|
0
|
0
|
|
|
|
|
Class::ReluctantORM::Exception::SQL::AmbiguousReference->croak("The column " . $col->column . " could not be unambiguously assigned to a table by column name - candidates: " . (join ',', map { ($_->schema ? ($_->schema . '.') : '') . $_->table . ($_->alias ? ' (' . $_->alias . ')' : '') } @tables_with_that_column)); |
|
0
|
0
|
|
|
|
|
|
1727
|
|
|
|
|
|
|
} |
1728
|
|
|
|
|
|
|
} |
1729
|
|
|
|
|
|
|
} |
1730
|
|
|
|
|
|
|
|
1731
|
|
|
|
|
|
|
#=======================================================# |
1732
|
|
|
|
|
|
|
# DIRECT EXECUTION |
1733
|
|
|
|
|
|
|
#=======================================================# |
1734
|
|
|
|
|
|
|
|
1735
|
|
|
|
|
|
|
=head1 DIRECT EXECUTION |
1736
|
|
|
|
|
|
|
|
1737
|
|
|
|
|
|
|
These low-level methods allow you to use DBI-style prepare/execute/fetch cycles on SQL objects. |
1738
|
|
|
|
|
|
|
|
1739
|
|
|
|
|
|
|
Use $driver->prepare($sql) to start this process. |
1740
|
|
|
|
|
|
|
|
1741
|
|
|
|
|
|
|
=cut |
1742
|
|
|
|
|
|
|
|
1743
|
|
|
|
|
|
|
=head2 $bool = $sql->is_prepared(); |
1744
|
|
|
|
|
|
|
|
1745
|
|
|
|
|
|
|
Returns true if the SQL object has been prepared using $driver->prepare(). |
1746
|
|
|
|
|
|
|
|
1747
|
|
|
|
|
|
|
=cut |
1748
|
|
|
|
|
|
|
|
1749
|
|
|
|
|
|
|
sub is_prepared { |
1750
|
0
|
|
|
0
|
1
|
|
my $sql = shift; |
1751
|
0
|
|
|
|
|
|
return defined ($sql->_sth()); |
1752
|
|
|
|
|
|
|
} |
1753
|
|
|
|
|
|
|
|
1754
|
|
|
|
|
|
|
=head2 $sql->execute(); |
1755
|
|
|
|
|
|
|
|
1756
|
|
|
|
|
|
|
=head2 $sql->execute(@bind_values); |
1757
|
|
|
|
|
|
|
|
1758
|
|
|
|
|
|
|
In the first form, executes the statement using the existing values bound to the Params (if any). |
1759
|
|
|
|
|
|
|
|
1760
|
|
|
|
|
|
|
In the second form, binds the given values to the parameters in the SQL object, and executes the statement handle. |
1761
|
|
|
|
|
|
|
|
1762
|
|
|
|
|
|
|
is_prepared() must return true for this to work. If anything goes wrong (including database errors) an exception will be thrown. |
1763
|
|
|
|
|
|
|
|
1764
|
|
|
|
|
|
|
=cut |
1765
|
|
|
|
|
|
|
|
1766
|
|
|
|
|
|
|
__PACKAGE__->mk_accessors(qw(execute_hints)); |
1767
|
|
|
|
|
|
|
|
1768
|
|
|
|
|
|
|
sub execute { |
1769
|
0
|
|
|
0
|
1
|
|
my $sql = shift; |
1770
|
0
|
0
|
|
|
|
|
unless ($sql->is_prepared()) { |
1771
|
0
|
|
|
|
|
|
Class::ReluctantORM::Exception::SQL::ExecuteWithoutPrepare->croak(); |
1772
|
|
|
|
|
|
|
} |
1773
|
|
|
|
|
|
|
|
1774
|
|
|
|
|
|
|
# If binds were provided, set them |
1775
|
0
|
0
|
|
|
|
|
if (@_) { |
1776
|
0
|
|
|
|
|
|
$sql->set_bind_values(@_); |
1777
|
|
|
|
|
|
|
} |
1778
|
|
|
|
|
|
|
|
1779
|
0
|
|
|
|
|
|
my %monitor_args = $sql->__monitor_args(); |
1780
|
0
|
|
|
|
|
|
my $driver = $sql->_execution_driver(); |
1781
|
|
|
|
|
|
|
|
1782
|
0
|
|
|
|
|
|
$driver->_monitor_execute_begin(%monitor_args); |
1783
|
0
|
|
|
|
|
|
$driver->_pre_execute_hook($sql); |
1784
|
0
|
|
|
|
|
|
$sql->_sth->execute($sql->get_bind_values()); |
1785
|
0
|
|
|
|
|
|
$driver->_post_execute_hook($sql); |
1786
|
0
|
|
|
|
|
|
$driver->_monitor_execute_finish(%monitor_args); |
1787
|
|
|
|
|
|
|
|
1788
|
0
|
|
|
|
|
|
return; |
1789
|
|
|
|
|
|
|
} |
1790
|
|
|
|
|
|
|
|
1791
|
|
|
|
|
|
|
sub __monitor_args { |
1792
|
0
|
|
|
0
|
|
|
my $sql = shift; |
1793
|
|
|
|
|
|
|
return ( |
1794
|
0
|
|
|
|
|
|
sql_obj => $sql, |
1795
|
|
|
|
|
|
|
sql_str => $sql->_sql_string, |
1796
|
|
|
|
|
|
|
binds => [ $sql->get_bind_values() ], |
1797
|
|
|
|
|
|
|
sth => $sql->_sth(), |
1798
|
|
|
|
|
|
|
); |
1799
|
|
|
|
|
|
|
} |
1800
|
|
|
|
|
|
|
|
1801
|
|
|
|
|
|
|
=head2 $sql->fetchrow(); |
1802
|
|
|
|
|
|
|
|
1803
|
|
|
|
|
|
|
Fetches one row from the statment handle. The fetched values are bound to the Output Columns of the SQL object - access them using $sql->output_columns. |
1804
|
|
|
|
|
|
|
|
1805
|
|
|
|
|
|
|
=cut |
1806
|
|
|
|
|
|
|
|
1807
|
|
|
|
|
|
|
sub fetchrow { |
1808
|
0
|
|
|
0
|
1
|
|
my $sql = shift; |
1809
|
|
|
|
|
|
|
|
1810
|
0
|
0
|
|
|
|
|
unless ($sql->is_prepared()) { |
1811
|
0
|
|
|
|
|
|
Class::ReluctantORM::Exception::SQL::ExecuteWithoutPrepare->croak(); |
1812
|
|
|
|
|
|
|
} |
1813
|
|
|
|
|
|
|
|
1814
|
0
|
|
|
|
|
|
my $row = $sql->_sth->fetchrow_hashref(); |
1815
|
0
|
|
|
|
|
|
$sql->_execution_driver->_monitor_fetch_row($sql->__monitor_args(), row => $row); |
1816
|
0
|
|
|
|
|
|
$sql->set_single_row_results($row); |
1817
|
|
|
|
|
|
|
|
1818
|
0
|
|
|
|
|
|
return $row; |
1819
|
|
|
|
|
|
|
} |
1820
|
|
|
|
|
|
|
|
1821
|
|
|
|
|
|
|
=head2 $sql->fetch_all(); |
1822
|
|
|
|
|
|
|
|
1823
|
|
|
|
|
|
|
Fetches all rows from the statement handle, and calls your callback after fetching each row (see $sql->add_fetchrow_listener()). |
1824
|
|
|
|
|
|
|
|
1825
|
|
|
|
|
|
|
=cut |
1826
|
|
|
|
|
|
|
|
1827
|
|
|
|
|
|
|
sub fetch_all { |
1828
|
0
|
|
|
0
|
1
|
|
my $sql = shift; |
1829
|
|
|
|
|
|
|
|
1830
|
0
|
0
|
|
|
|
|
unless ($sql->is_prepared()) { |
1831
|
0
|
|
|
|
|
|
Class::ReluctantORM::Exception::SQL::ExecuteWithoutPrepare->croak(); |
1832
|
|
|
|
|
|
|
} |
1833
|
|
|
|
|
|
|
|
1834
|
0
|
|
|
|
|
|
my %monitor_args = $sql->__monitor_args(); |
1835
|
0
|
|
|
|
|
|
while (my $row = $sql->_sth->fetchrow_hashref()) { |
1836
|
0
|
|
|
|
|
|
$sql->_execution_driver->_monitor_fetch_row(%monitor_args, row => $row); |
1837
|
0
|
|
|
|
|
|
$sql->set_single_row_results($row); |
1838
|
|
|
|
|
|
|
} |
1839
|
|
|
|
|
|
|
|
1840
|
0
|
|
|
|
|
|
return; |
1841
|
|
|
|
|
|
|
} |
1842
|
|
|
|
|
|
|
|
1843
|
|
|
|
|
|
|
|
1844
|
|
|
|
|
|
|
=head2 $sql->finish(); |
1845
|
|
|
|
|
|
|
|
1846
|
|
|
|
|
|
|
Releases the statement handle. is_prepared() must be true for this to work. |
1847
|
|
|
|
|
|
|
|
1848
|
|
|
|
|
|
|
=cut |
1849
|
|
|
|
|
|
|
|
1850
|
|
|
|
|
|
|
sub finish { |
1851
|
0
|
|
|
0
|
1
|
|
my $sql = shift; |
1852
|
0
|
0
|
|
|
|
|
unless ($sql->is_prepared()) { |
1853
|
0
|
|
|
|
|
|
Class::ReluctantORM::Exception::SQL::FinishWithoutPrepare->croak(); |
1854
|
|
|
|
|
|
|
} |
1855
|
|
|
|
|
|
|
|
1856
|
0
|
|
|
|
|
|
$sql->_sth->finish(); |
1857
|
0
|
|
|
|
|
|
$sql->_execution_driver->_monitor_finish($sql->__monitor_args()); |
1858
|
|
|
|
|
|
|
|
1859
|
0
|
|
|
|
|
|
$sql->_sth(undef); |
1860
|
0
|
|
|
|
|
|
$sql->_sql_string(undef); |
1861
|
0
|
|
|
|
|
|
$sql->_execution_driver(undef); |
1862
|
|
|
|
|
|
|
|
1863
|
0
|
|
|
|
|
|
return; |
1864
|
|
|
|
|
|
|
} |
1865
|
|
|
|
|
|
|
|
1866
|
|
|
|
|
|
|
|
1867
|
|
|
|
|
|
|
#=======================================================# |
1868
|
|
|
|
|
|
|
# Results Fetching |
1869
|
|
|
|
|
|
|
#=======================================================# |
1870
|
|
|
|
|
|
|
|
1871
|
|
|
|
|
|
|
=head1 FETCHING RESULTS |
1872
|
|
|
|
|
|
|
|
1873
|
|
|
|
|
|
|
=cut |
1874
|
|
|
|
|
|
|
|
1875
|
|
|
|
|
|
|
=head2 $bool = $sql->has_results(); |
1876
|
|
|
|
|
|
|
|
1877
|
|
|
|
|
|
|
Returns true if the SQL object has been executed and has at least one row of results. |
1878
|
|
|
|
|
|
|
|
1879
|
|
|
|
|
|
|
=cut |
1880
|
|
|
|
|
|
|
|
1881
|
|
|
|
|
|
|
__PACKAGE__->mk_accessors(qw(has_results)); |
1882
|
|
|
|
|
|
|
|
1883
|
|
|
|
|
|
|
=head2 $sql->add_fetchrow_listener($coderef); |
1884
|
|
|
|
|
|
|
|
1885
|
|
|
|
|
|
|
Adds a coderef that will be called with the SQL object as the only argument immediately after a row is fetched. You may then obtain results from the $sql->output_columns, calling output_value on each. |
1886
|
|
|
|
|
|
|
|
1887
|
|
|
|
|
|
|
=cut |
1888
|
|
|
|
|
|
|
|
1889
|
|
|
|
|
|
|
sub add_fetchrow_listener { |
1890
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
1891
|
0
|
|
|
|
|
|
my $coderef = shift; |
1892
|
0
|
0
|
|
|
|
|
unless (ref($coderef) eq 'CODE') { |
1893
|
0
|
|
|
|
|
|
Class::ReluctantORM::Exception::Param::WrongType->croak(expected => 'CODEREF', param => 'code'); |
1894
|
|
|
|
|
|
|
} |
1895
|
0
|
|
|
|
|
|
push @{$self->{fetchrow_listeners}}, $coderef; |
|
0
|
|
|
|
|
|
|
1896
|
|
|
|
|
|
|
} |
1897
|
|
|
|
|
|
|
|
1898
|
|
|
|
|
|
|
|
1899
|
|
|
|
|
|
|
=head2 $sql->clear_fetchrow_listeners(); |
1900
|
|
|
|
|
|
|
|
1901
|
|
|
|
|
|
|
Clears the list of listeners. |
1902
|
|
|
|
|
|
|
|
1903
|
|
|
|
|
|
|
=cut |
1904
|
|
|
|
|
|
|
|
1905
|
|
|
|
|
|
|
sub clear_fetchrow_listeners { |
1906
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
1907
|
0
|
|
|
|
|
|
$self->{fetchrow_listeners} = []; |
1908
|
|
|
|
|
|
|
} |
1909
|
|
|
|
|
|
|
|
1910
|
|
|
|
|
|
|
sub _notify_fetchrow_listeners { |
1911
|
0
|
|
|
0
|
|
|
my $self = shift; |
1912
|
0
|
|
|
|
|
|
foreach my $coderef (@{$self->{fetchrow_listeners}}) { |
|
0
|
|
|
|
|
|
|
1913
|
0
|
|
|
|
|
|
$coderef->($self); |
1914
|
|
|
|
|
|
|
} |
1915
|
|
|
|
|
|
|
} |
1916
|
|
|
|
|
|
|
|
1917
|
|
|
|
|
|
|
sub set_single_row_results { |
1918
|
0
|
|
|
0
|
0
|
|
my $sql = shift; |
1919
|
0
|
|
|
|
|
|
my $row = shift; |
1920
|
0
|
0
|
|
|
|
|
if ($row) { |
1921
|
0
|
|
|
|
|
|
foreach my $col ($sql->output_columns) { |
1922
|
0
|
|
|
|
|
|
$col->output_value($row->{$col->alias}); |
1923
|
|
|
|
|
|
|
} |
1924
|
0
|
|
|
|
|
|
$sql->has_results(1); |
1925
|
0
|
|
|
|
|
|
$sql->_notify_fetchrow_listeners(); |
1926
|
|
|
|
|
|
|
} else { |
1927
|
0
|
|
|
|
|
|
$sql->has_results(0); |
1928
|
|
|
|
|
|
|
} |
1929
|
|
|
|
|
|
|
} |
1930
|
|
|
|
|
|
|
|
1931
|
|
|
|
|
|
|
|
1932
|
|
|
|
|
|
|
#=================================================================# |
1933
|
|
|
|
|
|
|
# MISC METHODS |
1934
|
|
|
|
|
|
|
#=================================================================# |
1935
|
|
|
|
|
|
|
|
1936
|
|
|
|
|
|
|
|
1937
|
|
|
|
|
|
|
=head1 MISC METHODS |
1938
|
|
|
|
|
|
|
|
1939
|
|
|
|
|
|
|
=cut |
1940
|
|
|
|
|
|
|
|
1941
|
|
|
|
|
|
|
=head2 $str = $sql->pretty_print(); |
1942
|
|
|
|
|
|
|
|
1943
|
|
|
|
|
|
|
Returns a human-readable string representation of the query. Not appropriate for use for feeding to a prepare() statement. |
1944
|
|
|
|
|
|
|
|
1945
|
|
|
|
|
|
|
=cut |
1946
|
|
|
|
|
|
|
|
1947
|
|
|
|
|
|
|
sub pretty_print { |
1948
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
1949
|
0
|
|
|
|
|
|
my %args = @_; |
1950
|
0
|
|
|
|
|
|
my $op = $self->operation; |
1951
|
0
|
|
0
|
|
|
|
my $prefix = $args{prefix} || ''; |
1952
|
0
|
|
|
|
|
|
my $str = $prefix . "$op\n"; |
1953
|
0
|
|
|
|
|
|
$prefix .= ' '; |
1954
|
0
|
0
|
|
|
|
|
if ($op ne 'DELETE') { |
1955
|
0
|
|
|
|
|
|
$str .= $prefix . "OUTPUT Columns:\n"; |
1956
|
0
|
|
|
|
|
|
foreach my $oc ($self->output_columns) { |
1957
|
0
|
|
|
|
|
|
$str .= $prefix . ' ' . $oc->pretty_print(one_line => 1) . "\n"; |
1958
|
|
|
|
|
|
|
} |
1959
|
|
|
|
|
|
|
} |
1960
|
0
|
0
|
|
|
|
|
if ($op ne 'SELECT') { |
1961
|
0
|
|
|
|
|
|
$str .= $prefix . 'TABLE: ' . $self->table->pretty_print(one_line => 1) . "\n"; |
1962
|
|
|
|
|
|
|
} else { |
1963
|
0
|
|
|
|
|
|
$str .= $self->from->pretty_print(prefix => $prefix); |
1964
|
|
|
|
|
|
|
} |
1965
|
|
|
|
|
|
|
|
1966
|
0
|
0
|
0
|
|
|
|
if (($op eq 'INSERT') || ($op eq 'UPDATE')) { |
1967
|
0
|
|
|
|
|
|
$str .= $self->__pretty_print_inputs(prefix => $prefix); |
1968
|
|
|
|
|
|
|
} |
1969
|
0
|
0
|
0
|
|
|
|
if ($op eq 'INSERT' && $self->input_subquery()) { |
1970
|
0
|
|
|
|
|
|
$str .= $prefix . "INPUT SUBQUERY:\n"; |
1971
|
0
|
|
|
|
|
|
$str .= $self->input_subquery->statement->pretty_print(prefix => $prefix . ' '); |
1972
|
|
|
|
|
|
|
} |
1973
|
0
|
0
|
|
|
|
|
if ($op ne 'INSERT') { |
1974
|
0
|
0
|
|
|
|
|
if ($self->_cooked_where) { |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
1975
|
0
|
|
|
|
|
|
$str .= 'WHERE[cooked] ' . $self->_cooked_where() . "\n"; |
1976
|
|
|
|
|
|
|
} elsif ($self->raw_where) { |
1977
|
0
|
|
|
|
|
|
$str .= 'WHERE[raw] ' . $self->raw_where() . "\n"; |
1978
|
|
|
|
|
|
|
} elsif ($self->where) { |
1979
|
0
|
|
|
|
|
|
$str .= $self->where->pretty_print(prefix => $prefix); |
1980
|
|
|
|
|
|
|
} |
1981
|
|
|
|
|
|
|
} |
1982
|
0
|
0
|
|
|
|
|
if ($self->order_by) { |
1983
|
0
|
|
|
|
|
|
$str .= $self->order_by->pretty_print(prefix => $prefix); |
1984
|
|
|
|
|
|
|
} |
1985
|
0
|
0
|
|
|
|
|
if (defined $self->limit) { |
1986
|
0
|
|
|
|
|
|
$str .= $prefix . 'LIMIT ' . $self->limit; |
1987
|
0
|
0
|
|
|
|
|
if (defined $self->offset) { |
1988
|
0
|
|
|
|
|
|
$str .= 'OFFSET ' . $self->offset; |
1989
|
|
|
|
|
|
|
} |
1990
|
|
|
|
|
|
|
} |
1991
|
|
|
|
|
|
|
|
1992
|
0
|
|
|
|
|
|
return $str; |
1993
|
|
|
|
|
|
|
} |
1994
|
|
|
|
|
|
|
sub __pretty_print_inputs { |
1995
|
0
|
|
|
0
|
|
|
my $self = shift; |
1996
|
0
|
|
|
|
|
|
my %args = @_; |
1997
|
0
|
|
0
|
|
|
|
my $prefix = $args{prefix} || ''; |
1998
|
0
|
|
|
|
|
|
my $str = $prefix . "INPUTS:\n"; |
1999
|
0
|
|
|
|
|
|
foreach my $i ($self->inputs) { |
2000
|
0
|
|
|
|
|
|
$str .= $prefix . ' '; |
2001
|
0
|
|
|
|
|
|
$str .= $i->{column}->pretty_print(one_line => 1); |
2002
|
0
|
0
|
|
|
|
|
if ($i->{param}) { |
2003
|
0
|
|
|
|
|
|
$str .= ' = '; |
2004
|
0
|
|
|
|
|
|
$str .= $i->{param}->pretty_print(one_line =>1); |
2005
|
0
|
|
|
|
|
|
$str .= "\n"; |
2006
|
|
|
|
|
|
|
} |
2007
|
|
|
|
|
|
|
} |
2008
|
0
|
|
|
|
|
|
return $str; |
2009
|
|
|
|
|
|
|
} |
2010
|
|
|
|
|
|
|
|
2011
|
|
|
|
|
|
|
|
2012
|
|
|
|
|
|
|
=head2 $sql->set_default_output_aliases(); |
2013
|
|
|
|
|
|
|
|
2014
|
|
|
|
|
|
|
Ensures that each table and output column has |
2015
|
|
|
|
|
|
|
an alias. If a table or column already has |
2016
|
|
|
|
|
|
|
an alias, it is left alone. |
2017
|
|
|
|
|
|
|
|
2018
|
|
|
|
|
|
|
=cut |
2019
|
|
|
|
|
|
|
|
2020
|
|
|
|
|
|
|
sub set_default_output_aliases { |
2021
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
2022
|
|
|
|
|
|
|
|
2023
|
0
|
|
|
|
|
|
$self->__set_default_table_aliases(); |
2024
|
0
|
|
|
|
|
|
$self->__set_default_column_aliases(); |
2025
|
|
|
|
|
|
|
} |
2026
|
|
|
|
|
|
|
|
2027
|
|
|
|
|
|
|
sub __set_default_column_aliases { |
2028
|
0
|
|
|
0
|
|
|
my $self = shift; |
2029
|
|
|
|
|
|
|
|
2030
|
|
|
|
|
|
|
# Make sure each output column has an alias |
2031
|
0
|
|
|
|
|
|
foreach my $oc (grep { !defined($_->alias)} $self->output_columns) { |
|
0
|
|
|
|
|
|
|
2032
|
0
|
|
|
|
|
|
my $exp = $oc->expression(); |
2033
|
0
|
0
|
|
|
|
|
if ($exp->is_column()) { |
2034
|
0
|
|
|
|
|
|
my $col = $oc->expression(); |
2035
|
0
|
|
|
|
|
|
$oc->alias($col->table->alias() . '_' . $col->column); |
2036
|
|
|
|
|
|
|
} else { |
2037
|
|
|
|
|
|
|
# Make something up |
2038
|
0
|
|
|
|
|
|
$oc->alias($self->new_column_alias()); |
2039
|
|
|
|
|
|
|
} |
2040
|
|
|
|
|
|
|
} |
2041
|
|
|
|
|
|
|
} |
2042
|
|
|
|
|
|
|
|
2043
|
|
|
|
|
|
|
sub __set_default_table_aliases { |
2044
|
0
|
|
|
0
|
|
|
my $self = shift; |
2045
|
0
|
|
|
|
|
|
my $counter = 0; |
2046
|
|
|
|
|
|
|
|
2047
|
0
|
|
|
|
|
|
my %tables_by_alias = map { $_->alias => $_ } grep { defined($_->alias) } $self->tables; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
2048
|
|
|
|
|
|
|
|
2049
|
|
|
|
|
|
|
# Make sure each table has an alias |
2050
|
|
|
|
|
|
|
# Be sure to exclude those whose names look like a alias macro! |
2051
|
0
|
|
|
|
|
|
foreach my $table (grep {!defined($_->alias)} $self->tables) { |
|
0
|
|
|
|
|
|
|
2052
|
0
|
|
|
|
|
|
my $alias = 'ts' . $counter; |
2053
|
0
|
|
|
|
|
|
while (exists $tables_by_alias{$alias}) { |
2054
|
0
|
|
|
|
|
|
$counter++; |
2055
|
0
|
|
|
|
|
|
$alias = 'ts' . $counter; |
2056
|
|
|
|
|
|
|
} |
2057
|
0
|
|
|
|
|
|
$table->alias($alias); |
2058
|
0
|
|
|
|
|
|
$tables_by_alias{$alias} = $table; |
2059
|
|
|
|
|
|
|
} |
2060
|
|
|
|
|
|
|
|
2061
|
|
|
|
|
|
|
} |
2062
|
|
|
|
|
|
|
|
2063
|
|
|
|
|
|
|
|
2064
|
|
|
|
|
|
|
sub clone { |
2065
|
0
|
|
|
0
|
0
|
|
my $self = shift; |
2066
|
0
|
|
|
|
|
|
my $class = ref $self; |
2067
|
|
|
|
|
|
|
|
2068
|
0
|
|
|
|
|
|
my $other = $class->new($self->operation()); |
2069
|
|
|
|
|
|
|
|
2070
|
|
|
|
|
|
|
# Scalars |
2071
|
0
|
0
|
|
|
|
|
if (defined $self->limit) { $other->limit($self->limit()); } |
|
0
|
|
|
|
|
|
|
2072
|
0
|
0
|
|
|
|
|
if (defined $self->offset) { $other->offset($self->offset()); } |
|
0
|
|
|
|
|
|
|
2073
|
0
|
0
|
|
|
|
|
if (defined $self->raw_where) { |
2074
|
0
|
|
|
|
|
|
$other->raw_where($self->raw_where()); |
2075
|
0
|
0
|
|
|
|
|
if ($self->_cooked_where) { $other->_cooked_where($self->_cooked_where); } |
|
0
|
|
|
|
|
|
|
2076
|
0
|
0
|
|
|
|
|
if ($self->_raw_where_execargs) { $other->_raw_where_execargs($self->_raw_where_execargs); } |
|
0
|
|
|
|
|
|
|
2077
|
0
|
0
|
|
|
|
|
if ($self->_raw_where_params) { $other->_raw_where_params([ map { $_->clone() } $self->_raw_where_params ]); } |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
2078
|
|
|
|
|
|
|
} |
2079
|
|
|
|
|
|
|
|
2080
|
|
|
|
|
|
|
# Single Objects |
2081
|
0
|
0
|
|
|
|
|
if ($self->where) { $other->where( $self->where->clone() ); } |
|
0
|
|
|
|
|
|
|
2082
|
0
|
0
|
|
|
|
|
if ($self->get('table')) { $other->table( $self->table->clone() ); } |
|
0
|
|
|
|
|
|
|
2083
|
0
|
0
|
|
|
|
|
if ($self->from) { $other->from( $self->from->clone() ); } |
|
0
|
|
|
|
|
|
|
2084
|
0
|
0
|
|
|
|
|
if ($self->order_by) { $other->order_by( $self->order_by->clone() ); } |
|
0
|
|
|
|
|
|
|
2085
|
0
|
0
|
|
|
|
|
if ($self->input_subquery) { $other->input_subquery($self->input_subquery->clone()); } |
|
0
|
|
|
|
|
|
|
2086
|
|
|
|
|
|
|
|
2087
|
|
|
|
|
|
|
# Lists of things |
2088
|
0
|
|
|
|
|
|
foreach my $input (@{$self->{inputs}}) { |
|
0
|
|
|
|
|
|
|
2089
|
0
|
|
|
|
|
|
push @{$other->{inputs}}, |
|
0
|
|
|
|
|
|
|
2090
|
|
|
|
|
|
|
{ |
2091
|
|
|
|
|
|
|
column => $input->{column}->clone(), |
2092
|
|
|
|
|
|
|
param => $input->{param}->clone(), |
2093
|
|
|
|
|
|
|
}; |
2094
|
|
|
|
|
|
|
} |
2095
|
0
|
|
|
|
|
|
foreach my $output ($self->output_columns) { |
2096
|
0
|
|
|
|
|
|
$other->add_output($output->clone()); |
2097
|
|
|
|
|
|
|
} |
2098
|
|
|
|
|
|
|
|
2099
|
0
|
|
|
|
|
|
return $other; |
2100
|
|
|
|
|
|
|
|
2101
|
|
|
|
|
|
|
} |
2102
|
|
|
|
|
|
|
|
2103
|
|
|
|
|
|
|
|
2104
|
|
|
|
|
|
|
sub DESTROY { |
2105
|
0
|
|
|
0
|
|
|
my $sql = shift; |
2106
|
|
|
|
|
|
|
# Break links between all objects |
2107
|
|
|
|
|
|
|
|
2108
|
0
|
0
|
0
|
|
|
|
if ($sql->from && $sql->from->root_relation) { $sql->from->root_relation->__break_links(); } |
|
0
|
|
|
|
|
|
|
2109
|
0
|
0
|
0
|
|
|
|
if ($sql->where && $sql->where->root_criterion) { $sql->where->root_criterion->__break_links(); } |
|
0
|
|
|
|
|
|
|
2110
|
|
|
|
|
|
|
} |
2111
|
|
|
|
|
|
|
|
2112
|
|
|
|
|
|
|
1; |