File Coverage

blib/lib/SQL/Query.pm
Criterion Covered Total %
statement 37 37 100.0
branch 2 2 100.0
condition n/a
subroutine 10 10 100.0
pod 5 5 100.0
total 54 54 100.0


line stmt bran cond sub pod time code
1             package SQL::Query;
2              
3 3     3   92227 use strict;
  3         7  
  3         117  
4 3     3   16 use warnings;
  3         7  
  3         94  
5 3     3   16 use vars qw($VERSION);
  3         5  
  3         173  
6              
7             $VERSION = 0.01;
8              
9 3     3   1590 use Abstract::Meta::Class ':all';
  3         16590  
  3         710  
10 3     3   690 use SQL::Entity::Column ':all';
  3         8  
  3         1704  
11              
12             =head1 NAME
13              
14             SQL::Query - Sql query generator for database entities.
15              
16             =head1 SYNOPSIS
17              
18             my $entity = SQL::Entity->new(
19             name => 'emp',
20             unique_expression => 'rowid',
21             columns => [
22             sql_column(name => 'ename'),
23             sql_column(name => 'empno'),
24             sql_column(name => 'deptno')
25             ],
26             indexes => [
27             sql_index(name => 'foo', columns => ['empno', hint => 'INDEX_ASC(emp FORCE_ORDER)'])
28             ].
29             order_index => 'foo',
30             );
31              
32             my $query = SQL::Query->new(entity => $entity);
33             my ($sql, $bind_variables) = $query->query();
34              
35             =head1 DESCRIPTION
36              
37             Generates sql for entity definition,
38              
39             - navigation feature (limit/offset)
40              
41             - ad hoc query optimialization (for where condition uses (subquerties or join dynamically))
42              
43             Lets into account the following use cases:
44              
45             1, Retrieve employers with thier department:
46             Physically it requries retrieving N rows starts from X row (offset/limit),
47             so assuming that you have to fetch physically only 50 rows at the time
48             from a few millions rows subquery will executes 50 times in the outer results.
49             It may be faster the the join clause, especially when using single table CBO hint that
50             is forcing result order rather then ORDER BY ...
51              
52             =begin text
53              
54             For Oracle you will have:
55              
56             SELECT
57             t.*,
58             (SELECT dname FROM dept d WHERE d.deptno = t.deptno) as dname
59             FROM (
60             SELECT /*+ INDEX_ASC(t FORCE_ORDER) */
61             t.*,
62             ROWNUM as THE_ROWNUM
63             FROM
64             emp t
65             WHERE ROWNUM < 120
66             ) t
67             WHERE THE_ROWNUM > 100
68              
69              
70             =end text
71              
72             2, Retrieve employers with thier department for departaments 'hr','ho' :
73              
74             =begin text
75              
76             SELECT
77             t.*
78             FROM (
79             SELECT /*+ INDEX_ASC(FORCE_ORDER) */
80             t.*,
81             d.dname,
82             ROWNUM as THE_ROWNUM
83             FROM
84             emp t
85             JOIN
86             dept d ON (d.deptno = t.deptno)
87             WHERE d.dname IN ('hr', 'ho')
88             AND ROWNUM < 120
89             ) t
90             WHERE THE_ROWNUM > 100
91              
92             =end text
93              
94             =head2 EXPORT
95              
96             None.
97              
98             =head2 ATTRIBUTES
99              
100             =over
101              
102             =item limit
103              
104             =cut
105              
106             has '$.limit' => (default => '20');
107              
108              
109             =item offset
110              
111             =cut
112              
113             has '$.offset' => (default => '1');
114              
115              
116             =item dialect
117              
118             =cut
119              
120             has '$.dialect' => (default => 'Oracle');
121              
122              
123             =item entity
124              
125             =cut
126              
127             has '$.entity' => (associated_class => 'SQL::Entity');
128              
129              
130             =item _entity_limit_wrapper
131              
132             Stores entity object that is wrapped by limit implementation.
133              
134             =cut
135              
136              
137             has '$._entity_limit_wrapper';
138              
139              
140             =back
141              
142             =head2 METHODS
143              
144             =over
145              
146             =item initialise
147              
148             =cut
149              
150             sub initialise {
151 5     5 1 1737 my ($self) = @_;
152 5         20 my $class = $self->load_module($self->dialect);
153 5         11 my $entity_manager;
154 5         17 my $entity = $self->entity->clone;
155 5         28 $self->_entity_limit_wrapper(bless $entity, $class);
156             }
157              
158              
159              
160              
161             =item query
162              
163             Returns sql statement and bind variables array ref.
164             Takes optionally array ref of the requested columns(undef return all columns in projection), condition object,
165              
166             =cut
167              
168             sub query {
169 6     6 1 1712 my ($self, $requested_columns, $condition) = @_;
170 6         19 my $wrapper = $self->_entity_limit_wrapper;
171 6         58 my ($sql, $bind_variables) = $wrapper->query($self->offset, $self->limit, $requested_columns, $condition);
172 6         21 ($sql, $bind_variables);
173             }
174              
175              
176             =item set_sql_template_parameters
177              
178             =cut
179              
180             sub set_sql_template_parameters {
181 1     1 1 22 my ($self, $value) = @_;
182 1         3 $self->_entity_limit_wrapper->set_sql_template_parameters($value);
183             }
184              
185             =item order_index
186              
187             Change order_index for associated entity
188              
189             =cut
190              
191             sub order_index {
192 1     1 1 1092 my ($self, $index_name) = @_;
193 1         5 $self->_entity_limit_wrapper->set_order_index($index_name);
194             }
195              
196              
197             =item load_module
198              
199             Loads specyfic limit module wrapper.
200              
201             =cut
202              
203             {
204             my %loaded_modules = ();
205             sub load_module {
206 5     5 1 36 my ($self, $module) = @_;
207 5         20 my $module_name = __PACKAGE__ . "::Limit::\u$module";
208 5 100       25 return $loaded_modules{$module_name} if $loaded_modules{$module_name};
209 4         7 my $module_to_load = $module_name;
210 4         20 $module_to_load =~ s/::/\//g;
211 4         7 eval { require "${module_to_load}.pm" };
  4         2823  
212 4         16 $loaded_modules{$module_name} = $module_name;
213 4         9 $module_name;
214             }
215             }
216              
217              
218             1;
219              
220             __END__