sub
__rows_bindtype () {
+{
sqlt_datatype
=>
'integer'
}
}
sub
__offset_bindtype () {
+{
sqlt_datatype
=>
'integer'
}
}
sub
__total_bindtype () {
+{
sqlt_datatype
=>
'integer'
}
}
sub
_LimitOffset {
my
(
$self
,
$sql
,
$rs_attrs
,
$rows
,
$offset
) =
@_
;
$sql
.=
$self
->_parse_rs_attrs(
$rs_attrs
) .
" LIMIT ?"
;
push
@{
$self
->{limit_bind}}, [
$self
->
__rows_bindtype
=>
$rows
];
if
(
$offset
) {
$sql
.=
" OFFSET ?"
;
push
@{
$self
->{limit_bind}}, [
$self
->
__offset_bindtype
=>
$offset
];
}
return
$sql
;
}
sub
_LimitXY {
my
(
$self
,
$sql
,
$rs_attrs
,
$rows
,
$offset
) =
@_
;
$sql
.=
$self
->_parse_rs_attrs(
$rs_attrs
) .
" LIMIT "
;
if
(
$offset
) {
$sql
.=
'?, '
;
push
@{
$self
->{limit_bind}}, [
$self
->
__offset_bindtype
=>
$offset
];
}
$sql
.=
'?'
;
push
@{
$self
->{limit_bind}}, [
$self
->
__rows_bindtype
=>
$rows
];
return
$sql
;
}
sub
_RowNumberOver {
my
(
$self
,
$sql
,
$rs_attrs
,
$rows
,
$offset
) =
@_
;
my
$sq_attrs
=
$self
->_subqueried_limit_attrs (
$sql
,
$rs_attrs
);
my
$requested_order
= (
delete
$rs_attrs
->{order_by}) ||
$self
->_rno_default_order;
local
$self
->{order_bind};
my
$rno_ord
=
$self
->_order_by (
$requested_order
);
push
@{
$self
->{select_bind}}, @{
$self
->{order_bind}};
my
$mid_sel
=
$sq_attrs
->{selection_outer};
if
(
my
$extra_order_sel
=
$sq_attrs
->{order_supplement}) {
for
my
$extra_col
(
sort
{
$extra_order_sel
->{
$a
} cmp
$extra_order_sel
->{
$b
} }
keys
%$extra_order_sel
) {
$sq_attrs
->{selection_inner} .=
sprintf
(
', %s AS %s'
,
$extra_col
,
$extra_order_sel
->{
$extra_col
},
);
}
}
for
my
$map
(
$sq_attrs
->{order_supplement},
$sq_attrs
->{outer_renames}) {
for
my
$col
(
sort
{ (
length
$b
) <=> (
length
$a
) }
keys
%{
$map
||{}} ) {
my
$re_col
=
quotemeta
(
$col
);
$rno_ord
=~ s/
$re_col
/
$map
->{
$col
}/;
}
}
my
$group_having
=
$self
->_parse_rs_attrs(
$rs_attrs
);
my
$qalias
=
$self
->_quote (
$rs_attrs
->{alias});
my
$idx_name
=
$self
->_quote (
'rno__row__index'
);
push
@{
$self
->{limit_bind}}, [
$self
->
__offset_bindtype
=>
$offset
+ 1], [
$self
->
__total_bindtype
=>
$offset
+
$rows
];
return
<<EOS;
SELECT $sq_attrs->{selection_outer} FROM (
SELECT $mid_sel, ROW_NUMBER() OVER( $rno_ord ) AS $idx_name FROM (
SELECT $sq_attrs->{selection_inner} $sq_attrs->{query_leftover}${group_having}
) $qalias
) $qalias WHERE $idx_name >= ? AND $idx_name <= ?
EOS
}
sub
_rno_default_order {
return
undef
;
}
sub
_SkipFirst {
my
(
$self
,
$sql
,
$rs_attrs
,
$rows
,
$offset
) =
@_
;
$sql
=~ s/^ \s* SELECT \s+ //ix
or
$self
->throw_exception(
"Unrecognizable SELECT: $sql"
);
return
sprintf
(
'SELECT %s%s%s%s'
,
$offset
?
do
{
push
@{
$self
->{pre_select_bind}}, [
$self
->
__offset_bindtype
=>
$offset
];
'SKIP ? '
}
:
''
,
do
{
push
@{
$self
->{pre_select_bind}}, [
$self
->
__rows_bindtype
=>
$rows
];
'FIRST ? '
},
$sql
,
$self
->_parse_rs_attrs (
$rs_attrs
),
);
}
sub
_FirstSkip {
my
(
$self
,
$sql
,
$rs_attrs
,
$rows
,
$offset
) =
@_
;
$sql
=~ s/^ \s* SELECT \s+ //ix
or
$self
->throw_exception(
"Unrecognizable SELECT: $sql"
);
return
sprintf
(
'SELECT %s%s%s%s'
,
do
{
push
@{
$self
->{pre_select_bind}}, [
$self
->
__rows_bindtype
=>
$rows
];
'FIRST ? '
},
$offset
?
do
{
push
@{
$self
->{pre_select_bind}}, [
$self
->
__offset_bindtype
=>
$offset
];
'SKIP ? '
}
:
''
,
$sql
,
$self
->_parse_rs_attrs (
$rs_attrs
),
);
}
sub
_RowNum {
my
(
$self
,
$sql
,
$rs_attrs
,
$rows
,
$offset
) =
@_
;
my
$sq_attrs
=
$self
->_subqueried_limit_attrs (
$sql
,
$rs_attrs
);
my
$qalias
=
$self
->_quote (
$rs_attrs
->{alias});
my
$idx_name
=
$self
->_quote (
'rownum__index'
);
my
$order_group_having
=
$self
->_parse_rs_attrs(
$rs_attrs
);
if
(!
$offset
) {
push
@{
$self
->{limit_bind}}, [
$self
->
__rows_bindtype
=>
$rows
];
return
<<EOS;
SELECT $sq_attrs->{selection_outer} FROM (
SELECT $sq_attrs->{selection_inner} $sq_attrs->{query_leftover}${order_group_having}
) $qalias WHERE ROWNUM <= ?
EOS
}
if
(
$rs_attrs
->{order_by}
and
$rs_attrs
->{result_source}->storage->_order_by_is_stable(
@{
$rs_attrs
}{
qw/from order_by where/
}
)
) {
push
@{
$self
->{limit_bind}}, [
$self
->
__total_bindtype
=>
$offset
+
$rows
], [
$self
->
__offset_bindtype
=>
$offset
+ 1 ];
return
<<EOS;
SELECT $sq_attrs->{selection_outer} FROM (
SELECT $sq_attrs->{selection_outer}, ROWNUM AS $idx_name FROM (
SELECT $sq_attrs->{selection_inner} $sq_attrs->{query_leftover}${order_group_having}
) $qalias WHERE ROWNUM <= ?
) $qalias WHERE $idx_name >= ?
EOS
}
else
{
push
@{
$self
->{limit_bind}}, [
$self
->
__offset_bindtype
=>
$offset
+ 1 ], [
$self
->
__total_bindtype
=>
$offset
+
$rows
];
return
<<EOS;
SELECT $sq_attrs->{selection_outer} FROM (
SELECT $sq_attrs->{selection_outer}, ROWNUM AS $idx_name FROM (
SELECT $sq_attrs->{selection_inner} $sq_attrs->{query_leftover}${order_group_having}
) $qalias
) $qalias WHERE $idx_name BETWEEN ? AND ?
EOS
}
}
sub
_prep_for_skimming_limit {
my
(
$self
,
$sql
,
$rs_attrs
) =
@_
;
my
$sq_attrs
=
$self
->_subqueried_limit_attrs (
$sql
,
$rs_attrs
);
my
$requested_order
=
delete
$rs_attrs
->{order_by};
$sq_attrs
->{order_by_requested} =
$self
->_order_by (
$requested_order
);
$sq_attrs
->{grpby_having} =
$self
->_parse_rs_attrs (
$rs_attrs
);
if
(!
$rs_attrs
->{offset}) {
$sq_attrs
->{order_by_inner} =
$sq_attrs
->{order_by_requested};
}
else
{
$sq_attrs
->{quoted_rs_alias} =
$self
->_quote (
$rs_attrs
->{alias});
local
$self
->{order_bind};
my
$inner_order
;
if
(
$sq_attrs
->{order_by_requested}) {
$self
->throw_exception (
'Unable to safely perform "skimming type" limit with supplied unstable order criteria'
)
unless
(
$rs_attrs
->{result_source}->schema->storage->_order_by_is_stable(
$rs_attrs
->{from},
$requested_order
,
$rs_attrs
->{where},
));
$inner_order
=
$requested_order
;
}
else
{
$inner_order
= [
map
{
"$rs_attrs->{alias}.$_"
}
( @{
$rs_attrs
->{result_source}->_identifying_column_set
||
$self
->throw_exception(
sprintf
(
'Unable to auto-construct stable order criteria for "skimming type" limit '
.
"dialect based on source '%s'"
,
$rs_attrs
->{result_source}->name) );
} )
];
}
$sq_attrs
->{order_by_inner} =
$self
->_order_by (
$inner_order
);
my
@out_chunks
;
for
my
$ch
(
$self
->_order_by_chunks (
$inner_order
)) {
$ch
=
$ch
->[0]
if
ref
$ch
eq
'ARRAY'
;
(
$ch
,
my
$is_desc
) =
$self
->_split_order_chunk(
$ch
);
push
@out_chunks
, { (
$is_desc
?
'-asc'
:
'-desc'
) => \
$ch
};
}
$sq_attrs
->{order_by_middle} =
$self
->_order_by (\
@out_chunks
);
$sq_attrs
->{selection_middle} =
$sq_attrs
->{selection_outer};
if
(
my
$extra_order_sel
=
$sq_attrs
->{order_supplement}) {
for
my
$extra_col
(
sort
{
$extra_order_sel
->{
$a
} cmp
$extra_order_sel
->{
$b
} }
keys
%$extra_order_sel
) {
$sq_attrs
->{selection_inner} .=
sprintf
(
', %s AS %s'
,
$extra_col
,
$extra_order_sel
->{
$extra_col
},
);
$sq_attrs
->{selection_middle} .=
', '
.
$extra_order_sel
->{
$extra_col
};
}
push
@{
$self
->{select_bind}}, @{
$self
->{order_bind}};
}
for
my
$map
(
$sq_attrs
->{order_supplement},
$sq_attrs
->{outer_renames}) {
for
my
$col
(
sort
{ (
length
$b
) <=> (
length
$a
) }
keys
%{
$map
||{}}) {
my
$re_col
=
quotemeta
(
$col
);
$_
=~ s/
$re_col
/
$map
->{
$col
}/
for
(
$sq_attrs
->{order_by_middle},
$sq_attrs
->{order_by_requested});
}
}
}
$sq_attrs
;
}
sub
_Top {
my
(
$self
,
$sql
,
$rs_attrs
,
$rows
,
$offset
) =
@_
;
my
$lim
=
$self
->_prep_for_skimming_limit(
$sql
,
$rs_attrs
);
$sql
=
sprintf
(
'SELECT TOP %u %s %s %s %s'
,
$rows
+ (
$offset
||0),
$offset
?
$lim
->{selection_inner} :
$lim
->{selection_original},
$lim
->{query_leftover},
$lim
->{grpby_having},
$lim
->{order_by_inner},
);
$sql
=
sprintf
(
'SELECT TOP %u %s FROM ( %s ) %s %s'
,
$rows
,
$lim
->{selection_middle},
$sql
,
$lim
->{quoted_rs_alias},
$lim
->{order_by_middle},
)
if
$offset
;
$sql
=
sprintf
(
'SELECT %s FROM ( %s ) %s %s'
,
$lim
->{selection_outer},
$sql
,
$lim
->{quoted_rs_alias},
$lim
->{order_by_requested},
)
if
$offset
and (
$lim
->{order_by_requested} or
$lim
->{selection_middle} ne
$lim
->{selection_outer}
);
return
$sql
;
}
sub
_FetchFirst {
my
(
$self
,
$sql
,
$rs_attrs
,
$rows
,
$offset
) =
@_
;
my
$lim
=
$self
->_prep_for_skimming_limit(
$sql
,
$rs_attrs
);
$sql
=
sprintf
(
'SELECT %s %s %s %s FETCH FIRST %u ROWS ONLY'
,
$offset
?
$lim
->{selection_inner} :
$lim
->{selection_original},
$lim
->{query_leftover},
$lim
->{grpby_having},
$lim
->{order_by_inner},
$rows
+ (
$offset
||0),
);
$sql
=
sprintf
(
'SELECT %s FROM ( %s ) %s %s FETCH FIRST %u ROWS ONLY'
,
$lim
->{selection_middle},
$sql
,
$lim
->{quoted_rs_alias},
$lim
->{order_by_middle},
$rows
,
)
if
$offset
;
$sql
=
sprintf
(
'SELECT %s FROM ( %s ) %s %s'
,
$lim
->{selection_outer},
$sql
,
$lim
->{quoted_rs_alias},
$lim
->{order_by_requested},
)
if
$offset
and (
$lim
->{order_by_requested} or
$lim
->{selection_middle} ne
$lim
->{selection_outer}
);
return
$sql
;
}
sub
_GenericSubQ {
my
(
$self
,
$sql
,
$rs_attrs
,
$rows
,
$offset
) =
@_
;
my
$main_rsrc
=
$rs_attrs
->{result_source};
$self
->throw_exception (
'Generic Subquery Limit does not work on resultsets without an order. Provide a stable, '
.
'main-table-based order criteria.'
)
unless
$rs_attrs
->{order_by};
my
$usable_order_colinfo
=
$main_rsrc
->storage->_extract_colinfo_of_stable_main_source_order_by_portion(
$rs_attrs
);
$self
->throw_exception(
'Generic Subquery Limit can not work with order criteria based on sources other than the main one'
)
if
(
!
keys
%{
$usable_order_colinfo
||{}}
or
grep
{
$_
->{-source_alias} ne
$rs_attrs
->{alias} }
(
values
%$usable_order_colinfo
)
);
my
$supplied_order
=
delete
$rs_attrs
->{order_by};
my
@order_bits
=
do
{
local
$self
->{quote_char};
local
$self
->{order_bind};
map
{
ref
$_
?
$_
->[0] :
$_
}
$self
->_order_by_chunks (
$supplied_order
)
};
$#order_bits
= ( (
keys
%$usable_order_colinfo
) - 1 );
if
(
$self
->quote_char) {
$usable_order_colinfo
->{
$self
->_quote(
$_
)} =
$usable_order_colinfo
->{
$_
}
for
keys
%$usable_order_colinfo
;
}
my
$count_tbl_alias
=
'rownum__emulation'
;
my
$main_alias
=
$rs_attrs
->{alias};
my
$main_tbl_name
=
$main_rsrc
->name;
my
(
@unqualified_names
,
@qualified_names
,
@is_desc
,
@new_order_by
);
for
my
$bit
(
@order_bits
) {
(
$bit
,
my
$is_desc
) =
$self
->_split_order_chunk(
$bit
);
push
@is_desc
,
$is_desc
;
push
@unqualified_names
,
$usable_order_colinfo
->{
$bit
}{-colname};
push
@qualified_names
,
$usable_order_colinfo
->{
$bit
}{-fq_colname};
push
@new_order_by
, { (
$is_desc
?
'-desc'
:
'-asc'
) =>
$usable_order_colinfo
->{
$bit
}{-fq_colname} };
};
my
(
@where_cond
,
@skip_colpair_stack
);
for
my
$i
(0 ..
$#order_bits
) {
my
$ci
=
$usable_order_colinfo
->{
$order_bits
[
$i
]};
my
(
$subq_col
,
$main_col
) =
map
{
"$_.$ci->{-colname}"
} (
$count_tbl_alias
,
$main_alias
);
my
$cur_cond
= {
$subq_col
=> { (
$is_desc
[
$i
] ?
'>'
:
'<'
) => {
-ident
=>
$main_col
} } };
push
@skip_colpair_stack
, [
{
$main_col
=> {
-ident
=>
$subq_col
} },
];
if
(
$ci
->{is_nullable}) {
push
@{
$skip_colpair_stack
[-1]}, {
$main_col
=>
undef
,
$subq_col
=>
undef
};
$cur_cond
= [
{
(
$is_desc
[
$i
] ?
$subq_col
:
$main_col
) => {
'!='
,
undef
},
(
$is_desc
[
$i
] ?
$main_col
:
$subq_col
) =>
undef
,
},
{
$subq_col
=> {
'!='
,
undef
},
$main_col
=> {
'!='
,
undef
},
-and
=>
$cur_cond
,
},
];
}
push
@where_cond
, {
'-and'
, => [
@skip_colpair_stack
[0..
$i
-1],
$cur_cond
] };
}
my
$counted_where
=
do
{
local
$self
->{where_bind};
$self
->where(\
@where_cond
);
};
my
$rownum_cond
;
if
(
$offset
) {
$rownum_cond
=
'BETWEEN ? AND ?'
;
push
@{
$self
->{limit_bind}},
[
$self
->
__offset_bindtype
=>
$offset
],
[
$self
->
__total_bindtype
=>
$offset
+
$rows
- 1]
;
}
else
{
$rownum_cond
=
'< ?'
;
push
@{
$self
->{limit_bind}},
[
$self
->
__rows_bindtype
=>
$rows
]
;
}
my
$inner_order_sql
=
do
{
local
$self
->{order_bind};
my
$s
=
$self
->_order_by (\
@new_order_by
);
$self
->throw_exception(
'Inner gensubq order may not contain binds... something went wrong'
)
if
@{
$self
->{order_bind}};
$s
;
};
my
$sq_attrs
=
$self
->_subqueried_limit_attrs (
$sql
, {
%$rs_attrs
,
order_by
=> \
@new_order_by
}
);
my
$in_sel
=
$sq_attrs
->{selection_inner};
$in_sel
.=
", $_"
for
sort
keys
%{
$sq_attrs
->{order_supplement}};
my
$group_having_sql
=
$self
->_parse_rs_attrs(
$rs_attrs
);
return
sprintf
("
SELECT
$sq_attrs
->{selection_outer}
FROM (
SELECT
$in_sel
$sq_attrs
->{query_leftover}${group_having_sql}
)
%s
WHERE ( SELECT COUNT(*) FROM
%s
%s
$counted_where
)
$rownum_cond
$inner_order_sql
",
map
{
$self
->_quote (
$_
) } (
$rs_attrs
->{alias},
$main_tbl_name
,
$count_tbl_alias
,
));
}
sub
_subqueried_limit_attrs {
my
(
$self
,
$proto_sql
,
$rs_attrs
) =
@_
;
$self
->throw_exception(
'Limit dialect implementation usable only in the context of DBIC (missing $rs_attrs)'
)
unless
ref
(
$rs_attrs
) eq
'HASH'
;
unless
(
$rs_attrs
->{_selector_sql}
and
$proto_sql
=~ s/^ \s* SELECT \s* \Q
$rs_attrs
->{_selector_sql}//ix
) {
$self
->throw_exception(
"Unrecognizable SELECT: $proto_sql"
);
}
my
(
$re_sep
,
$re_alias
) =
map
{
quotemeta
$_
} (
$self
->{name_sep},
$rs_attrs
->{alias} );
my
(
@sel
,
$in_sel_index
);
for
my
$i
(0 .. $
my
$s
=
$rs_attrs
->{
select
}[
$i
];
my
$sql_alias
= (
ref
$s
) eq
'HASH'
?
$s
->{-as} :
undef
;
my
(
$sql_sel
) =
$self
->_recurse_fields (
$s
);
push
@sel
, {
arg
=>
$s
,
sql
=>
$sql_sel
,
unquoted_sql
=>
do
{
local
$self
->{quote_char};
(
$self
->_recurse_fields (
$s
))[0];
},
as
=>
$sql_alias
||
$rs_attrs
->{as}[
$i
]
||
$self
->throw_exception(
"Select argument $i ($s) without corresponding 'as'"
)
,
};
$in_sel_index
->{
$sql_sel
}++
unless
$sql_sel
=~ / (?: ^ | \W ) \? (?: \W | $ ) /x;
$in_sel_index
->{
$self
->_quote (
$sql_alias
)}++
if
$sql_alias
;
if
(!
ref
$s
&&
$sql_sel
=~ /
$re_sep
(.+) $/x) {
$in_sel_index
->{$1}++;
}
}
my
(
$sel
,
$renamed
);
for
my
$node
(
@sel
) {
push
@{
$sel
->{original}},
$node
->{sql};
if
(
!
$in_sel_index
->{
$node
->{sql}}
or
$node
->{as} =~ / (?<! ^
$re_alias
) \. /x
or
$node
->{unquoted_sql} =~ / (?<! ^
$re_alias
)
$re_sep
/x
) {
$node
->{as} =
$self
->_unqualify_colname(
$node
->{as});
my
$quoted_as
=
$self
->_quote(
$node
->{as});
push
@{
$sel
->{inner}},
sprintf
'%s AS %s'
,
$node
->{sql},
$quoted_as
;
push
@{
$sel
->{outer}},
$quoted_as
;
$renamed
->{
$node
->{sql}} =
$quoted_as
;
}
else
{
push
@{
$sel
->{inner}},
$node
->{sql};
push
@{
$sel
->{outer}},
$self
->_quote (
ref
$node
->{arg} ?
$node
->{as} :
$node
->{arg});
}
}
my
$extra_order_sel
;
for
my
$chunk
(
$self
->_order_by_chunks (
$rs_attrs
->{order_by})) {
$chunk
=
$chunk
->[0]
if
(
ref
$chunk
) eq
'ARRAY'
;
(
$chunk
) =
$self
->_split_order_chunk(
$chunk
);
next
if
$in_sel_index
->{
$chunk
};
$extra_order_sel
->{
$chunk
} ||=
$self
->_quote (
'ORDER__BY__'
.
sprintf
'%03d'
,
scalar
keys
%{
$extra_order_sel
||{}}
);
}
return
{
query_leftover
=>
$proto_sql
,
(
map
{(
"selection_$_"
=>
join
(
', '
, @{
$sel
->{
$_
}} ) )}
keys
%$sel
),
outer_renames
=>
$renamed
,
order_supplement
=>
$extra_order_sel
,
};
}
sub
_unqualify_colname {
my
(
$self
,
$fqcn
) =
@_
;
$fqcn
=~ s/ \. /__/xg;
return
$fqcn
;
}
1;